Initial Commit
This commit is contained in:
79
openspec/archive/2026-05-18-add-slack-client/proposal.md
Normal file
79
openspec/archive/2026-05-18-add-slack-client/proposal.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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<SlackErrorCode>` (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.
|
||||
Reference in New Issue
Block a user