Files
suaveplan/openspec/changes/enterprise-message-bridge/proposal.md
2026-05-27 23:29:53 +01:00

5.9 KiB

OpenSpec Change: enterprise-message-bridge

  • Change ID: enterprise-message-bridge
  • Package: @suaveplan/message-bridge
  • Category: universal
  • Target path: universal/social/message-bridge
  • Status: IN PROGRESS
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/message-bridge.md

Summary

Deliver @suaveplan/message-bridge on the genesis branch from scratch at enterprise quality. The package provides a unified multi-platform message bridge: a MessageBridge facade that normalizes inbound and outbound messages across registered MessageAdapter implementations (Slack, Discord, Telegram, iMessage, SMS), a canonical UnifiedMessage model, platform-agnostic routing rules, deduplication of inbound messages by message ID, delivery confirmation tracking, and an event bus for inbound message propagation. This package defines the MessageAdapter interface that all platform adapters implement. Genesis adds structured errors, Zod schemas, OTel instrumentation, AbortSignal cancellation, and property-based tests.


Current-state audit (genesis)

The package does not exist on genesis yet — this is a fresh build.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) MessageBridgeError; MSG_BRIDGE.* error codes
@suaveplan/types (workspace:^) LoggerLike, Clock, EventBusLike
@suaveplan/events (workspace:^) Inbound message event propagation
@suaveplan/retry (workspace:^) Retry on send failures
zod (catalog:forms) Schema validation
@opentelemetry/api (catalog:observability) Spans + counters

Dev deps: vitest, @vitest/coverage-istanbul, fast-check (all catalog:testing).


Public API surface

UnifiedMessage

interface UnifiedMessage {
  id: string
  platform: string          // 'slack' | 'discord' | 'telegram' | 'imessage' | 'sms' | custom
  channelId?: string
  threadId?: string
  senderId: string
  senderName?: string
  text: string
  attachments: MessageAttachment[]
  timestamp: Date
  raw?: unknown             // original platform payload
  metadata: Record<string, unknown>
}

interface MessageAttachment {
  id: string
  type: 'image' | 'video' | 'audio' | 'file'
  url?: string
  content?: Uint8Array
  mimeType?: string
  filename?: string
  size?: number
}

interface SendOptions {
  platform: string
  to: string               // channel ID, chat ID, or phone number depending on platform
  threadId?: string
  attachments?: MessageAttachment[]
  signal?: AbortSignal
}

MessageAdapter interface

interface MessageAdapter {
  readonly platform: string
  start(signal?: AbortSignal): AsyncGenerator<UnifiedMessage>
  send(to: string, text: string, options?: AdapterSendOptions): Promise<void>
  isAvailable(): boolean
}

MessageBridge

class MessageBridge {
  constructor(options?: MessageBridgeOptions)
  register(adapter: MessageAdapter): void
  unregister(platform: string): void
  send(options: SendOptions, text: string): Promise<void>
  start(signal?: AbortSignal): void   // starts all registered adapters
  stop(): void
  on(event: 'message', handler: (msg: UnifiedMessage) => void): () => void
  platforms(): string[]
}

interface MessageBridgeOptions {
  bus?: EventBusLike
  logger?: LoggerLike
  clock?: Clock
  deduplicationWindowMs?: number   // default 60_000; 0 disables
}

Routing

interface RouteRule {
  fromPlatform?: string
  toPlatform: string
  transform?: (msg: UnifiedMessage) => Partial<UnifiedMessage>
}

function applyRoutes(bridge: MessageBridge, rules: RouteRule[]): () => void

Error codes

type MessageBridgeErrorCode =
  | 'MSG_BRIDGE_ADAPTER_NOT_FOUND'
  | 'MSG_BRIDGE_SEND_FAILED'
  | 'MSG_BRIDGE_ADAPTER_UNAVAILABLE'
  | 'MSG_BRIDGE_DEDUP_CONFLICT'
  | 'MSG_BRIDGE_ROUTE_FAILED'
  | 'MSG_BRIDGE_ABORTED'

Genesis additions

  1. MessageBridgeError extends SuaveplanError with six stable codes.
  2. Zod schemas for UnifiedMessage, MessageAttachment, SendOptions, RouteRule, MessageBridgeOptions.
  3. Message deduplication — a sliding-window set keyed by ${platform}:${id} prevents duplicate inbound delivery when multiple adapters or polling overlaps produce the same message.
  4. Routing rules — applyRoutes wires cross-platform message forwarding (e.g. Slack → Telegram bridge) with optional transform.
  5. OTel instrumentation — counter message_bridge.inbound_total with attribute message_bridge.platform; counter message_bridge.outbound_total; histogram message_bridge.send_duration_ms.
  6. Property-based tests — deduplication window correctly excludes duplicates and expires old entries for arbitrary sequences of message IDs.

File layout

universal/social/message-bridge/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/
      index.ts
    types/
      common.ts
      common.md
    core/
      message-bridge/
        message-bridge.ts
        message-bridge.md
        message-bridge.types.ts
        message-bridge.test.ts
        index.ts
      deduplicator/
        deduplicator.ts
        deduplicator.md
        deduplicator.test.ts
        index.ts
    routing/
      route-rules/
        route-rules.ts
        route-rules.md
        route-rules.test.ts
        index.ts
    __benchmarks__/
      message-bridge.bench.ts
  tests/
    integration.test.ts
    property.test.ts

Impact

Downstream consumers: @suaveplan/imessage-adapter, @suaveplan/slack-client (adapter registration), @suaveplan/discord-client, @suaveplan/telegram-client, @suaveplan/sms-providers.

Note: This package must be built before the adapter packages since it defines the MessageAdapter interface they implement.

Workspace dep prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/retry.