Initial Commit
This commit is contained in:
103
openspec/archive/2026-05-25-enterprise-sms/proposal.md
Normal file
103
openspec/archive/2026-05-25-enterprise-sms/proposal.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Enterprise migration: `@suaveplan/sms`
|
||||
|
||||
- **Change ID**: `enterprise-sms`
|
||||
- **Status**: PROPOSED
|
||||
- **Capability**: `sms`
|
||||
- **Target package**: `@suaveplan/sms` at `0.1.0`
|
||||
- **Target path**: `packages/communication/messaging/sms/`
|
||||
- **Design**: ./design.md
|
||||
- **Spec delta**: ./specs/sms/spec.md
|
||||
- **Source on main**: `/mnt/s/web/suaveplan/packages/server/sms` (`~1389 LOC`)
|
||||
- **Enterprise plan**: `/mnt/s/web/suaveplan/enterprise/sms.md`
|
||||
|
||||
## Why
|
||||
|
||||
`@suaveplan/sms` is the universal SMS core: a provider-agnostic
|
||||
sending pipeline with template rendering, opt-out management, rate
|
||||
limiting, scheduling, retry, and delivery tracking. Providers
|
||||
(Twilio, Vonage, MessageBird, AWS SNS, in-house gateways) plug into
|
||||
the abstract `SmsProvider` contract.
|
||||
|
||||
The upstream package (`v1.0.2`) is functionally narrow — provider
|
||||
seam, template render, opt-out list, four events — but is missing
|
||||
every enterprise lever the genesis tree commits to:
|
||||
|
||||
- `BaseError` legacy hierarchy instead of `defineCodes("SMS", [...])`.
|
||||
- No Zod boundary on `SmsMessage`, `SmsTemplate`, `SmsProvider` inputs.
|
||||
- No OpenTelemetry counters or histograms.
|
||||
- No retry/backoff on transient provider failures.
|
||||
- No scheduling (`sendAt`) or batched dispatch.
|
||||
- No rate limiting per provider / per recipient.
|
||||
- No delivery-receipt ingest pipeline; status is fetched on demand.
|
||||
- No `AbortSignal` propagation on send / scheduler / status pulls.
|
||||
- No in-memory test provider; tests use ad-hoc anonymous mocks.
|
||||
- One global event-emitter shape; no typed `EventBus` bridge.
|
||||
- Tests pass with vi-mocks (`@suaveplan/testing` `vi.fn()`) — fine for
|
||||
unit harnesses, but the package never exercises a real provider
|
||||
round-trip end-to-end.
|
||||
|
||||
This change rewrites the package on genesis at enterprise quality
|
||||
while preserving every upstream export, then layers the SMS-core
|
||||
features (`MessageScheduler`, `RateLimiter`, `DeliveryTracker`,
|
||||
`RetryPolicy`, OTel instruments, Zod boundaries, abort signals,
|
||||
`EventBus` bridge, in-memory test provider).
|
||||
|
||||
## Parity Checklist
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| `SmsService` send / template render / opt-out | ✅ | ✅ | Preserved verbatim, plus rate-limit + abort + retry on top. |
|
||||
| `SmsProvider` abstract contract | ✅ | ✅ | Preserved (`name`, `send`, `getStatus`). |
|
||||
| `SmsTemplate` (`{{var}}` substitution) | ✅ | ✅ | Preserved + `defaults` map + escape pass. |
|
||||
| `SmsMessage` / `SmsDeliveryReport` / `SmsDeliveryStatus` | ✅ | ✅ | Preserved + Zod schemas. |
|
||||
| `OptOutEntry` registry | ✅ | ✅ | Preserved + bulk import / export. |
|
||||
| `SmsEventMap` (`sms.sent`/`delivered`/`failed`/`opted_out`/`opted_in`) | ✅ | ✅ | Preserved + new `sms.scheduled` / `sms.retry` / `sms.rate_limited`. |
|
||||
| Legacy `EventEmitter` surface (`on/off/emit`) | ✅ | ✅ | Preserved (delegates to typed `EventBus`). |
|
||||
| `SmsError` legacy class + factory methods | ✅ | ✅ | Preserved as back-compat alias over genesis `defineCodes` family. |
|
||||
| `respondProblemJson` Hono helper | ✅ | ✅ | Preserved. |
|
||||
| `registerSmsErrorCodes` / `SMS_ERROR_METADATA` | ✅ | ✅ | Preserved via genesis `ErrorCodes.registerMany`. |
|
||||
| Stable `SMS.*` `defineCodes` registry | 🚫 (legacy `BaseError`) | ⬆️ | Genesis addition. |
|
||||
| Zod boundary on every public input | 🚫 | ⬆️ | Genesis addition. |
|
||||
| OTel counters + histogram | 🚫 | ⬆️ | Genesis addition (4 instruments). |
|
||||
| `MessageScheduler` (sendAt + cancel) | 🚫 | ⬆️ | Genesis addition. |
|
||||
| `RateLimiter` (token bucket per recipient) | 🚫 | ⬆️ | Genesis addition. |
|
||||
| `DeliveryTracker` (receipt ingest + cache) | 🚫 | ⬆️ | Genesis addition. |
|
||||
| `RetryPolicy` (exponential backoff w/ jitter) | 🚫 | ⬆️ | Genesis addition. |
|
||||
| `InMemorySmsProvider` (test provider) | 🚫 | ⬆️ | Genesis addition; replaces ad-hoc test mocks. |
|
||||
| `AbortSignal` propagation | 🚫 | ⬆️ | Genesis addition. |
|
||||
| 100/100/100/100 coverage | 🚫 | ⬆️ | Genesis gate. |
|
||||
|
||||
## What
|
||||
|
||||
A net-new package at `packages/communication/messaging/sms/` at
|
||||
`0.1.0`, universal tier (no Node/Bun-only APIs), Zod-validated
|
||||
boundaries, OTel-instrumented, with the full upstream public
|
||||
surface plus the genesis enterprise additions.
|
||||
|
||||
This is the SMS **core** — not the providers package. The abstract
|
||||
`SmsProvider` interface ships with an in-memory test provider
|
||||
(`InMemorySmsProvider`) so consumers and integration tests can run
|
||||
without external services. Concrete provider integrations
|
||||
(Twilio, Vonage, AWS SNS) ship in the sibling `@suaveplan/sms-providers`
|
||||
package next wave.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/sms` to enumerate.
|
||||
|
||||
No genesis package currently imports `@suaveplan/sms`:
|
||||
|
||||
- `@suaveplan/sms-providers` — pending migration, will consume this as `workspace:^0.1.0`. [x] no action needed (reason: package not yet on genesis)
|
||||
- `@suaveplan/api-plugin-messaging` — pending migration. [x] no action needed (reason: package not yet on genesis)
|
||||
|
||||
Foundation deps (`@suaveplan/error`, `@suaveplan/events`,
|
||||
`@suaveplan/types`) are read-only consumers; no change required.
|
||||
|
||||
## Out of scope
|
||||
|
||||
Nothing. Per `feedback_no_deferrals_on_migration.md` and AGENTS §2b,
|
||||
no "phase 2" is permitted. The whole upstream surface plus the
|
||||
enterprise additions land here, feature-complete, with
|
||||
100/100/100/100 coverage.
|
||||
Reference in New Issue
Block a user