{"_id":"@brainst0rm/relay","name":"@brainst0rm/relay","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@brainst0rm/relay","version":"0.1.0","repository":{"type":"git","url":"git+https://github.com/justinjilg/brainstorm.git","directory":"packages/relay"},"type":"module","description":"Brainstorm-platform relay service: governed dispatch between operators (CLI/SDK) and endpoints (brainstorm-agent). Per-envelope Ed25519 signing with domain separation; RFC 8785 JCS canonicalization with NFC normalization; SQLite audit log with channel-of-","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./canonical":{"types":"./dist/canonical.d.ts","import":"./dist/canonical.js"},"./signing":{"types":"./dist/signing.d.ts","import":"./dist/signing.js"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"}},"bin":{"brainstorm-relay":"dist/bin.js"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","test":"vitest run","start":"node dist/bin.js"},"dependencies":{"@brainst0rm/shared":"0.14.3","@noble/ed25519":"^2.1.0","@noble/hashes":"^1.5.0","better-sqlite3":"^11.3.0","canonicalize":"^2.0.0","ws":"^8.18.0"},"devDependencies":{"@types/better-sqlite3":"^7.6.11","@types/ws":"^8.5.12","@types/node":"^22.0.0","tsup":"^8.3.0","typescript":"^5.6.0","vitest":"^2.1.0"},"_id":"@brainst0rm/relay@0.1.0","gitHead":"10e9a5392bcf59b26446ef84a942d5b89cecc491","bugs":{"url":"https://github.com/justinjilg/brainstorm/issues"},"homepage":"https://github.com/justinjilg/brainstorm#readme","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-+XL6qvZa4g+DbnjB3dxQnN+n0g/5kLUItHHT08Sb13M03in7Vy6vTMstTTqRCnH9frga2tD1OeOSwumcNM5VSQ==","shasum":"81ff546f587e6b2650548e0c159f7c89509873bb","tarball":"https://registry.npmjs.org/@brainst0rm/relay/-/relay-0.1.0.tgz","fileCount":126,"unpackedSize":711836,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brainst0rm%2frelay@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEuYD7344GR6XAgE8HotCwsFcech7vdrRz0rWcdrgPANAiB8YJ7aI/6LMeH3369IHIbXQqPOj8iDCstrUWXkBp+GIw=="}]},"_npmUser":{"name":"justinjilg","email":"justin.jilg@gmail.com"},"directories":{},"maintainers":[{"name":"justinjilg","email":"justin.jilg@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/relay_0.1.0_1778932008086_0.4414405598605362"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-16T11:46:47.984Z","0.1.0":"2026-05-16T11:46:48.296Z","modified":"2026-05-16T11:46:48.674Z"},"maintainers":[{"name":"justinjilg","email":"justin.jilg@gmail.com"}],"description":"Brainstorm-platform relay service: governed dispatch between operators (CLI/SDK) and endpoints (brainstorm-agent). Per-envelope Ed25519 signing with domain separation; RFC 8785 JCS canonicalization with NFC normalization; SQLite audit log with channel-of-","homepage":"https://github.com/justinjilg/brainstorm#readme","repository":{"type":"git","url":"git+https://github.com/justinjilg/brainstorm.git","directory":"packages/relay"},"bugs":{"url":"https://github.com/justinjilg/brainstorm/issues"},"readme":"# @brainst0rm/relay\n\nBrainstorm-platform relay service: governed dispatch between operators (CLI/SDK) and endpoints (`brainstorm-agent`). Implements the wire protocol from `docs/endpoint-agent-protocol-v1.md`.\n\n## Architecture\n\nThree transports:\n\n```\noperator (CLI/SDK)  ←──WS──→  brainstorm-relay  ←──WS──→  brainstorm-agent (endpoint)\n                                                            ↑\n                                                            ↓ vsock\n                                                          microVM (CHV/VF sandbox)\n```\n\nThe relay is the platform-layer service per plan v3.2 D9: NEW service, NOT an extension of MSP's relay. MSP becomes a consumer of this relay alongside other future products.\n\n## Run (laptop dev)\n\n```bash\n# Generate secrets (one-time per dev environment)\nexport BRAINSTORM_RELAY_ADMIN_TOKEN=$(openssl rand -hex 32)\nexport BRAINSTORM_RELAY_TENANT_KEY_HEX=$(openssl rand -hex 32)\nexport BRAINSTORM_RELAY_OPERATOR_HMAC_KEY_HEX=$(openssl rand -hex 32)\n\n# Optional: set non-default ids/ports\nexport BRAINSTORM_RELAY_OPERATOR_ID=\"alice@example.com\"\nexport BRAINSTORM_RELAY_TENANT_ID=\"tenant-local\"\nexport BRAINSTORM_RELAY_PORT_WS=8443\nexport BRAINSTORM_RELAY_PORT_HTTP=8444\n\nnpm install\nnpm run build\nnpm start\n```\n\nOutput:\n\n```\n[relay] WS listening on 127.0.0.1:8443 (paths /v1/operator, /v1/endpoint/connect)\n[relay] HTTP enrollment listening on 127.0.0.1:8444\n[relay] data dir: ~/.brainstorm/relay\n[relay] tenant_id: tenant-local, operator_id: alice@example.com\n```\n\n## Modules\n\n| Module             | Purpose                                                                                                                            |\n| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |\n| `canonical.ts`     | RFC 8785 JCS + NFC normalization + 5 SIGN_CONTEXT prefixes; `__proto__` defense; key-collision detection                           |\n| `signing.ts`       | Ed25519 sign/verify with `ed25519-jcs-sha256-v1` algorithm; safe HMAC wrapper; constant-time compare                               |\n| `operator-key.ts`  | HKDF-SHA-256 mandatory operator-key derivation per spec §3.2                                                                       |\n| `verification.ts`  | Operator HMAC verify + endpoint connection-proof Ed25519 verify with clock-skew bounds                                             |\n| `audit.ts`         | SQLite audit log with channel-of-origin discipline; verbatim operator-bytes; hash verification                                     |\n| `nonce-store.ts`   | Persistent nonce-replay store; min 100k capacity; fail-closed `NONCE_CACHE_FULL` semantics                                         |\n| `session-store.ts` | Operator + endpoint session registry; reconnect-replaces-prior; stale-session detection                                            |\n| `lifecycle.ts`     | 7-state machine (`pending\\|dispatched\\|started\\|progress\\|completed\\|failed\\|timed_out`); pure `nextState`; late-arrival semantics |\n| `dispatch.ts`      | `DispatchOrchestrator` — operator request → ChangeSetPreview → signed CommandEnvelope                                              |\n| `result-router.ts` | Endpoint frame → operator frame fanout; stale-session + endpoint-identity binding                                                  |\n| `ack-timeout.ts`   | 5s ACK-timeout timer with injectable clock; relay-observable per V3-ACK-01                                                         |\n| `relay-server.ts`  | Glue: handshake + frame dispatch + fanout                                                                                          |\n| `ws-binding.ts`    | Actual `ws` library wrapper; binds operator + endpoint paths                                                                       |\n| `enrollment.ts`    | HTTP endpoints for endpoint registration (admin issues token, agent enrolls public key)                                            |\n| `bin.ts`           | Entry point — wires all modules from env config                                                                                    |\n| `types.ts`         | Wire types from spec §13 schemas                                                                                                   |\n\n## Cryptographic invariants\n\nThe foundation crypto is the most-reviewed surface. Key invariants enforced:\n\n1. **NFC-then-JCS, never JCS-then-NFC** — applying NFC to JCS output bytes would mutate already-canonical bytes and break verification (`canonical.ts` step 2).\n\n2. **Domain separation via SIGN_CONTEXT prefix** — every signing context (CommandEnvelope, ConnectionProof, BootstrapToken, OperatorHmac, EvidenceChunk) prepends a unique byte string before SHA-256 + Ed25519. Cross-context replay (using a CommandEnvelope sig as a connection proof) fails because the prefix differs.\n\n3. **`__proto__` own-key preservation** — `nfcNormalize` uses `Object.create(null) + Object.defineProperty` to prevent the legacy `__proto__` setter from silently dropping a wire field from the canonical form (Codex blocking finding).\n\n4. **NFC key-collision rejection** — if two distinct wire keys (e.g. `\"café\"` NFD + `\"café\"` NFC) normalize to the same form, `nfcNormalize` throws `NfcKeyCollisionError`. Last-write-wins canonical bytes would violate signing injectivity.\n\n5. **Signed `target_endpoint_id`** — every CommandEnvelope is bound to its target endpoint. Cross-endpoint replay within the same tenant fails with `WRONG_AUDIENCE`.\n\n6. **Persistent nonce store, fail-closed** — nonces survive endpoint restart (SQLite); under capacity pressure, relay rejects new envelopes with `NONCE_CACHE_FULL` rather than evicting unexpired entries.\n\n7. **Channel-of-origin via AuditLogEntry wrapper** — anti-contamination is structural at the audit layer, not a wire-layer convention. Operator-payload bytes are preserved verbatim; relay-internal annotations live in `metadata_sidecar`.\n\n## Test coverage\n\nVitest. Run with `npm test`.\n\n```\ncanonical.test.ts        — NFC + JCS + domain separation + __proto__ regression + collision rejection\nsigning.test.ts          — Ed25519 sign/verify + cross-context rejection + tamper detection + HMAC\noperator-key.test.ts     — HKDF deterministic + sensitivity to ikm/info\naudit.test.ts            — channel-of-origin + verbatim operator-bytes + hash verification\nnonce-store.test.ts      — fresh accept + replay reject + eviction window + restart durability\nsession-store.test.ts    — operator/endpoint registry + reconnect + stale-session detection\nlifecycle.test.ts        — happy path + late_arrival + invalid_transition + ACK timeout\nverification.test.ts     — operator HMAC + connection-proof Ed25519 + clock-skew bounds\nresult-router.test.ts    — ACK→started + stale-session reject + cross-endpoint reject + late-arrival audit\nack-timeout.test.ts      — fake-clock-based timer correctness\ndispatch.test.ts         — orchestrator happy path + tenant-mismatch + preview-hash mismatch\nenrollment.test.ts       — token issue + atomic consume + revoke + re-enrollment\nintegration.test.ts      — end-to-end dispatch happy path + rejection paths\n```\n\n## Status\n\nMVP foundation complete. Ready for:\n\n- Brainstorm CLI dispatch subcommand (P1.2) to consume operator-side WS path\n- brainstorm-agent extension (P1.3, owned by `crd4sdom`) to consume endpoint-side WS path\n- Stage 1.0 loopback validation (P1.4)\n\nPost-MVP backlog:\n\n- TLS termination directly in the relay (currently relies on reverse proxy)\n- Operator identity registry (multi-operator support; MVP is single operator)\n- Multi-tenant signing keys\n- Reconnect with offline command queue (D16)\n- Production deployment target finalization (D17)\n- HA relay (D8: single-instance MVP)\n","readmeFilename":"README.md","_rev":"1-02865b795f13dfadf5119a54ac283ca4"}