4.4 KiB
4.4 KiB
Tasks: enterprise-server-sms
Change ID: enterprise-server-sms
Package: @suaveplan/sms
Target path: server/services/sms
Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/retry, @suaveplan/suppression, @suaveplan/idempotency, @suaveplan/templates, @suaveplan/logger
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create directory tree per
proposal.md. package.jsonat0.1.0,"sideEffects": false, exports"."+service+providers/in-memory.- Deps: all workspace + catalog deps as listed.
- DevDeps:
vitest catalog:testing,@vitest/coverage-istanbul catalog:testing,@suaveplan/testing workspace:^. tsconfig.jsonextendinglibrary-node.json;vitest.config.tswithcreateTestConfig().- Scripts:
lint,typecheck,test,test:coverage,build,docs:check.
2. Types and errors
- Define all interfaces and Zod schemas (
SmsMessageSchema,SmsServiceOptionsSchema) insrc/types/sms.types.ts. - Write
sms.types.md(≥200 words, both examples, API reference). src/error-codes.ts: five codes;registerSmsErrorCodes(); eager call.
3. InMemory provider
- Implement
InMemoryProviderinsrc/providers/in-memory-provider/in-memory-provider.ts:send(message): validate E.164 format; store in internalMap<messageId, SmsRecord>; returnSmsSendResultwithstatus: 'sent'.getStatus(messageId): return from internal map orundefined.setStatus(messageId, status): test helper to simulate delivery updates.getSentMessages(): return all recorded messages (testing helper).
- Write
in-memory-provider.test.ts: send + getStatus round-trip; setStatus update; getSentMessages. - Write
in-memory-provider.md(≥200 words).
4. SMS service
- Implement
SmsServiceinsrc/service/sms-service/sms-service.ts:send(message):- Validate
message.toas E.164 (regex); throwSMS_INVALID_NUMBERif invalid. - Check suppression via
suppressionStore?.check(to); throwSMS_SUPPRESSEDif suppressed. - Idempotency: use
message.idempotencyKeywith@suaveplan/idempotencyguard if key present. - Call
provider.send(message)with@suaveplan/retry; throwSMS_SEND_FAILEDon exhaustion. - Log result; OTel span
sms.send; countersms.sends_totalwithstatus.
- Validate
sendBatch(messages): parallel sends withPromise.allSettled; collect results.getStatus(messageId): delegate to provider.handleInbound(payload): parse viaInboundSmsSchemaZod schema; throwSMS_INBOUND_PARSE_FAILEDon invalid.handleStatusCallback(payload): parse viaSmsDeliveryStatusSchema.
- Write
sms-service.types.ts. - Write
sms-service.test.ts(≤500 lines): successful send; suppressed number skips; invalid number throws; retry on provider failure; batch; inbound parse; status callback parse; idempotency dedup. - Write
sms-service.md(≥200 words, simple + advanced, API reference).
5. Integration tests
tests/integration.test.ts:SmsService+InMemoryProvider+ real suppression store (from@suaveplan/suppression); send to suppressed number; send batch; verify delivery status updates;{ timeout: 10000 }.
6. Docs + verify + publish
- Root
README.md: quick-start with InMemoryProvider for tests, provider implementation guide, API table. bunx turbo lint --filter=@suaveplan/sms— zero errors.bunx turbo typecheck --filter=@suaveplan/sms— zero errors.bunx turbo test --filter=@suaveplan/sms— all pass.bunx turbo test:coverage --filter=@suaveplan/sms— 100%.bun scripts/publish.ts --package @suaveplan/sms.- Update
CLAUDE.md; archive toopenspec/archive/2026-04-24-enterprise-server-sms/.