# Add @suaveplan/slack-client **Package**: `@suaveplan/slack-client` **Status**: PROPOSED **Type**: new-package ## Why Genesis needs a typed, deterministic, OpenTelemetry-instrumented Slack client for transactional messaging (alerting, notifications, ops bots). The upstream `@suaveplan/slack-client@1.0.1` at `../suaveplan/packages/universal/slack-client/` works but does not meet genesis enterprise-quality bar — it monkey-patches `globalThis.fetch` in tests (`vi.stubGlobal`), has no Zod boundary validation, no OTel meters, no injectable `Clock`/`Rng`/`Logger`, leaks `as unknown` casts, and registers codes through the legacy `ErrorCodes.registerMany` runtime registry instead of `defineCodes`. This change ships a fresh build at enterprise quality with full functional parity. ## What changes - ADDED: `@suaveplan/slack-client@0.1.0` under `packages/messaging/slack-client/` - ADDED: `SlackApiClient` — Slack Web API: `sendMessage`, `updateMessage`, `deleteMessage`, `listChannels`, `getChannelInfo`, `listUsers`, `getStatus` - ADDED: `SlackWebhook` — Slack incoming-webhook client: `send`, `sendText` - ADDED: `SLACK_CODES` registry (`defineCodes("SLACK", [...])`) with 8 reasons: `API_ERROR`, `AUTH_ERROR`, `RATE_LIMITED`, `CHANNEL_NOT_FOUND`, `WORKSPACE_NOT_FOUND`, `CONFIG_MISSING`, `WEBHOOK_FAILED`, `TIMEOUT` - ADDED: 8 typed error subclasses extending `SuaveplanError` (one per reason) - ADDED: Zod schemas at every public boundary: `SlackApiClientOptionsSchema`, `SlackWebhookOptionsSchema`, `SlackMessageSchema`, `SlackBlockSchema`, `SlackBlockElementSchema`, `SlackTextObjectSchema` - ADDED: OTel meter instruments — `slack.requests`, `slack.errors`, `slack.latency_ms`, `slack.webhook_sends` — emitted from every production hot path - ADDED: Injectable transport bag — `Clock`, `Rng`, `FetchLike`, `LoggerLike` — all from `@suaveplan/types` - ADDED: Deterministic `FakeFetch` test double under `src/fakes/` for parity tests (no `vi.mock` anywhere) - ADDED: Retry-After parser supporting delay-seconds, HTTP-date, and `x-ratelimit-reset` epoch/ISO - ADDED: Slack-error string classifier (`invalid_auth` / `channel_not_found` / `team_not_found` families) ## Out of scope None. The package ships at full functional parity with the upstream package plus the enterprise upgrades demanded by `enterprise/slack-client.md` §3. ## Source mapping This change derives from `../suaveplan/enterprise/slack-client.md` and the working implementation at `../suaveplan/packages/universal/slack-client/src/`. | Requirement | Source lines | Notes | |---|---|---| | Generic Slack API client + webhook surface | enterprise/slack-client.md L10-L13 | verbatim | | Zero browser/Node/React deps (universal tier) | enterprise/slack-client.md L37 | verbatim | | Zod schemas at every boundary | enterprise/slack-client.md L38 | verbatim | | Deterministic — no module-scope `Math.random` / `Date.now`, injectable clock/RNG | enterprise/slack-client.md L40 | verbatim | | OpenTelemetry instrumentation via `@opentelemetry/api` | enterprise/slack-client.md L41 | verbatim | | Structured errors extending `@suaveplan/error` with stable codes | enterprise/slack-client.md L42, L61 | verbatim | | AbortSignal cancellation everywhere async | enterprise/slack-client.md L43 | verbatim | | Idempotency + retry primitives wired | enterprise/slack-client.md L44 | applied to Retry-After / rate-limit; cooperative retry left to caller's `@suaveplan/retry` (no double-wrap) | | i18n-ready (no hard-coded user-visible strings) | enterprise/slack-client.md L46 | every thrown error carries a stable code + structured context; messages are templated | | Test coverage 100% statement/branch/function/line (istanbul) | enterprise/slack-client.md L50 + genesis CLAUDE.md "Quality" | genesis policy is 100%, stricter than the 95% in the source | | File-size budget 250/300 soft/hard, 500 test | enterprise/slack-client.md L52 + CLAUDE.md | enforced by manual split | | No mocks — injectable fakes only | enterprise/slack-client.md L53 + CLAUDE.md rule 2 | verbatim | | `sideEffects: false` | enterprise/slack-client.md L55 | verbatim | | Biome clean, zero suppressions | enterprise/slack-client.md L58 + CLAUDE.md rule 9 | verbatim | | TypeScript strict, no `any` / `ts-ignore` | enterprise/slack-client.md L59 | verbatim | | Error hierarchy with typed discriminants | enterprise/slack-client.md L61 | verbatim | | `chat.postMessage`, `chat.update`, `chat.delete`, `conversations.list`, `conversations.info`, `users.list` | upstream packages/universal/slack-client/src/core/slack-api-client/slack-api-client.ts L52-L138 | parity | | Webhook POST with text + blocks | upstream packages/universal/slack-client/src/core/slack-webhook/slack-webhook.ts L41-L105 | parity | | HTTP 429 Retry-After / x-ratelimit-reset parsing | upstream slack-api-client.ts L259-L284 | parity | | Slack-error → code classifier (`invalid_auth`, `channel_not_found`, `team_not_found`) | upstream slack-api-client.ts L286-L313 | parity | ### Requirements with no source line | Requirement | Justification | |---|---| | 100% coverage threshold (vs. 95% in source) | Genesis-wide policy — `CLAUDE.md` Quality section | | Test runner imports MUST go through `@suaveplan/testing/runner` | Genesis rule 11 — not applicable to upstream which predates the rule | | `defineCodes("SLACK", […])` registry style | Genesis-wide error-codes convention — upstream used the legacy `ErrorCodes.registerMany` API | | `FakeFetch` test double + setup polyfills wiring real `MeterProvider` | Genesis pre-archive checklist row 5 (OTel asserted with real `MeterProvider`, not `vi.mock`) | ## Verification approach - `src/client/slack-api-client.test.ts` — pins every `SlackApiClient` method scenario with `FakeFetch` - `src/webhook/slack-webhook.test.ts` — pins every `SlackWebhook` scenario with `FakeFetch` - `src/errors/errors.test.ts` — pins error-code identity and message keys for all 8 codes - `src/schemas/schemas.test.ts` — Zod boundary parse + safeParse assertions for every public schema - `src/client/retry-after.test.ts` — pins every Retry-After header form (delay-seconds, HTTP-date, x-ratelimit-reset epoch + ISO, missing) - `src/client/classify.test.ts` — pins HTTP-status and slack-error-string classifiers - `src/otel/metrics.test.ts` — asserts the four OTel instruments emit on every production path, via real in-memory `MeterProvider` - `src/fakes/fake-fetch.test.ts` — pins the deterministic test double itself - `src/index.test.ts` — pins the public-surface barrel ## Migration impact None — additive. No existing consumer of `@suaveplan/slack-client` exists in genesis.