feat(observer): M-Obs 1-3 — event bus, server hooks, observer backend

M-Obs 1: Event bus in @shade/core
- ShadeEventEmitter with typed event union, ring buffer for replay
- 12 event types covering session lifecycle, ratchet operations,
  prekey changes, identity rotation, trust changes
- Wired into ShadeSessionManager (zero overhead when not enabled)
- shortHash helper for safe display of public keys
- Security test: regex-checks event payloads contain no key material

M-Obs 2: Prekey server event hooks
- PrekeyServerEvents emitter mirroring core's pattern
- 5 server event types: registered, fetched, replenished, deleted, rate_limited
- Wired into all routes including the rate-limit error handler
- shortHash helper using crypto.subtle directly (no provider dep)

M-Obs 3: @shade/observer package
- StateAggregator subscribes to client + server events, builds rolling snapshot
- Hono routes: GET /api/state (snapshot), GET /api/events (SSE stream)
- Bearer token auth via SHADE_OBSERVER_TOKEN, query string for SSE
- Refuses to start with token < 16 chars (ConfigurationError)
- Static file serving for bundled dashboard at /dashboard/
- Placeholder dashboard renders when no built SPA present

220 tests passing, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 18:49:51 +02:00
parent 75008b623a
commit b014f9b44c
17 changed files with 1364 additions and 5 deletions

View File

@@ -3,6 +3,7 @@ import type { CryptoProvider } from '@shade/core';
import { createPrekeyRoutes } from './routes.js';
import { MemoryPrekeyStore } from './memory-store.js';
import type { PrekeyStore } from './store.js';
import type { PrekeyServerEvents } from './events.js';
export { createPrekeyRoutes } from './routes.js';
export { MemoryPrekeyStore } from './memory-store.js';
@@ -27,10 +28,16 @@ export function createPrekeyServer(options: {
crypto: CryptoProvider;
store?: PrekeyStore;
disableRateLimit?: boolean;
events?: PrekeyServerEvents;
}): Hono {
const store = options.store ?? new MemoryPrekeyStore();
return createPrekeyRoutes(store, options.crypto, { disableRateLimit: options.disableRateLimit });
return createPrekeyRoutes(store, options.crypto, {
disableRateLimit: options.disableRateLimit,
events: options.events,
});
}
export { RateLimiter, MemoryRateLimitStore } from './rate-limit.js';
export type { RateLimitStore, RateLimitConfig } from './rate-limit.js';
export { PrekeyServerEvents, shortHash as serverShortHash } from './events.js';
export type { PrekeyServerEvent, PrekeyServerEventName, PrekeyServerEventMap, PrekeyServerEventListener } from './events.js';