# OpenSpec Change: enterprise-discord-client - **Change ID**: enterprise-discord-client - **Package**: `@suaveplan/discord-client` - **Category**: universal - **Target path**: `universal/social/discord-client` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/discord-client.md` --- ## Summary Deliver `@suaveplan/discord-client` on the genesis branch from scratch at enterprise quality. The package provides a generic Discord API client: Webhook-based message delivery (text, embeds, files), Bot REST API wrappers for channel messaging, guild info, member lookups, message reactions, thread management, Slash-command interaction responses, Component interaction acknowledgement, and a `DiscordClient` facade. Injectable fetch; no `discord.js` dependency. Genesis adds structured errors, Zod schemas, OTel instrumentation, retry with circuit breaker, rate-limit awareness (per-route bucket tracking), and `AbortSignal` cancellation. --- ## 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:^`) | `DiscordError`; `DISCORD.*` error codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` | | `@suaveplan/retry` (`workspace:^`) | Retry + circuit breaker for API calls | | `@suaveplan/crypto` (`workspace:^`) | Ed25519 interaction signature verification | | `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 ### `DiscordClient` ```ts class DiscordClient { constructor(options: DiscordClientOptions) // Webhooks sendWebhook(webhookUrl: string, payload: WebhookPayload): Promise executeWebhook(options: ExecuteWebhookOptions): Promise // Messages sendMessage(channelId: string, payload: MessagePayload): Promise editMessage(channelId: string, messageId: string, payload: MessagePayload): Promise deleteMessage(channelId: string, messageId: string): Promise addReaction(channelId: string, messageId: string, emoji: string): Promise // Channels / Threads getChannel(channelId: string): Promise createThread(channelId: string, messageId: string, options: ThreadOptions): Promise // Guild / Members getGuild(guildId: string): Promise getMember(guildId: string, userId: string): Promise } interface DiscordClientOptions { token: string applicationId?: string publicKey?: string // Ed25519 public key for interaction verification retryOptions?: RetryOptions fetch?: typeof globalThis.fetch clock?: Clock logger?: LoggerLike } ``` ### Embed builder ```ts function createEmbed(): EmbedBuilder interface EmbedBuilder { title(text: string): EmbedBuilder description(text: string): EmbedBuilder color(hex: number): EmbedBuilder field(name: string, value: string, inline?: boolean): EmbedBuilder footer(text: string, iconUrl?: string): EmbedBuilder thumbnail(url: string): EmbedBuilder image(url: string): EmbedBuilder timestamp(date?: Date): EmbedBuilder build(): DiscordEmbed } ``` ### Interaction handling ```ts function verifyDiscordInteraction( body: string, signature: string, timestamp: string, publicKey: string ): Promise function parseInteraction(body: unknown): DiscordInteraction ``` ### Error codes ```ts type DiscordErrorCode = | 'DISCORD_AUTH_FAILED' | 'DISCORD_API_ERROR' | 'DISCORD_RATE_LIMITED' | 'DISCORD_SIGNATURE_INVALID' | 'DISCORD_CHANNEL_NOT_FOUND' | 'DISCORD_MESSAGE_NOT_FOUND' | 'DISCORD_ABORTED' ``` --- ## Genesis additions 1. **`DiscordError`** extends `SuaveplanError` with seven stable codes. 2. **Zod schemas** for `MessagePayload`, `WebhookPayload`, `DiscordEmbed`, `DiscordMessage`, `DiscordInteraction`, `DiscordChannel`, `DiscordGuild`, `DiscordMember`. 3. **Per-route rate-limit bucket tracking** — parses `X-RateLimit-*` response headers and enforces bucket-level backoff before the next request on the same route. 4. **Ed25519 interaction verification** — delegates to `@suaveplan/crypto`'s `ed25519.verify` for interaction signature verification. 5. **OTel instrumentation** — span `discord.api_call` with attribute `discord.endpoint`; counter `discord.api_total`; histogram `discord.api_duration_ms`. 6. **Property-based tests** — Embed builder produces valid JSON for arbitrary chained calls; rate-limit bucket parser correctly handles all header combinations. --- ## File layout ```text universal/social/discord-client/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ common.ts common.md core/ discord-client/ discord-client.ts discord-client.md discord-client.types.ts discord-client.test.ts index.ts api-caller/ api-caller.ts api-caller.md api-caller.test.ts index.ts rate-limiter/ rate-limiter.ts rate-limiter.md rate-limiter.test.ts index.ts embeds/ embed-builder/ embed-builder.ts embed-builder.md embed-builder.test.ts index.ts interactions/ interaction-verifier/ interaction-verifier.ts interaction-verifier.md interaction-verifier.test.ts index.ts interaction-parser/ interaction-parser.ts interaction-parser.md interaction-parser.test.ts index.ts __benchmarks__/ discord-client.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream consumers**: `@suaveplan/message-bridge`, `@suaveplan/notification-channel`. **Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/retry`, `@suaveplan/crypto`.