Stacked on #2 (events/module-credential). One commit:
RPC replies ride the mesh.rpc exchange (keyed by the caller's reply queue) rather than the default exchange, so a scoped module account can reply with write on mesh.rpc alone — never the default exchange.
Adds an invoke <module> <tool> [json] subcommand, the caller's side of per-key serving.
Together with the SDK's per-key serving (mesh-sdk #2), this is how a module's tools reach the command surface under the module's own scoped account.
Proven in mesh-lab: assigned-plex/sonarr/grafana serve their tools over this; a caller invokes and gets the tool's answer.
Stacked on #2 (events/module-credential). One commit:
- RPC replies ride the `mesh.rpc` exchange (keyed by the caller's reply queue) rather than the default exchange, so a scoped module account can reply with write on `mesh.rpc` alone — never the default exchange.
- Adds an `invoke <module> <tool> [json]` subcommand, the caller's side of per-key serving.
Together with the SDK's per-key serving (mesh-sdk #2), this is how a module's tools reach the command surface under the module's own scoped account.
Proven in mesh-lab: assigned-plex/sonarr/grafana serve their tools over this; a caller invokes and gets the tool's answer.
Implements novox/hq ADR 0052 (hq PR #20).
https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
jschoubben
changed target branch from events/module-credential to main2026-09-05 01:01:49 +00:00
The AMQP adapter now honours the full contract: events published
persistent with metadata in headers; a durable per-consumer queue
(<node>.<module>.events) with prefetch and a dead-letter exchange
(mesh.events.dead); manual ack for at-least-once.
Failure paths, not just the happy one:
- a confirm channel, so a publish the broker never accepted fails the
emit rather than vanishing — at-least-once starts at the emitter;
- a handler that keeps failing is requeued once, then dead-lettered
(poison set aside, never looping);
- an undecodable body is dead-lettered at once — it never decodes on
redelivery, and must not wedge the queue.
Binding-conformance tests against a disposable broker (a stand-in for the
mesh-hosted broker, ADR 0001): headers on the wire with a pure body, the
redelivery-limit dead-letter, and the poison-body dead-letter.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
'mesh-tools emit <type> [json]' connects, emits one ADR 0047 event (awaiting
the publish confirm), and exits. The serve path already runs a module's
on('#') subscription as an import side effect, so the runtime hosts both an
emitter and the audit-logger consumer.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
A module reads its broker credential from MESH_BROKER_FILE — the sealed
{url,fingerprint} the mesh delivered — and connects over amqps pinned to
exactly that certificate. The pin is two-phase (fetch cert, verify, then
trust only it), because Node's checkServerIdentity does not run under
rejectUnauthorized:false, so a naive connect-then-check would already have
sent the password to whoever answered.
A scoped module (assumeExchanges) never declares the exchanges (its account
may not) nor its own queue with a dead-letter (the broker refuses that to a
non-administrator) — the mesh pre-declared the queue, so it passively checks
it, binds and consumes. The RPC reply queue is lazy, and a module that
registered no tools serves none: a pure-events consumer touches only what its
account allows.
Verified end-to-end against a real broker as the scoped account: the audit
logger consumes # and records events, over an account that is not the
broker's own.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The mesh scoped the account to a node and module; the credential now carries
both, so the runtime names its queue and stamps its events as the mesh
authorised without a manifest interpolating a node the vocabulary has no token
for. Verified: with only MESH_BROKER_FILE, the audit logger consumed as
anchor/audit-logger.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
Replies go through the RPC exchange keyed by the caller's reply-queue name, not
the default exchange — so a serving module's scoped account answers with write
on mesh.rpc alone, never the default exchange (which would let it publish into
any queue). 'mesh-tools invoke <module> <tool> [args]' is the caller's side, the
sibling of emit. Verified against a real broker: a scoped account serves its
tool and is refused another module's serve queue.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Stacked on #2 (events/module-credential). One commit:
mesh.rpcexchange (keyed by the caller's reply queue) rather than the default exchange, so a scoped module account can reply with write onmesh.rpcalone — never the default exchange.invoke <module> <tool> [json]subcommand, the caller's side of per-key serving.Together with the SDK's per-key serving (mesh-sdk #2), this is how a module's tools reach the command surface under the module's own scoped account.
Proven in mesh-lab: assigned-plex/sonarr/grafana serve their tools over this; a caller invokes and gets the tool's answer.
Implements novox/hq ADR 0052 (hq PR #20).
https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
'mesh-tools emit <type> [json]' connects, emits one ADR 0047 event (awaiting the publish confirm), and exits. The serve path already runs a module's on('#') subscription as an import side effect, so the runtime hosts both an emitter and the audit-logger consumer. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzFA module reads its broker credential from MESH_BROKER_FILE — the sealed {url,fingerprint} the mesh delivered — and connects over amqps pinned to exactly that certificate. The pin is two-phase (fetch cert, verify, then trust only it), because Node's checkServerIdentity does not run under rejectUnauthorized:false, so a naive connect-then-check would already have sent the password to whoever answered. A scoped module (assumeExchanges) never declares the exchanges (its account may not) nor its own queue with a dead-letter (the broker refuses that to a non-administrator) — the mesh pre-declared the queue, so it passively checks it, binds and consumes. The RPC reply queue is lazy, and a module that registered no tools serves none: a pure-events consumer touches only what its account allows. Verified end-to-end against a real broker as the scoped account: the audit logger consumes # and records events, over an account that is not the broker's own. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF