From fb5799b8c9c0bdfdd658b7c9d569de98cd6ba58e Mon Sep 17 00:00:00 2001 From: tada5hi Date: Fri, 26 Jun 2026 17:11:32 +0200 Subject: [PATCH] docs(node-message-broker): refresh README for the completed broker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The service was feature-complete (S1–S6) but its README still described the build as "in progress". Replace the stale Status section with: - a Data flow section (send / wakeup / pull / deliver) noting the analysisId HKDF binding, - an HTTP API table for the flamesdk-compatible container surface, and - a Layout that matches the actual core/ + adapters/ + app/modules tree. Also tighten the root README service description (flamesdk-compatible, pulls inbound). --- README.md | 2 +- apps/node-message-broker/README.md | 77 ++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ffb1fc5..9622894 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ architecture as the Hub backend services. | Service | Description | |---------|-------------| -| **[node-message-broker](apps/node-message-broker)** πŸ’¬ | Node-side message broker β€” container-facing REST API, end-to-end crypto, local delivery; relays to the Hub durable mailbox. Replaces the legacy Java `node-message-broker`. | +| **[node-message-broker](apps/node-message-broker)** πŸ’¬ | Node-side message broker β€” `flamesdk`-compatible container REST API, node-to-node end-to-end crypto, and local webhook delivery; relays to and pulls from the Hub durable mailbox. Replaces the legacy Java `node-message-broker`. | _Further node-side services may be added here over time; others may remain in their own repositories._ diff --git a/apps/node-message-broker/README.md b/apps/node-message-broker/README.md index 1c074c2..8892f70 100644 --- a/apps/node-message-broker/README.md +++ b/apps/node-message-broker/README.md @@ -37,26 +37,59 @@ A thin TypeScript service β€” the successor to the legacy Java `node-message-bro only ever sees ciphertext. 3. **Hub link** β€” REST `send` / `pull` / `ack` against the Hub durable mailbox via `@privateaim/messenger-http-kit`, plus the SSE wakeup stream that triggers pulls. -4. **Local delivery** β€” webhook fan-out (default) + optional container pull. +4. **Local delivery** β€” webhook fan-out to the analysis container. 5. **Node-side analysis policy** β€” capability check (`ANALYSIS_SELF_MESSAGE_BROKER_USE`) + participant resolution via server-core. Durability and routing live in the **Hub** broker; this service is an encrypt/decrypt + local-delivery adapter. See the design authority (Plan 013, Track B). -## Status +## Data flow -In progress. In place: the hexagonal skeleton, config, HTTP server, -webhook-subscription CRUD + fan-out delivery, the **Hub link** (REST `send` / `pull` / -`ack` via `@privateaim/messenger-http-kit` + a reconnecting SSE wakeup stream, both -authenticated as the node client), and the **end-to-end crypto** adapter (`seal` / -`open` over `@privateaim/kit`'s `crypto/message`). +The broker is a thin encrypt/decrypt + local-delivery adapter in front of the Hub's +durable, analysis-agnostic mailbox. `analysisId` rides in message `metadata`; the Hub never +interprets it. -Still open (Plan 013 Track B, Phase 4): wiring the `onWakeup` β†’ pull β†’ decrypt β†’ -`delivery.deliver()` loop, the container-facing message routes (`POST /:id/messages`, -`…/broadcast`, `GET /:id/participants`, `…/participants/self`, and the additive pull -endpoint), and the analysis policy (`ANALYSIS_SELF_MESSAGE_BROKER_USE`) + participant -resolution β€” the `core/analysis` ports exist, but no adapter is wired yet. +``` +send: Container ──REST──▢ Broker ──[seal per recipient, analysisId-bound]──▢ Hub /messages +notify: Hub ──SSE "messagePending" (payload-free)──▢ Broker (long-poll pull is the fallback) +pull: Broker ──GET /messages (cursor, long-poll)──▢ Hub +deliver: Broker ──[open, decrypt]──▢ webhook POST to the analysis container +``` + +- **Send** β€” resolve the analysis participants (server-core), seal the payload **once per + recipient** under that node's ECDH public key, and relay one Hub message per recipient. +- **Inbound** β€” a payload-free wakeup (or the long-poll fallback) triggers a pull; each + message's **sender** node key is resolved, the frame is decrypted, and the plaintext is + fanned out verbatim to the analysis's registered webhooks, then acked (delete-on-ack, + at-least-once). +- **Crypto** β€” node-to-node ECDH (P-256) + per-message HKDF + AES-256-GCM via + `@privateaim/kit`. `analysisId` is bound into the HKDF `info` on both `seal` and `open`, + so a relabelled `metadata.analysisId` fails to decrypt rather than mis-routing. + +## HTTP API + +The container-facing surface (auth: node-local Authup JWT β€” the analysis presents its +`KEYCLOAK_TOKEN`). Every `/analyses/:id/*` route additionally requires the +`ANALYSIS_SELF_MESSAGE_BROKER_USE` capability and that the caller's client owns the analysis. +The surface is kept compatible with the FLAME `flamesdk` (verified against +[`python-sdk`](https://github.com/PrivateAIM/python-sdk)). + +| Method & path | Body | Response | +|---|---|---| +| `POST /analyses/:id/messages` | `{ recipients: string[] /* node ids */, message: }` | `202`, empty | +| `POST /analyses/:id/messages/broadcast` | `{ message: }` | `202`, empty | +| `GET /analyses/:id/participants` | β€” | `[{ nodeId, nodeType }]` (bare array) | +| `GET /analyses/:id/participants/self` | β€” | `{ nodeId, nodeType }` (`404` if absent) | +| `POST /analyses/:id/messages/subscriptions` | `{ webhookUrl }` | registered subscription | +| `GET /analyses/:id/messages/subscriptions` | β€” | `{ data, meta: { total } }` | +| `DELETE /analyses/:id/messages/subscriptions` | `{ webhookUrl }` | unregistered | +| `GET /healthz` | β€” | liveness (unauthenticated) | + +`message` is an opaque JSON payload relayed verbatim β€” the SDK round-trips its own envelope +(`meta.id`, `sender`, …) inside it; the broker never mints ids or wraps the payload. Inbound +delivery is **webhook-push only** (no pull endpoint). Request bodies are validated with +validup + zod. ## Configuration @@ -83,9 +116,23 @@ npm run cli -- start ``` src/ -β”œβ”€β”€ core/ # ports β€” hub link, crypto, local delivery, analysis policy (no infra imports) -β”œβ”€β”€ adapters/ # implementations β€” http controllers, hub client + SSE wakeup, crypto, delivery -└── app/ # orchestration β€” builder, factory, DI modules (config, components, http) +β”œβ”€β”€ core/ # ports + domain logic (no infra imports) +β”‚ β”œβ”€β”€ hub/ # IHubClient β€” send / pull / ack / onWakeup +β”‚ β”œβ”€β”€ crypto/ # ICryptoService β€” seal / open +β”‚ β”œβ”€β”€ delivery/ # IDeliveryService β€” webhook registry + fan-out +β”‚ β”œβ”€β”€ analysis/ # participant resolver + analysis-scope policy +β”‚ β”œβ”€β”€ authz/ # capability-check gateway port +β”‚ β”œβ”€β”€ messaging/ # outbound send / broadcast orchestration +β”‚ └── inbound/ # inbound delivery loop (pull β†’ decrypt β†’ deliver β†’ ack) +β”œβ”€β”€ adapters/ # external implementations +β”‚ β”œβ”€β”€ http/ # routup controllers + permission-checker middleware +β”‚ β”œβ”€β”€ hub/ # HubClient + reconnecting SSE wakeup source +β”‚ β”œβ”€β”€ crypto/ # CryptoService over @privateaim/kit +β”‚ β”œβ”€β”€ core/ # server-core participant resolver +β”‚ β”œβ”€β”€ authz/ # Authup permission gateway + provider +β”‚ └── delivery/ # in-memory webhook delivery +└── app/ # orchestration β€” builder, factory, DI modules + └── modules/ # config Β· components Β· core-client Β· inbound Β· http ``` ## License