From 302645dd01d07f69e3ea238450daa3ae6cd7730a Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 21:07:52 +0200 Subject: [PATCH] A launched bundle is told the module it serves (hq ADR 0193) node-tools sets MESH_SERVED_MODULE for each child it launches, so the SDK's stdio loop lists that module's tools by their names and a seat's verbs as the seat's, whichever was registered first. --- node-tools/src/runtime.ts | 3 ++- .../test/fixtures/served-seat-first.mjs | 8 ++++++ node-tools/test/node-runtime.test.ts | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100755 node-tools/test/fixtures/served-seat-first.mjs diff --git a/node-tools/src/runtime.ts b/node-tools/src/runtime.ts index 9cfe86f..7993c2f 100644 --- a/node-tools/src/runtime.ts +++ b/node-tools/src/runtime.ts @@ -161,7 +161,8 @@ export async function runTools(opts: RuntimeOptions): Promise<() => void> { const path = resolve(entry); try { if (launches(path)) { - const child = await launch(module, path, envFor(module)); + // Told the module it serves it as, so a seat's verbs are the seat's (ADR 0193). + const child = await launch(module, path, { ...envFor(module), MESH_SERVED_MODULE: module }); children.push(child.stop); for (const r of child.registrations) launched.push({ ...r, owner: module }); continue; diff --git a/node-tools/test/fixtures/served-seat-first.mjs b/node-tools/test/fixtures/served-seat-first.mjs new file mode 100755 index 0000000..24de78a --- /dev/null +++ b/node-tools/test/fixtures/served-seat-first.mjs @@ -0,0 +1,8 @@ +#!/usr/bin/env node +// A launched bundle registering its seat before its own tools, served through the SDK's loop +// (novox/hq ADR 0193): which is the seat's and which the module's comes from the name it is given. +import { registerModuleTools } from "@novox/mesh-sdk/tools"; +import { serveRegisteredOverStdio } from "@novox/mesh-sdk/stdio"; +registerModuleTools("node-shelf", () => [{ name: "list", description: "the shelf", input: {}, run: async () => ({ shelf: ["x"] }) }]); +registerModuleTools("theta", (env) => [{ name: "own", description: "theta's", input: {}, run: async () => ({ theta: env.THETA_WORD ?? null }) }]); +await serveRegisteredOverStdio(); diff --git a/node-tools/test/node-runtime.test.ts b/node-tools/test/node-runtime.test.ts index 24fd813..96c4b65 100644 --- a/node-tools/test/node-runtime.test.ts +++ b/node-tools/test/node-runtime.test.ts @@ -289,3 +289,30 @@ test("each bundle is given its own environment and none of another's, imported o resetTools(); } }); + +test("a launched bundle is told the module it serves, so its seat's verbs stay the seat's (ADR 0193)", async (t) => { + if (!url) return t.skip("MESH_TEST_NATS unset"); + resetTools(); + const mesh = await aMesh(); + await mesh.issue(membershipOf("theta", "anchor", { seats: { "node-shelf": ["list"] } })); + const credential = { url, node: "anchor", module: "node-tools" }; + const nodeTools = await connectNats(credential); + const asker = await connectNats({ url, module: "console", node: "workstation" }); + const log = console.log; + let stop = () => {}; + try { + console.log = () => {}; + stop = await runTools({ broker: nodeTools, credential, envs: new Map([["theta", { THETA_WORD: "given" }]]), + serves: [{ module: "theta", entrypoints: [fixture("served-seat-first.mjs")] }] }); + console.log = log; + assert.deepEqual((await callTool(asker, "theta.own@anchor", {})).result, { theta: "given" }); + assert.deepEqual((await callTool(asker, "seat:node-shelf.list@anchor", {})).result, { shelf: ["x"] }); + } finally { + console.log = log; + stop(); + await asker.close(); + await nodeTools.close(); + await mesh.close(); + resetTools(); + } +});