Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4a4f4d1ba |
@@ -5,17 +5,6 @@
|
|||||||
"invokes": [
|
"invokes": [
|
||||||
"*"
|
"*"
|
||||||
],
|
],
|
||||||
"provides": [
|
|
||||||
{
|
|
||||||
"name": "mcp-endpoint",
|
|
||||||
"scope": "node"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"serves": {
|
|
||||||
"mcp-endpoint": {
|
|
||||||
"port": 4270
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"own-secrets": {
|
"own-secrets": {
|
||||||
"broker": "${dir:mesh-state}/broker"
|
"broker": "${dir:mesh-state}/broker"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { connectNats, fatalBrokerReason as fatalNatsReason, type Credential } from "./broker-nats.js";
|
import { connectNats, fatalBrokerReason as fatalNatsReason, type Credential } from "./broker-nats.js";
|
||||||
import { takeToolEnvs, runTools, type ServedModule } from "./runtime.js";
|
import { runTools, type ServedModule } from "./runtime.js";
|
||||||
import { serveMcpHttp, type Listening } from "./http.js";
|
import { serveMcpHttp, type Listening } from "./http.js";
|
||||||
|
|
||||||
/** The credential this process connected with, for what it says beyond the connection (ADR 0159). */
|
/** The credential this process connected with, for what it says beyond the connection (ADR 0159). */
|
||||||
@@ -163,9 +163,7 @@ async function serve(): Promise<void> {
|
|||||||
const broker = await connectBrokerPatiently();
|
const broker = await connectBrokerPatiently();
|
||||||
// Parsed after connecting: a bare entrypoint belongs to the module the credential names.
|
// Parsed after connecting: a bare entrypoint belongs to the module the credential names.
|
||||||
const { serves, moduleEntrypoints } = servedModulesFrom(process.env.MESH_TOOL_MODULES ?? "", lastCredential?.module);
|
const { serves, moduleEntrypoints } = servedModulesFrom(process.env.MESH_TOOL_MODULES ?? "", lastCredential?.module);
|
||||||
// Each module's environment, composed by the mesh (ADR 0192): taken before any bundle is imported.
|
const stop = await runTools({ broker, serves, moduleEntrypoints, credential: lastCredential });
|
||||||
const envs = takeToolEnvs();
|
|
||||||
const stop = await runTools({ broker, serves, moduleEntrypoints, credential: lastCredential, envs });
|
|
||||||
|
|
||||||
// The console is this runtime's serving mode (ADR 0175 §6): as node-tools, or wherever the
|
// The console is this runtime's serving mode (ADR 0175 §6): as node-tools, or wherever the
|
||||||
// listen address is given, the same process answers MCP on loopback for whoever is on the
|
// listen address is given, the same process answers MCP on loopback for whoever is on the
|
||||||
|
|||||||
+7
-106
@@ -9,12 +9,10 @@
|
|||||||
// and the per-module shape is the list with one entry. A bundle that fails to import is named —
|
// and the per-module shape is the list with one entry. A bundle that fails to import is named —
|
||||||
// in the log and in what `tools` answers for it — and the others serve.
|
// in the log and in what `tools` answers for it — and the others serve.
|
||||||
|
|
||||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { dirname, join, resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { register } from "node:module";
|
||||||
import { registerHooks } from "node:module";
|
|
||||||
import { useBroker } from "@novox/mesh-sdk/messaging";
|
import { useBroker } from "@novox/mesh-sdk/messaging";
|
||||||
import * as sdkTools from "@novox/mesh-sdk/tools";
|
|
||||||
import { collectTools, toolKey, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
import { collectTools, toolKey, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
||||||
import type { Broker } from "@novox/mesh-sdk/messaging";
|
import type { Broker } from "@novox/mesh-sdk/messaging";
|
||||||
import { atWork, seatToolSubject, type Credential, type RuntimeBroker } from "./broker-nats.js";
|
import { atWork, seatToolSubject, type Credential, type RuntimeBroker } from "./broker-nats.js";
|
||||||
@@ -62,39 +60,6 @@ export interface RuntimeOptions {
|
|||||||
* (novox/hq ADR 0159). Absent for a runtime started by hand, which then serves no seat its
|
* (novox/hq ADR 0159). Absent for a runtime started by hand, which then serves no seat its
|
||||||
* memberships do not name. */
|
* memberships do not name. */
|
||||||
credential?: Credential;
|
credential?: Credential;
|
||||||
/** What each served module's bundles are given (novox/hq ADR 0192): module → words, composed by
|
|
||||||
* the mesh per machine. A module absent here is given the runtime's own words and nothing more. */
|
|
||||||
envs?: ReadonlyMap<string, Readonly<Record<string, string>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The variable the mesh composes every served module's environment into, as JSON (ADR 0192). Read
|
|
||||||
* once at start and removed from the process's environment, so no bundle finds another's there. */
|
|
||||||
export const TOOL_ENV = "MESH_TOOL_ENV";
|
|
||||||
|
|
||||||
/** Read and remove the composed environments from an environment (the process's, by default). */
|
|
||||||
export function takeToolEnvs(env: NodeJS.ProcessEnv = process.env): Map<string, Record<string, string>> {
|
|
||||||
const raw = env[TOOL_ENV];
|
|
||||||
delete env[TOOL_ENV];
|
|
||||||
const out = new Map<string, Record<string, string>>();
|
|
||||||
if (!raw) return out;
|
|
||||||
let parsed: unknown;
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(raw);
|
|
||||||
} catch {
|
|
||||||
throw new Error(`${TOOL_ENV} is not JSON; the mesh composes it as {"<module>": {"<word>": "<value>"}}`);
|
|
||||||
}
|
|
||||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
||||||
throw new Error(`${TOOL_ENV} is not an object of modules`);
|
|
||||||
}
|
|
||||||
for (const [module, words] of Object.entries(parsed as Record<string, unknown>)) {
|
|
||||||
if (!words || typeof words !== "object" || Array.isArray(words)) {
|
|
||||||
throw new Error(`${TOOL_ENV}: ${module}'s environment is not an object of words`);
|
|
||||||
}
|
|
||||||
const own: Record<string, string> = {};
|
|
||||||
for (const [k, v] of Object.entries(words as Record<string, unknown>)) own[k] = String(v);
|
|
||||||
out.set(module, own);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Two environment words the mesh sets for the node's runtime and every tool reads from its
|
/** Two environment words the mesh sets for the node's runtime and every tool reads from its
|
||||||
@@ -139,10 +104,6 @@ export async function runTools(opts: RuntimeOptions): Promise<() => void> {
|
|||||||
for (const module of served.keys()) await runtime.follow(module);
|
for (const module of served.keys()) await runtime.follow(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
// What each module's bundles are given: the runtime's own words, and over them the module's own.
|
|
||||||
const envs = opts.envs ?? new Map<string, Record<string, string>>();
|
|
||||||
const envFor = (module: string): NodeJS.ProcessEnv => ({ ...process.env, ...(envs.get(module) ?? {}) });
|
|
||||||
|
|
||||||
// Import each bundle, guarded (ADR 0175: one faulty bundle must not take the node's tools down).
|
// Import each bundle, guarded (ADR 0175: one faulty bundle must not take the node's tools down).
|
||||||
// Importing the entrypoint runs its registerModuleTools(...) — that is the whole handshake — and
|
// Importing the entrypoint runs its registerModuleTools(...) — that is the whole handshake — and
|
||||||
// the registrations it adds are the ones that appear after it, which is how each is attributed
|
// the registrations it adds are the ones that appear after it, which is how each is attributed
|
||||||
@@ -161,7 +122,7 @@ export async function runTools(opts: RuntimeOptions): Promise<() => void> {
|
|||||||
const path = resolve(entry);
|
const path = resolve(entry);
|
||||||
try {
|
try {
|
||||||
if (launches(path)) {
|
if (launches(path)) {
|
||||||
const child = await launch(module, path, envFor(module));
|
const child = await launch(module, path);
|
||||||
children.push(child.stop);
|
children.push(child.stop);
|
||||||
for (const r of child.registrations) launched.push({ ...r, owner: module });
|
for (const r of child.registrations) launched.push({ ...r, owner: module });
|
||||||
continue;
|
continue;
|
||||||
@@ -186,16 +147,8 @@ export async function runTools(opts: RuntimeOptions): Promise<() => void> {
|
|||||||
// out rather than fatal — on 2026-10-01 the credential of a module that had just learned to
|
// out rather than fatal — on 2026-10-01 the credential of a module that had just learned to
|
||||||
// implement a seat did not yet name the claim, and the whole runtime restarted for it.
|
// implement a seat did not yet name the claim, and the whole runtime restarted for it.
|
||||||
const claimed = seatsClaimed(served.keys(), self, opts.credential, runtime);
|
const claimed = seatsClaimed(served.keys(), self, opts.credential, runtime);
|
||||||
// Each registration's contributor is given its own module's environment and no other's (ADR 0192):
|
|
||||||
// the runtime's own words, and over them what the mesh composed for the module whose bundle made
|
|
||||||
// the registration. An SDK too old to ask per registration cannot do that; said, not hidden.
|
|
||||||
const each = (sdkTools as { collectToolsEach?: (f: (module: string, i: number) => NodeJS.ProcessEnv) => { module: string; tools: ToolDefinition[] }[] }).collectToolsEach;
|
|
||||||
if (!each && envs.size > 0) {
|
|
||||||
console.log(`[mesh-tools] this runtime's SDK cannot give each bundle its own environment; ${[...envs.keys()].join(", ")} serve with the runtime's words only (novox/hq ADR 0192)`);
|
|
||||||
}
|
|
||||||
const collected = each ? each((module, i) => envFor(owner[i] ?? self ?? module)) : collectTools();
|
|
||||||
const registrations = [
|
const registrations = [
|
||||||
...collected.map((r, i) => ({ ...r, owner: owner[i] ?? self ?? r.module })),
|
...collectTools().map((r, i) => ({ ...r, owner: owner[i] ?? self ?? r.module })),
|
||||||
...launched,
|
...launched,
|
||||||
];
|
];
|
||||||
const ownRegistrations = registrations.filter(({ module, owner: by }) => {
|
const ownRegistrations = registrations.filter(({ module, owner: by }) => {
|
||||||
@@ -366,61 +319,9 @@ async function serveClaimedSeats(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sdkHooked = false;
|
let sdkHooked = false;
|
||||||
const SDK = "@novox/mesh-sdk";
|
/** Resolve `@novox/mesh-sdk` for every bundle to the copy this runtime imported (sdk-hooks.ts). */
|
||||||
/**
|
|
||||||
* The one SDK in a node's runtime (novox/hq ADR 0175, 04-ISSUES/209).
|
|
||||||
*
|
|
||||||
* A bundle carries its own dependencies — the toolchain copies them in so a bundle starts anywhere
|
|
||||||
* (to-be 38 WP3) — and among them is a copy of the SDK. Imported in this process, that copy would be
|
|
||||||
* a second SDK: its own registry of tools, its own broker handle. A bundle calling registerModuleTools
|
|
||||||
* through it registers into a list this runtime never reads, and its tools are silently not served.
|
|
||||||
* So every import of the SDK, from whichever bundle, is resolved as if this runtime had written it:
|
|
||||||
* one registry, one broker — the runtime's. Everything else a bundle carries resolves from the
|
|
||||||
* bundle's own tree, as before. A launched bundle (ADR 0188) is another process and is untouched.
|
|
||||||
*
|
|
||||||
* Installed once, in-thread, before the first bundle is imported; the hook sees every import after,
|
|
||||||
* `require` included. A bundle whose own copy is another version than the runtime's is said once,
|
|
||||||
* so a tool failing against the runtime's SDK points at the bundle rather than at the runtime.
|
|
||||||
*/
|
|
||||||
function oneSdk(): void {
|
function oneSdk(): void {
|
||||||
if (sdkHooked) return;
|
if (sdkHooked) return;
|
||||||
registerHooks({
|
|
||||||
resolve(specifier, context, next) {
|
|
||||||
if (specifier === SDK || specifier.startsWith(SDK + "/")) {
|
|
||||||
if (context.parentURL) sayOtherSdk(context.parentURL);
|
|
||||||
return next(specifier, { ...context, parentURL: import.meta.url });
|
|
||||||
}
|
|
||||||
return next(specifier, context);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
sdkHooked = true;
|
sdkHooked = true;
|
||||||
}
|
register("./sdk-hooks.js", { parentURL: import.meta.url, data: { runtimeURL: import.meta.url } });
|
||||||
|
|
||||||
const sdkSaid = new Set<string>();
|
|
||||||
/** The version of the SDK copy nearest a file, by its package.json, or nothing when the file has none above it. */
|
|
||||||
function sdkVersionNear(fileURL: string): { dir: string; version: string } | undefined {
|
|
||||||
let dir = dirname(fileURLToPath(fileURL));
|
|
||||||
for (;;) {
|
|
||||||
const pkg = join(dir, "node_modules", SDK, "package.json");
|
|
||||||
if (existsSync(pkg)) {
|
|
||||||
try {
|
|
||||||
return { dir, version: String((JSON.parse(readFileSync(pkg, "utf8")) as { version?: string }).version ?? "?") };
|
|
||||||
} catch {
|
|
||||||
return { dir, version: "?" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const up = dirname(dir);
|
|
||||||
if (up === dir) return undefined;
|
|
||||||
dir = up;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function sayOtherSdk(parentURL: string): void {
|
|
||||||
if (!parentURL.startsWith("file:")) return;
|
|
||||||
const own = sdkVersionNear(import.meta.url);
|
|
||||||
const theirs = sdkVersionNear(parentURL);
|
|
||||||
if (!theirs || !own || theirs.dir === own.dir || sdkSaid.has(theirs.dir)) return;
|
|
||||||
sdkSaid.add(theirs.dir);
|
|
||||||
if (theirs.version !== own.version) {
|
|
||||||
console.log(`[mesh-tools] ${theirs.dir} carries ${SDK} ${theirs.version}; this runtime's is ${own.version}, and the bundle speaks to the runtime's`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// The one SDK in a node's runtime (novox/hq ADR 0175, 04-ISSUES/209).
|
||||||
|
//
|
||||||
|
// A bundle carries its own dependencies — the toolchain copies them in so a bundle starts anywhere
|
||||||
|
// (to-be 38 WP3) — and among them is a copy of @novox/mesh-sdk. Imported in this process, that copy
|
||||||
|
// would be a second SDK: its own registry of tools, its own broker handle. A bundle calling
|
||||||
|
// registerModuleTools through it registers into a list this runtime never reads, and its tools are
|
||||||
|
// silently not served. So every import of the SDK, from whichever bundle, is resolved as if this
|
||||||
|
// runtime had written it: one registry, one broker — the runtime's. Everything else a bundle
|
||||||
|
// carries resolves from the bundle's own tree, as before.
|
||||||
|
//
|
||||||
|
// Installed through module.register(), whose resolve hook sees every import that follows.
|
||||||
|
|
||||||
|
interface ResolveContext {
|
||||||
|
parentURL?: string;
|
||||||
|
conditions: string[];
|
||||||
|
importAttributes: Record<string, string>;
|
||||||
|
}
|
||||||
|
interface Resolved {
|
||||||
|
url: string;
|
||||||
|
format?: string | null;
|
||||||
|
shortCircuit?: boolean;
|
||||||
|
}
|
||||||
|
type NextResolve = (specifier: string, context?: Partial<ResolveContext>) => Promise<Resolved>;
|
||||||
|
|
||||||
|
const SDK = "@novox/mesh-sdk";
|
||||||
|
let runtimeURL = "";
|
||||||
|
|
||||||
|
/** Told, once, which file's tree holds the runtime's SDK. */
|
||||||
|
export function initialize(data: { runtimeURL: string }): void {
|
||||||
|
runtimeURL = data.runtimeURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolve(specifier: string, context: ResolveContext, next: NextResolve): Promise<Resolved> {
|
||||||
|
if (runtimeURL && (specifier === SDK || specifier.startsWith(SDK + "/"))) {
|
||||||
|
return next(specifier, { ...context, parentURL: runtimeURL });
|
||||||
|
}
|
||||||
|
return next(specifier, context);
|
||||||
|
}
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
import { registerModuleTools } from "@novox/mesh-sdk/tools";
|
|
||||||
registerModuleTools("delta", (env) => [
|
|
||||||
{ name: "given", description: "what delta was given", input: {}, run: async () => ({ mine: env.DELTA_TOKEN_FILE ?? null, theirs: env.GAMMA_CONFIG_FILE ?? null }) },
|
|
||||||
]);
|
|
||||||
-5
@@ -1,5 +0,0 @@
|
|||||||
// A bundle that reads what it was given (novox/hq ADR 0192): its contributor's environment.
|
|
||||||
import { registerModuleTools } from "@novox/mesh-sdk/tools";
|
|
||||||
registerModuleTools("gamma", (env) => [
|
|
||||||
{ name: "given", description: "what gamma was given", input: {}, run: async () => ({ mine: env.GAMMA_CONFIG_FILE ?? null, theirs: env.DELTA_TOKEN_FILE ?? null, runtime: env.MESH_OPERATOR_ACCOUNT ?? null, composed: env.MESH_TOOL_ENV ?? null }) },
|
|
||||||
]);
|
|
||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// Launched (ADR 0188): its environment is the child's own.
|
|
||||||
import { serveStdio } from "@novox/mesh-sdk/stdio";
|
|
||||||
await serveStdio("zeta", [
|
|
||||||
{ name: "given", description: "what zeta was given", input: {}, run: async () => ({ mine: process.env.ZETA_URL ?? null, theirs: process.env.GAMMA_CONFIG_FILE ?? null, composed: process.env.MESH_TOOL_ENV ?? null }) },
|
|
||||||
]);
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { cpSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
import { cpSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { connect, StringCodec } from "nats";
|
import { connect, StringCodec } from "nats";
|
||||||
@@ -21,7 +21,7 @@ import { resetTools } from "@novox/mesh-sdk/tools";
|
|||||||
import { connectNats, membershipSubject } from "../dist/broker-nats.js";
|
import { connectNats, membershipSubject } from "../dist/broker-nats.js";
|
||||||
import { callTool, toolsOn } from "../dist/client.js";
|
import { callTool, toolsOn } from "../dist/client.js";
|
||||||
import { servedModulesFrom } from "../dist/main.js";
|
import { servedModulesFrom } from "../dist/main.js";
|
||||||
import { runTools, takeToolEnvs } from "../dist/runtime.js";
|
import { runTools } from "../dist/runtime.js";
|
||||||
|
|
||||||
const url = process.env.MESH_TEST_NATS;
|
const url = process.env.MESH_TEST_NATS;
|
||||||
const fixture = (name: string) => fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url));
|
const fixture = (name: string) => fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url));
|
||||||
@@ -199,93 +199,36 @@ test("the node's runtime serves five modules' bundles on one credential — two
|
|||||||
test("a bundle carrying its own copy of the SDK registers into the runtime's registry, and its tools are served (issue 209)", async (t) => {
|
test("a bundle carrying its own copy of the SDK registers into the runtime's registry, and its tools are served (issue 209)", async (t) => {
|
||||||
if (!url) return t.skip("MESH_TEST_NATS unset");
|
if (!url) return t.skip("MESH_TEST_NATS unset");
|
||||||
resetTools();
|
resetTools();
|
||||||
let dir = "";
|
|
||||||
let stop = () => {};
|
|
||||||
const closing: Array<() => Promise<void>> = [];
|
|
||||||
const said: string[] = [];
|
|
||||||
const log = console.log;
|
|
||||||
try {
|
|
||||||
// A bundle as the toolchain packs one: its compiled entrypoint, a package.json saying ES modules,
|
// A bundle as the toolchain packs one: its compiled entrypoint, a package.json saying ES modules,
|
||||||
// and its dependencies copied in — the SDK among them, a second copy beside the runtime's own,
|
// and its dependencies copied in — the SDK among them, a second copy beside the runtime's own.
|
||||||
// and a dependency of the bundle's own that the runtime does not carry.
|
const dir = mkdtempSync(join(tmpdir(), "mesh-bundle-"));
|
||||||
dir = mkdtempSync(join(tmpdir(), "mesh-bundle-"));
|
|
||||||
const sdk = realpathSync(fileURLToPath(new URL("../node_modules/@novox/mesh-sdk/", import.meta.url)));
|
const sdk = realpathSync(fileURLToPath(new URL("../node_modules/@novox/mesh-sdk/", import.meta.url)));
|
||||||
cpSync(sdk, join(dir, "node_modules", "@novox", "mesh-sdk"), { recursive: true });
|
cpSync(sdk, join(dir, "node_modules", "@novox", "mesh-sdk"), { recursive: true });
|
||||||
mkdirSync(join(dir, "node_modules", "zeta-flavour"), { recursive: true });
|
|
||||||
writeFileSync(join(dir, "node_modules", "zeta-flavour", "package.json"), '{"name":"zeta-flavour","type":"module","main":"index.js"}\n');
|
|
||||||
writeFileSync(join(dir, "node_modules", "zeta-flavour", "index.js"), 'export const flavour = "the bundle\'s own";\n');
|
|
||||||
writeFileSync(join(dir, "package.json"), '{"type":"module","private":true}\n');
|
writeFileSync(join(dir, "package.json"), '{"type":"module","private":true}\n');
|
||||||
writeFileSync(join(dir, "index.js"),
|
writeFileSync(join(dir, "index.js"),
|
||||||
'import { registerModuleTools } from "@novox/mesh-sdk/tools";\n' +
|
'import { registerModuleTools } from "@novox/mesh-sdk/tools";\n' +
|
||||||
'import { flavour } from "zeta-flavour";\n' +
|
'registerModuleTools("zeta", () => [{ name: "probe", description: "answers", input: {}, run: async () => ({ zeta: true }) }]);\n');
|
||||||
'registerModuleTools("zeta", () => [{ name: "probe", description: "answers", input: {}, run: async () => ({ zeta: true, flavour }) }]);\n');
|
|
||||||
const mesh = await aMesh();
|
const mesh = await aMesh();
|
||||||
closing.push(() => mesh.close());
|
|
||||||
await mesh.issue(membershipOf("zeta", "anchor"));
|
await mesh.issue(membershipOf("zeta", "anchor"));
|
||||||
const credential = { url, node: "anchor", module: "node-tools" };
|
const credential = { url, node: "anchor", module: "node-tools" };
|
||||||
const nodeTools = await connectNats(credential);
|
const nodeTools = await connectNats(credential);
|
||||||
closing.push(() => nodeTools.close());
|
|
||||||
const asker = await connectNats({ url, module: "console", node: "workstation" });
|
const asker = await connectNats({ url, module: "console", node: "workstation" });
|
||||||
closing.push(() => asker.close());
|
const said: string[] = [];
|
||||||
|
const log = console.log;
|
||||||
console.log = (...a: unknown[]) => said.push(a.join(" "));
|
console.log = (...a: unknown[]) => said.push(a.join(" "));
|
||||||
|
let stop = () => {};
|
||||||
|
try {
|
||||||
stop = await runTools({ broker: nodeTools, credential, serves: [{ module: "zeta", entrypoints: [join(dir, "index.js")] }] });
|
stop = await runTools({ broker: nodeTools, credential, serves: [{ module: "zeta", entrypoints: [join(dir, "index.js")] }] });
|
||||||
console.log = log;
|
console.log = log;
|
||||||
assert.ok(said.some((s) => /serving 1 tool\(s\) for 1 module\(s\): zeta\.probe/.test(s)), said.join("\n"));
|
assert.ok(said.some((s) => /serving 1 tool\(s\) for 1 module\(s\): zeta\.probe/.test(s)), said.join("\n"));
|
||||||
// The SDK is the runtime's (the registration arrived); the bundle's other dependency is its own.
|
assert.deepEqual((await callTool(asker, "zeta.probe@anchor", {})).result, { zeta: true });
|
||||||
assert.deepEqual((await callTool(asker, "zeta.probe@anchor", {})).result, { zeta: true, flavour: "the bundle's own" });
|
|
||||||
} finally {
|
} finally {
|
||||||
console.log = log;
|
console.log = log;
|
||||||
stop();
|
stop();
|
||||||
for (const close of closing.reverse()) await close();
|
|
||||||
if (dir) rmSync(dir, { recursive: true, force: true });
|
|
||||||
resetTools();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("each bundle is given its own environment and none of another's, imported or launched (ADR 0192)", async (t) => {
|
|
||||||
if (!url) return t.skip("MESH_TEST_NATS unset");
|
|
||||||
resetTools();
|
|
||||||
const mesh = await aMesh();
|
|
||||||
for (const m of ["gamma", "delta", "zeta"]) await mesh.issue(membershipOf(m, "anchor"));
|
|
||||||
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 = () => {};
|
|
||||||
const before = process.env.MESH_TOOL_ENV;
|
|
||||||
try {
|
|
||||||
process.env.MESH_OPERATOR_ACCOUNT = "somebody";
|
|
||||||
process.env.MESH_TOOL_ENV = JSON.stringify({
|
|
||||||
gamma: { GAMMA_CONFIG_FILE: "/var/lib/mesh/gamma/config.json" },
|
|
||||||
delta: { DELTA_TOKEN_FILE: "/var/lib/mesh/delta/token" },
|
|
||||||
zeta: { ZETA_URL: "http://127.0.0.1:3000" },
|
|
||||||
});
|
|
||||||
const envs = takeToolEnvs();
|
|
||||||
assert.equal(process.env.MESH_TOOL_ENV, undefined, "the composed environments were left in the process's");
|
|
||||||
console.log = () => {};
|
|
||||||
stop = await runTools({
|
|
||||||
broker: nodeTools, credential, envs,
|
|
||||||
serves: [
|
|
||||||
{ module: "gamma", entrypoints: [fixture("env-gamma.mjs")] },
|
|
||||||
{ module: "delta", entrypoints: [fixture("env-delta.mjs")] },
|
|
||||||
{ module: "zeta", entrypoints: [fixture("env-zeta.mjs")] },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
console.log = log;
|
|
||||||
assert.deepEqual((await callTool(asker, "gamma.given@anchor", {})).result,
|
|
||||||
{ mine: "/var/lib/mesh/gamma/config.json", theirs: null, runtime: "somebody", composed: null });
|
|
||||||
assert.deepEqual((await callTool(asker, "delta.given@anchor", {})).result,
|
|
||||||
{ mine: "/var/lib/mesh/delta/token", theirs: null });
|
|
||||||
assert.deepEqual((await callTool(asker, "zeta.given@anchor", {})).result,
|
|
||||||
{ mine: "http://127.0.0.1:3000", theirs: null, composed: null });
|
|
||||||
} finally {
|
|
||||||
console.log = log;
|
|
||||||
if (before === undefined) delete process.env.MESH_TOOL_ENV; else process.env.MESH_TOOL_ENV = before;
|
|
||||||
delete process.env.MESH_OPERATOR_ACCOUNT;
|
|
||||||
stop();
|
|
||||||
await asker.close();
|
await asker.close();
|
||||||
await nodeTools.close();
|
await nodeTools.close();
|
||||||
await mesh.close();
|
await mesh.close();
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
resetTools();
|
resetTools();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user