Stand up mesh-sdk — the stable spine a module builds against

Per novox/hq ADR 0044/0045: the sdk holds only what rarely changes and
is shared across modules; per-module code (a client, tool impls, a
create-a-resource adapter) lives in the module.

Five areas, real and tested:
- contracts: the runtime shapes module code touches (grant, credential,
  a mesh Interface, tool + envelope types) — not the manifest schema,
  which the control plane owns.
- provisioner: the reconcile harness every provider shares (watch grants,
  create via the module's adapter, seal + write the credential, remove on
  withdrawal). A module writes only the adapter.
- tools: registerModuleTools + collectTools — the serving harness; tools
  and their client live in the module.
- messaging: the Broker/Envelope/event contract over the mesh broker; the
  concrete binding is provided by the hosting runtime.
- primitives: AES-256-GCM seal/unseal, semver, resolved-env access.

Compiles (tsc, NodeNext) and passes tests: sealing round-trip + wrong-key
rejection, semver, tool registration (a thrower is skipped not fatal), and
the provisioner creating then removing a sealed grant.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-03 23:01:59 +02:00
commit a19a2f5cf0
12 changed files with 639 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
// The tool-serving harness. A module declares its tools through registerModuleTools; a runtime
// (the mesh's per-node tool host) collects the registrations and serves them through the command
// surface. HOW a tool is declared and served is settled and lives here; the tools themselves, and
// the API client they call, live in the module (novox/hq ADR 0044).
import type { ToolDefinition } from "../contracts/index.js";
export type { ToolDefinition };
/** A module contributes its tools as a function of its resolved environment. Returning [] (e.g.
* when a token is absent) is normal — the module simply exposes nothing until it can. */
export type ToolContributor = (env: NodeJS.ProcessEnv) => ToolDefinition[];
interface Registration {
readonly module: string;
readonly contribute: ToolContributor;
}
const registrations: Registration[] = [];
/**
* Declare the tools a module exposes. Called once, at module-tool load time, from the module's
* tools/ entrypoint. The client and the tool implementations are imported from the module itself.
*/
export function registerModuleTools(module: string, contribute: ToolContributor): void {
registrations.push({ module, contribute });
}
/**
* Collect every registered module's tools against an environment. The tool runtime calls this
* after loading the assigned modules' tool entrypoints. A module whose contributor throws is
* skipped with its error surfaced, never taking the others down.
*/
export function collectTools(env: NodeJS.ProcessEnv = process.env): { module: string; tools: ToolDefinition[] }[] {
return registrations.map(({ module, contribute }) => {
try {
return { module, tools: contribute(env) };
} catch (err) {
console.error(`[tools] ${module}: contributor failed, exposing none: ${err}`);
return { module, tools: [] };
}
});
}
/** Testing/inspection: drop all registrations. */
export function resetTools(): void {
registrations.length = 0;
}