Merge pull request 'collectToolsEach: each registration is given its own environment (hq ADR 0192)' (#12) from feat/0192-each-registration-its-own-env into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on

This commit is contained in:
2026-10-03 13:32:34 +00:00
3 changed files with 38 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@novox/mesh-sdk",
"version": "0.1.2",
"version": "0.1.3",
"description": "The stable spine a Novox Mesh module's own code builds against.",
"type": "module",
"exports": {
+21
View File
@@ -49,6 +49,27 @@ export function collectTools(env: NodeJS.ProcessEnv = process.env): { module: st
});
}
/**
* Collect every registered module's tools, each contributor given ITS OWN environment (novox/hq ADR
* 0192): `envFor` is asked with the registration's module and its index in registration order, and
* whatever it answers is all that contributor sees. A node's runtime serves many modules in one
* process, and a module's paths and words must reach its own tools and no other's — so the runtime
* that knows which bundle made which registration says which environment each gets, and the shared
* `collectTools(env)` stays what it was for a runtime serving one module.
*/
export function collectToolsEach(
envFor: (module: string, index: number) => NodeJS.ProcessEnv,
): { module: string; tools: ToolDefinition[] }[] {
return registrations.map(({ module, contribute }, index) => {
try {
return { module, tools: contribute(envFor(module, index)) };
} catch (err) {
console.error(`[tools] ${module}: contributor failed, exposing none: ${err}`);
return { module, tools: [] };
}
});
}
/** List the tools every registered module exposes — for discovery, without invoking anything. */
export function listTools(env: NodeJS.ProcessEnv = process.env): { module: string; name: string; description: string }[] {
return collectTools(env).flatMap(({ module, tools }) =>
+16 -1
View File
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { compareVersions } from "../dist/primitives/index.js";
import { registerModuleTools, collectTools, resetTools, serveTools, listTools, invokeTool } from "../dist/tools/index.js";
import { registerModuleTools, collectTools, collectToolsEach, resetTools, serveTools, listTools, invokeTool } from "../dist/tools/index.js";
import { runProvisioner } from "../dist/provisioner/index.js";
import { emit, on, type Event } from "../dist/events/index.js";
import { useBroker } from "../dist/messaging/index.js";
@@ -482,3 +482,18 @@ async function waitFor(cond: () => boolean, ms: number): Promise<void> {
await new Promise((r) => setTimeout(r, 10));
}
}
test("each registration is given the environment asked for it, and sees no other's (ADR 0192)", () => {
resetTools();
const seen: Record<string, unknown> = {};
registerModuleTools("alpha", (env) => { seen.alpha = { ...env }; return [{ name: "a", description: "", input: {}, run: async () => env.ALPHA_FILE }]; });
registerModuleTools("beta", (env) => { seen.beta = { ...env }; return [{ name: "b", description: "", input: {}, run: async () => env.BETA_URL }]; });
const envs: Record<string, NodeJS.ProcessEnv> = { alpha: { ALPHA_FILE: "/a/config.json" }, beta: { BETA_URL: "http://127.0.0.1:3000" } };
const asked: string[] = [];
const got = collectToolsEach((module, index) => { asked.push(`${module}#${index}`); return envs[module] ?? {}; });
assert.deepEqual(got.map((g) => g.module), ["alpha", "beta"]);
assert.deepEqual(asked, ["alpha#0", "beta#1"]);
assert.deepEqual(seen.alpha, { ALPHA_FILE: "/a/config.json" });
assert.deepEqual(seen.beta, { BETA_URL: "http://127.0.0.1:3000" });
resetTools();
});