From e63dfc68cdbe1ac90d34c49c24beb87745a238e7 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 15:28:48 +0200 Subject: [PATCH] collectToolsEach: each registration is given its own environment (hq ADR 0192) A node's runtime serves many modules in one process; a module's composed paths and words must reach its own tools and no other's. The runtime knows which bundle made which registration, so it says which environment each contributor gets. collectTools(env) is unchanged. 0.1.3. --- package.json | 2 +- src/tools/index.ts | 21 +++++++++++++++++++++ test/sdk.test.ts | 17 ++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3ca13b9..71c3592 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/tools/index.ts b/src/tools/index.ts index bfeb155..f4a8c91 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -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 }) => diff --git a/test/sdk.test.ts b/test/sdk.test.ts index 209ae01..39034ad 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -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 { 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 = {}; + 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 = { 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(); +});