Task 3.7 of novox/hq ADR 0116, and the whole of the sdk's diff for the bus
change. Three comments said AMQP where they meant 'message headers' and 'a
broker client'; the code never spoke it, which is why no module is rebuilt
for any of this (ADR 0039).
One name per thing, per the HQ glossary: the module/container/image/binary/repo
becomes mesh-controller, the seat the-controller, and the store+broker pair the
foundation (embedded base bundles, default template and example lock renamed with
their go:embed directives). No behaviour change — a pure vocabulary rename.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
A package artifact, built on a public base and published to the mesh's package
registry by version, so every module resolves it the ordinary way (hq ADR 0076).
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
Grant, Credential and an Interface type lived in contracts, exported and imported
by nothing, describing a grant with fields — resource, consumer — the live wire
does not use. The wire is the contributions file, whose shape (as, secret, node,
at, values) agrees between Go and TypeScript.
These dead types are how ADR 0074 came to claim a drift that inspection does not
find: they read as the contract and were not. A type is only as good as its being
the wire, and one that has drifted from it while still being exported is worse
than no type. Removed.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
It compiles itself on install and then shipped everything except the result.
With no explicit file list npm falls back to .gitignore, which ignores dist —
so every consumer received a package whose every entry point pointed at a
directory that had just been built and then excluded.
The workaround for this lived in mesh-tools, which compiled the dependency by
hand after installing it.
Its entry points all resolve into a compiled directory that is not in source
control, so anything installing it from a clone got a package whose every
export pointed at nothing. npm runs this after a git install; that is what lets
a build with only one repository in front of it depend on this one.
Rewrites the provisioner test to the new contract (a contributions file + an
unsealed secret; the adapter is handed the mesh's login and password, and removal
follows the consumer leaving the file) and drops the seal round-trip test with the
primitive it covered. Also fixes two tool tests left stale by the per-key serving
rework (ADR 0052): invoke by module.tool, and refuse one module's duplicate name
(two modules may now share a name). Suite green: 6 pass.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
runProvisioner now reconciles the mesh's `receives` contributions: for each
consumer it reads the mesh-minted password from the file the host unsealed and calls
the adapter to create the resource under the login the mesh derived. The adapter is
create({as,password,values}) / remove({as}), returning nothing — the consumer
already receives its copy through the mesh's own asymmetric channel. $MESH_SEAL_KEY,
the symmetric seal()/writeSealedCredential path, and the *.grant.json / *.credential
files are gone; the seal()/unseal() primitive had no other caller and was removed.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
serveTools now serves each tool on serve.<module>.<tool> instead of one
tools.invoke that dispatched by name — so a module's account is scoped to
serve.<module>.* and one module cannot answer another's calls. toolKey and
invokeTool are the caller's side. A tool name need only be unique within its
module now, not across the mesh.
emit stamps the ADR 0047 headers — x-event-id, x-source, x-node, x-time,
content-type, and optional x-causation-id / x-schema — and publishes the
body as only the domain payload. on() reconstructs the Event from those
headers. Event gains id (the x-event-id a consumer dedups on) plus the
optional causation/schema. EventHeaders joins the contracts spine.
Supersedes the first cut that carried source/node/time in the body.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The lighter sibling of provisioning — 1:many and broadcast, no credential,
just the broker's topic routing. A thin, audit-ready surface over the
broker's publish/subscribe:
- emit(type, body): publishes an Event carrying who emitted it (MESH_MODULE),
on which node (MESH_NODE) and when (ISO timestamp) — so a listener can
build a real audit trail.
- on(pattern, handler): react to events by topic pattern. The audit logger
is just on("#", ...).
Tested: a module emits; a targeted listener (module.umami.#) hears only its
events, the audit sink (#) hears every module's, and the metadata audit
needs is present.
The declared side — a manifest's emits/consumes, so the mesh knows the
event graph — and the audit-logger module are the next pieces.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
Add serveTools(broker) — the tool runtime's core: collect every module's
registered tools, index by name (refusing a duplicate name across two
modules rather than silently shadowing), and answer 'tools.invoke'
requests by running the named tool and returning its result. Plus
listTools() for discovery and Broker.handle() (the server side of
request/reply).
Proven by test: a real async, network-calling tool is registered (as a
module does), served over an in-memory broker, and invoked by name — it
reaches its upstream and returns the computed result. So a module's tools
genuinely serve: register -> collect -> serve -> invoke -> real work ->
result. The per-node runtime process that binds the mesh's real broker
and imports the assigned modules is the thin wrapper over this.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
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