@terreno/mcp 56.0.0-beta.0 → 56.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bootstrap.js +7 -7
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/templates/bootstrap/backend/types/models/userTypes.template.md +5 -4
- package/dist/docs/templates/tools/model.template.md +4 -2
- package/dist/docs/upgrades/0.31.0.md +60 -0
- package/dist/docs/upgrades/56.0.0-beta.2.md +59 -0
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +7 -1
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +65 -0
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +78 -0
- package/dist/docs/versioned/next/explanation/positioning.md +164 -0
- package/dist/docs/versioned/next/explanation/repository-settings.md +44 -0
- package/dist/docs/versioned/next/explanation/roadmap-process.md +330 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +887 -0
- package/dist/docs/versioned/next/how-to/README.md +8 -0
- package/dist/docs/versioned/next/how-to/build-for-web.md +69 -0
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +115 -0
- package/dist/docs/versioned/next/how-to/deploy-to-gcp.md +20 -228
- package/dist/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +183 -0
- package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +31 -0
- package/dist/docs/versioned/next/implementationPlans/README.md +56 -30
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +2 -1
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +115 -0
- package/dist/docs/versioned/next/implementationPlans/billing-stripe.md +158 -0
- package/dist/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +323 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +107 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +157 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +155 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +99 -0
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +180 -0
- package/dist/docs/versioned/next/implementationPlans/deploy-to-gcp.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/deploy-to-vercel.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/deployment-foundation.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-reference-coverage.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +188 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +269 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +126 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +269 -0
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +105 -0
- package/dist/docs/versioned/next/implementationPlans/orgs-and-teams.md +158 -0
- package/dist/docs/versioned/next/implementationPlans/oss-governance-baseline.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +147 -0
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +1 -0
- package/dist/docs/versioned/next/reference/README.md +5 -0
- package/dist/docs/versioned/next/reference/admin-spa.md +130 -0
- package/dist/docs/versioned/next/reference/ai.md +519 -0
- package/dist/docs/versioned/next/reference/comms.md +132 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +229 -382
- package/dist/docs/versioned/next/reference/test.md +108 -0
- package/dist/docs/versioned/next/roadmap.md +9 -0
- package/dist/docs/versioned/next/tasks/README.md +42 -0
- package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/dist/docs/versioned/next/tasks/billing-stripe.md +75 -0
- package/dist/docs/versioned/next/tasks/comms-abstraction.md +90 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-expo-push.md +29 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +39 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +39 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +19 -0
- package/dist/docs/versioned/next/tasks/comms-admin-dashboard.md +52 -0
- package/dist/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/dist/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/dist/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/dist/docs/versioned/next/tasks/infra-mcp.md +43 -0
- package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +215 -0
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +42 -0
- package/dist/docs/versioned/next/tasks/orgs-and-teams.md +70 -0
- package/dist/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/dist/docs/versioned/next/tasks/password-reset-and-email-verification.md +60 -0
- package/dist/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/dist/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/package.json +4 -3
- package/src/docs/templates/bootstrap/backend/types/models/userTypes.template.md +5 -4
- package/src/docs/templates/tools/model.template.md +4 -2
- package/src/docs/upgrades/0.31.0.md +60 -0
- package/src/docs/upgrades/56.0.0-beta.2.md +59 -0
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +7 -1
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +65 -0
- package/src/docs/versioned/next/explanation/deployment-baseline.md +78 -0
- package/src/docs/versioned/next/explanation/positioning.md +164 -0
- package/src/docs/versioned/next/explanation/repository-settings.md +44 -0
- package/src/docs/versioned/next/explanation/roadmap-process.md +330 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +887 -0
- package/src/docs/versioned/next/how-to/README.md +8 -0
- package/src/docs/versioned/next/how-to/build-for-web.md +69 -0
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +115 -0
- package/src/docs/versioned/next/how-to/deploy-to-gcp.md +20 -228
- package/src/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +183 -0
- package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +31 -0
- package/src/docs/versioned/next/implementationPlans/README.md +56 -30
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +2 -1
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +1 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +115 -0
- package/src/docs/versioned/next/implementationPlans/billing-stripe.md +158 -0
- package/src/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +1 -0
- package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +323 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +107 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +157 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +155 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +99 -0
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +180 -0
- package/src/docs/versioned/next/implementationPlans/deploy-to-gcp.md +1 -0
- package/src/docs/versioned/next/implementationPlans/deploy-to-vercel.md +1 -0
- package/src/docs/versioned/next/implementationPlans/deployment-foundation.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-reference-coverage.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +1 -0
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +1 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +188 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp.md +269 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +126 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +269 -0
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +105 -0
- package/src/docs/versioned/next/implementationPlans/orgs-and-teams.md +158 -0
- package/src/docs/versioned/next/implementationPlans/oss-governance-baseline.md +1 -0
- package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +147 -0
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -0
- package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +1 -0
- package/src/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +1 -0
- package/src/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +1 -0
- package/src/docs/versioned/next/reference/README.md +5 -0
- package/src/docs/versioned/next/reference/admin-spa.md +130 -0
- package/src/docs/versioned/next/reference/ai.md +519 -0
- package/src/docs/versioned/next/reference/comms.md +132 -0
- package/src/docs/versioned/next/reference/environment-variables.md +229 -382
- package/src/docs/versioned/next/reference/test.md +108 -0
- package/src/docs/versioned/next/roadmap.md +9 -0
- package/src/docs/versioned/next/tasks/README.md +42 -0
- package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/src/docs/versioned/next/tasks/billing-stripe.md +75 -0
- package/src/docs/versioned/next/tasks/comms-abstraction.md +90 -0
- package/src/docs/versioned/next/tasks/comms-adapter-expo-push.md +29 -0
- package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +39 -0
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +39 -0
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +19 -0
- package/src/docs/versioned/next/tasks/comms-admin-dashboard.md +52 -0
- package/src/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/src/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/src/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/src/docs/versioned/next/tasks/infra-mcp.md +43 -0
- package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +215 -0
- package/src/docs/versioned/next/tasks/native-module-baseline.md +42 -0
- package/src/docs/versioned/next/tasks/orgs-and-teams.md +70 -0
- package/src/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/src/docs/versioned/next/tasks/password-reset-and-email-verification.md +60 -0
- package/src/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/src/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Implementation Plan: Comms adapter — Twilio SMS
|
|
2
|
+
|
|
3
|
+
**Status:** Draft
|
|
4
|
+
**Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1020
|
|
5
|
+
**Priority:** Medium
|
|
6
|
+
**Effort:** Small batch
|
|
7
|
+
**Owner:** unassigned
|
|
8
|
+
**Created:** 2026-08-09
|
|
9
|
+
**Program:** [B2B platform](b2b-platform-program.md)
|
|
10
|
+
**Depends on:** [comms-abstraction](comms-abstraction.md); inbound-webhooks (Phase 2 only)
|
|
11
|
+
**RTK deprecation flag:** None — backend only
|
|
12
|
+
|
|
13
|
+
## Goal
|
|
14
|
+
|
|
15
|
+
Implement `SmsProvider` on Twilio Programmable Messaging so Terreno apps can send
|
|
16
|
+
transactional SMS (OTP fallback, alerts, invitation nudges) through the `@terreno/comms`
|
|
17
|
+
facade with delivery logging.
|
|
18
|
+
|
|
19
|
+
## Non-Goals
|
|
20
|
+
|
|
21
|
+
- OTP verification (that is `comms-adapter-twilio-verify`, on Twilio Verify).
|
|
22
|
+
- Push via Twilio (dropped by decision D5 — Expo push is the push adapter).
|
|
23
|
+
- Bulk/campaign messaging, MMS/RCS.
|
|
24
|
+
- Phone-number provisioning automation.
|
|
25
|
+
|
|
26
|
+
## Decisions
|
|
27
|
+
|
|
28
|
+
| Question | Decision |
|
|
29
|
+
|----------|----------|
|
|
30
|
+
| SDK | `twilio` as optional peer, subpath `@terreno/comms/adapters/twilioSms` |
|
|
31
|
+
| Sender config | Prefer `TWILIO_MESSAGING_SERVICE_SID`; fall back to `TWILIO_FROM_NUMBER`; constructor options override env |
|
|
32
|
+
| Number validation | Validate/normalize to E.164 with `libphonenumber-js` (already in catalog) before hitting the API |
|
|
33
|
+
| Delivery status | Phase 2: status callback endpoint via the inbound-webhooks framework mapping Twilio statuses to `DeliveryEvent`s |
|
|
34
|
+
| Secrets | `TWILIO_ACCOUNT_SID` + `TWILIO_AUTH_TOKEN` via env or the existing secret-provider layer |
|
|
35
|
+
| Error classification | Adapter owns a Twilio error-code → `errorClass` map (table below) so the facade retries only transient failures and the admin dashboard gates manual retries correctly |
|
|
36
|
+
| SDK failure handling | Twilio SDK calls wrapped with `withApiErrorHandling` from `@terreno/api` (per [call-external-apis](../how-to/call-external-apis.md)) — the SDK owns transport, the wrapper standardizes logging/normalization |
|
|
37
|
+
| Opt-outs | Phase 2: inbound STOP/START (and 21610 send failures) surface as `OptOutEvent` → the consumer's `onOptOut` hook; enforcement stays consumer-side via `beforeSend` (Twilio blocks opted-out sends itself) |
|
|
38
|
+
| Admin dashboard support | Every `CommsMessage` row stores the Twilio error code/class and a `metadata.consoleUrl` deep link to the message in the Twilio console for log digging |
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// @terreno/comms/adapters/twilioSms
|
|
44
|
+
export class TwilioSmsProvider implements SmsProvider {
|
|
45
|
+
readonly id = "twilio";
|
|
46
|
+
constructor(options?: {
|
|
47
|
+
accountSid?: string; // default env TWILIO_ACCOUNT_SID
|
|
48
|
+
authToken?: string; // default env TWILIO_AUTH_TOKEN
|
|
49
|
+
messagingServiceSid?: string;
|
|
50
|
+
fromNumber?: string;
|
|
51
|
+
statusCallbackUrl?: string; // Phase 2
|
|
52
|
+
});
|
|
53
|
+
sendSms(message: {to: string; body: string}): Promise<SendResult>;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Errors map Twilio exception codes to `SendResult.error`/`errorCode`/`errorClass`, with
|
|
58
|
+
the full Twilio error payload kept in `CommsMessage.metadata` for triage. Invalid
|
|
59
|
+
destination numbers fail fast with a `BadRequestError` before any API call.
|
|
60
|
+
|
|
61
|
+
### Error classification
|
|
62
|
+
|
|
63
|
+
The adapter classifies Twilio error codes into the abstraction's `errorClass` taxonomy.
|
|
64
|
+
Permanent failures must never be auto-retried (retrying an unsubscribed number is a
|
|
65
|
+
compliance problem, not a delivery problem); transient failures are retried once inline
|
|
66
|
+
by the facade and stay manually retryable from the admin dashboard.
|
|
67
|
+
|
|
68
|
+
| Twilio code(s) | Meaning | `errorClass` |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| 21211, 21214, 21217 | Invalid / unroutable destination number | `permanent` |
|
|
71
|
+
| 21408 | SMS not enabled for destination region | `permanent` |
|
|
72
|
+
| 21610 | Recipient has opted out (STOP) | `permanent` (also emits `OptOutEvent`) |
|
|
73
|
+
| 30003, 30005, 30006 | Unreachable handset / unknown device / landline | `permanent` |
|
|
74
|
+
| 30007 | Carrier content filtering | `permanent` (surface loudly — usually a sender-reputation problem) |
|
|
75
|
+
| 20429, 429 responses | Rate limited / concurrency limit | `transient` |
|
|
76
|
+
| 30001, 30002 | Queue overflow / account suspended-ish delivery interruption | `transient` |
|
|
77
|
+
| 5xx / network errors | Twilio-side or transport failure | `transient` |
|
|
78
|
+
| 20003, 20404 (auth/SID misconfig) | Bad credentials or missing resource | `config` |
|
|
79
|
+
|
|
80
|
+
Unknown codes default to `transient` (safe for one retry) and are logged with the raw
|
|
81
|
+
code so the map can be extended. `config` failures additionally `logger.error` at send
|
|
82
|
+
time — they mean the app is misconfigured, not that the message was undeliverable.
|
|
83
|
+
|
|
84
|
+
### Status callback mapping (Phase 2)
|
|
85
|
+
|
|
86
|
+
| Twilio `MessageStatus` | `DeliveryEvent.status` | `CommsMessage.status` |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `queued`, `accepted`, `sending`, `sent` | — (no event; already `sent`) | `sent` |
|
|
89
|
+
| `delivered` | `delivered` | `delivered` |
|
|
90
|
+
| `undelivered`, `failed` | `failed` (+ `errorCode` from `ErrorCode` param) | `failed` |
|
|
91
|
+
|
|
92
|
+
Inbound `STOP`/`START` keyword webhooks (same inbound-webhooks registration) map to
|
|
93
|
+
`OptOutEvent {channel: "sms", reason: "sms-stop"}` / opt-in, feeding the consumer's
|
|
94
|
+
`onOptOut` hook and `CommsMessage.metadata` on subsequent 21610 failures.
|
|
95
|
+
|
|
96
|
+
## Models / APIs / Notifications / UI
|
|
97
|
+
|
|
98
|
+
None new — Phase 2 adds one webhook route via the inbound-webhooks framework.
|
|
99
|
+
|
|
100
|
+
## Phases
|
|
101
|
+
|
|
102
|
+
1. **Send path:** provider, config resolution, E.164 validation, error-code
|
|
103
|
+
classification map, `withApiErrorHandling` wrapping, console deep-link metadata, tests
|
|
104
|
+
with mocked Twilio client; example-backend env-gated registration; docs.
|
|
105
|
+
2. **Delivery callbacks + opt-outs** *(gated on inbound-webhooks IP)*: status callback
|
|
106
|
+
route with Twilio signature verification, statuses → `DeliveryEvent` →
|
|
107
|
+
`CommsMessage.status` updates (with `errorCode`), STOP/START keyword handling →
|
|
108
|
+
`OptOutEvent` → `onOptOut`.
|
|
109
|
+
|
|
110
|
+
## Feature Flags & Migrations
|
|
111
|
+
|
|
112
|
+
None.
|
|
113
|
+
|
|
114
|
+
## Activity Log & User Updates
|
|
115
|
+
|
|
116
|
+
All sends logged to `CommsMessage` with provider `twilio`.
|
|
117
|
+
|
|
118
|
+
## Not Included / Future Work
|
|
119
|
+
|
|
120
|
+
- Inbound SMS (replies) handling beyond STOP/START keywords.
|
|
121
|
+
- Alphanumeric sender IDs, international compliance tooling.
|
|
122
|
+
- Terreno-managed opt-out suppression list — the adapter emits `OptOutEvent`; storing and
|
|
123
|
+
enforcing preferences is consumer-side (`beforeSend`) until `notification-center` lands.
|
|
124
|
+
|
|
125
|
+
## Files to Create / Modify
|
|
126
|
+
|
|
127
|
+
- `comms/src/adapters/twilioSms.ts` + tests
|
|
128
|
+
- `comms/package.json` — optional peer `twilio`
|
|
129
|
+
- `example-backend/src/server.ts` — env-gated registration
|
|
130
|
+
- `docs/reference/comms.md`, `docs/reference/environment-variables.md`
|
|
131
|
+
|
|
132
|
+
## Task List
|
|
133
|
+
|
|
134
|
+
See [docs/tasks/comms-adapter-twilio-sms.md](../tasks/comms-adapter-twilio-sms.md).
|
|
135
|
+
|
|
136
|
+
## Acceptance Criteria
|
|
137
|
+
|
|
138
|
+
- [ ] `sendSms` delivers via messaging service or from-number config, preferring the
|
|
139
|
+
messaging service when both are present.
|
|
140
|
+
- [ ] A non-E.164 destination fails with a 400 `APIError` before any Twilio call.
|
|
141
|
+
- [ ] Twilio API errors produce `accepted: false` results with `errorCode` and a correct
|
|
142
|
+
`errorClass` per the classification table; the full Twilio error payload lands in
|
|
143
|
+
`CommsMessage.metadata`; nothing throws through the facade.
|
|
144
|
+
- [ ] Permanent-class failures (21610, 30003, …) are never retried inline and appear as
|
|
145
|
+
non-retryable in the admin dashboard; transient-class failures retry once and stay
|
|
146
|
+
manually retryable.
|
|
147
|
+
- [ ] Each send stores `metadata.consoleUrl` linking the message SID to the Twilio
|
|
148
|
+
console.
|
|
149
|
+
- [ ] Consumer `onError` fires with the classified `SendResult` on every failed send
|
|
150
|
+
(mocked-client test).
|
|
151
|
+
- [ ] Apps not using the adapter do not install the `twilio` SDK.
|
|
152
|
+
- [ ] (Phase 2) Status callbacks flip `CommsMessage.status` to `delivered`/`failed` (with
|
|
153
|
+
`errorCode`) under verified Twilio signatures.
|
|
154
|
+
- [ ] (Phase 2) An inbound STOP fires `onOptOut` with `reason: "sms-stop"`; a subsequent
|
|
155
|
+
send to that number records 21610 as `permanent`.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Implementation Plan: Comms adapter — Twilio Verify (OTP)
|
|
2
|
+
|
|
3
|
+
**Status:** Draft
|
|
4
|
+
**Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1021
|
|
5
|
+
**Priority:** Medium
|
|
6
|
+
**Effort:** Small batch
|
|
7
|
+
**Owner:** unassigned
|
|
8
|
+
**Created:** 2026-08-09
|
|
9
|
+
**Program:** [B2B platform](b2b-platform-program.md)
|
|
10
|
+
**Depends on:** [comms-abstraction](comms-abstraction.md)
|
|
11
|
+
**RTK deprecation flag:** None — backend only
|
|
12
|
+
|
|
13
|
+
## Goal
|
|
14
|
+
|
|
15
|
+
Implement `VerificationProvider` on Twilio Verify so Terreno apps get managed one-time-code
|
|
16
|
+
flows (SMS and email channels) without storing, expiring, or rate-limiting codes
|
|
17
|
+
themselves. This is the delivery channel the future `mfa-step-up-auth` work builds on, and
|
|
18
|
+
it is immediately usable for phone-number verification.
|
|
19
|
+
|
|
20
|
+
## Non-Goals
|
|
21
|
+
|
|
22
|
+
- The MFA enrollment/step-up product itself (`mfa-step-up-auth` item).
|
|
23
|
+
- TOTP (app-authenticator) support — that is code-local (`otpauth`), not a Verify concern.
|
|
24
|
+
- Custom code storage or self-managed rate limiting (Verify owns both).
|
|
25
|
+
|
|
26
|
+
## Decisions
|
|
27
|
+
|
|
28
|
+
| Question | Decision |
|
|
29
|
+
|----------|----------|
|
|
30
|
+
| SDK | `twilio` optional peer (shared with the SMS adapter), subpath `@terreno/comms/adapters/twilioVerify` |
|
|
31
|
+
| Service config | `TWILIO_VERIFY_SERVICE_SID` env or constructor option |
|
|
32
|
+
| Channels | `sms` and `email` (Verify's email channel requires a configured Verify email integration; documented, not automated) |
|
|
33
|
+
| Abuse control | Rely on Verify's built-in rate limits; add Terreno-side per-user attempt caps only when `rate-limiting` IP lands |
|
|
34
|
+
| Logging | Start/check attempts logged to `CommsMessage` with channel `verification`, never logging the code |
|
|
35
|
+
| Error classification | Verify errors map to the abstraction taxonomy: 20429/429 and 5xx → `transient`; max-attempts, expired, and invalid-destination → `permanent`; missing/invalid service SID → `config` |
|
|
36
|
+
| Admin dashboard support | Verification rows appear in the [comms-admin-dashboard](comms-admin-dashboard.md) with `errorCode`/`errorClass` and a `metadata.consoleUrl` deep link to the Verify service logs — but the `verification` channel is **never retryable** from admin (codes expire; the user restarts the flow) |
|
|
37
|
+
| Hooks | `onError` fires on failed starts/checks like any other channel; `beforeSend` can gate verification starts (e.g. per-user cool-downs) |
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// @terreno/comms/adapters/twilioVerify
|
|
43
|
+
export class TwilioVerifyProvider implements VerificationProvider {
|
|
44
|
+
readonly id = "twilio-verify";
|
|
45
|
+
constructor(options?: {accountSid?: string; authToken?: string; verifyServiceSid?: string});
|
|
46
|
+
startVerification(options: {to: string; channel: "sms" | "email"}): Promise<SendResult>;
|
|
47
|
+
checkVerification(options: {to: string; code: string}): Promise<{valid: boolean; error?: string}>;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`checkVerification` maps Verify's `approved` status to `valid: true`; `pending`, `expired`,
|
|
52
|
+
and max-attempt states return `valid: false` with reason in the result error, letting
|
|
53
|
+
callers throw the appropriate `APIError`.
|
|
54
|
+
|
|
55
|
+
## Models / APIs / Notifications / UI
|
|
56
|
+
|
|
57
|
+
None new. Consumer routes (e.g. `POST /auth/verifyPhone`) arrive with the features that
|
|
58
|
+
need them; this IP ships the provider plus example-backend registration only.
|
|
59
|
+
|
|
60
|
+
## Phases
|
|
61
|
+
|
|
62
|
+
Single phase.
|
|
63
|
+
|
|
64
|
+
## Feature Flags & Migrations
|
|
65
|
+
|
|
66
|
+
None.
|
|
67
|
+
|
|
68
|
+
## Activity Log & User Updates
|
|
69
|
+
|
|
70
|
+
Verification attempts logged to `CommsMessage` (no codes; destination fully redacted by core,
|
|
71
|
+
with the verification channel stored in metadata).
|
|
72
|
+
|
|
73
|
+
## Not Included / Future Work
|
|
74
|
+
|
|
75
|
+
- MFA enrollment, recovery codes, biometric step-up (`mfa-step-up-auth`).
|
|
76
|
+
- WhatsApp/voice channels.
|
|
77
|
+
- Android SMS autofill hash configuration (`react-native-otp-verify`, decision D7).
|
|
78
|
+
|
|
79
|
+
## Files to Create / Modify
|
|
80
|
+
|
|
81
|
+
- `comms/src/adapters/twilioVerify.ts` + tests (mocked Twilio client)
|
|
82
|
+
- `example-backend/src/server.ts` — env-gated registration
|
|
83
|
+
- `docs/reference/comms.md`, `docs/reference/environment-variables.md`
|
|
84
|
+
|
|
85
|
+
## Task List
|
|
86
|
+
|
|
87
|
+
See [docs/tasks/comms-adapter-twilio-verify.md](../tasks/comms-adapter-twilio-verify.md).
|
|
88
|
+
|
|
89
|
+
## Acceptance Criteria
|
|
90
|
+
|
|
91
|
+
- [ ] `startVerification` creates a Verify verification on the configured service for sms
|
|
92
|
+
and email channels.
|
|
93
|
+
- [ ] `checkVerification` returns `valid: true` only for `approved`; expired and
|
|
94
|
+
max-attempt states return `valid: false` with a reason.
|
|
95
|
+
- [ ] Failed starts/checks record `errorCode`/`errorClass` per the classification row and
|
|
96
|
+
fire the consumer `onError` hook; verification rows are marked non-retryable for
|
|
97
|
+
the admin dashboard.
|
|
98
|
+
- [ ] No OTP code ever appears in logs or `CommsMessage` rows; destinations are redacted.
|
|
99
|
+
- [ ] Missing `TWILIO_VERIFY_SERVICE_SID` fails fast at registration with a clear error.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Implementation Plan: Comms admin dashboard (errors, retries, log digging)
|
|
2
|
+
|
|
3
|
+
**Status:** Draft
|
|
4
|
+
**Roadmap issue:** *(not yet seeded — see [roadmap-seed-issues.md](../explanation/roadmap-seed-issues.md))*
|
|
5
|
+
**Priority:** High
|
|
6
|
+
**Effort:** Medium batch
|
|
7
|
+
**Owner:** unassigned
|
|
8
|
+
**Created:** 2026-08-11
|
|
9
|
+
**Program:** [B2B platform](b2b-platform-program.md)
|
|
10
|
+
**Depends on:** [comms-abstraction](comms-abstraction.md) (Phases 1–2, including the hook + error-taxonomy additions); adapters optional but the dashboard is most useful once [comms-adapter-twilio-sms](comms-adapter-twilio-sms.md) and [comms-adapter-sendgrid](comms-adapter-sendgrid.md) land
|
|
11
|
+
**RTK deprecation flag:** Partial — screens use the generated SDK; migrate with syncdb like other admin screens
|
|
12
|
+
|
|
13
|
+
## Goal
|
|
14
|
+
|
|
15
|
+
Make the admin panel the single place to **operate** the comms layer: see errors and
|
|
16
|
+
failure rates at a glance, filter and dig into individual delivery logs (attempt history,
|
|
17
|
+
provider error codes, raw provider metadata), and retry failed sends with one click —
|
|
18
|
+
across every channel and provider, without touching Twilio/SendGrid consoles or raw Mongo.
|
|
19
|
+
|
|
20
|
+
The comms-abstraction IP ships a minimal read-only `GET /comms/messages` explorer. This IP
|
|
21
|
+
upgrades that into a real operations dashboard, following the same pattern the AI package
|
|
22
|
+
uses (`addAiRequestsExplorerRoutes`) but with mutation (retry) and much richer filtering.
|
|
23
|
+
|
|
24
|
+
## Non-Goals
|
|
25
|
+
|
|
26
|
+
- Building the messages themselves (comms-abstraction) or provider integrations (adapter
|
|
27
|
+
IPs).
|
|
28
|
+
- Charts beyond simple count cards — themed chart components arrive with
|
|
29
|
+
`charts-and-dashboards`; the stats endpoint is designed so charts can be added then.
|
|
30
|
+
- Broadcast/bulk *composition* from admin (sending new messages, campaign tooling).
|
|
31
|
+
- User-facing notification history (`notification-center` item).
|
|
32
|
+
- Cross-service log aggregation (Sentry/Cloud Logging stay the deep-debug tools; the
|
|
33
|
+
dashboard links out via `providerMessageId`).
|
|
34
|
+
|
|
35
|
+
## Decisions
|
|
36
|
+
|
|
37
|
+
| Question | Decision |
|
|
38
|
+
|----------|----------|
|
|
39
|
+
| Where do the routes live? | `@terreno/comms` (`src/routes/commsDashboard.ts`) — they operate on `CommsMessage`, which the comms package owns. `@terreno/admin-backend` stays model-generic |
|
|
40
|
+
| Where does the UI live? | `@terreno/admin-frontend` (`CommsDashboard*` components), rendered inside `AdminShellLayout` like the rest of the admin panel, and wired into example-frontend + admin-spa |
|
|
41
|
+
| Retry semantics | Retry creates a **new** `CommsMessage` row linked back via `retriedFromId`; the original row is never mutated except `retriedById`. Sends go through the normal `commsService` facade so hooks, logging, and error taxonomy all apply |
|
|
42
|
+
| What is retryable? | `status: "failed" \| "bounced"` **and** `errorClass !== "permanent"` (permanent failures — invalid number, unsubscribed, suppressed address — get a disabled button with the reason) **and** a retained payload exists. `verification` channel is never retryable (codes expire; users restart the flow) |
|
|
43
|
+
| Payload retention for retry | `CommsApp` option `retainPayloadDays` (default 30) keeps the rendered message body on the `CommsMessage` row, passed through the `redactPayload` hook before storage; without a retained payload the retry button is disabled with the reason |
|
|
44
|
+
| Filtering | Server-side query params, not client-side table filtering — delivery logs get large. Free-text `q` matches `subject`, `error`, and `to` (last-4 for redacted destinations) |
|
|
45
|
+
| Bulk retry | `POST /comms/messages/retryMany` takes the same filter object as the list route plus a hard cap (default 100 per call) so an admin can re-drive an outage window without scripting |
|
|
46
|
+
| Stats | One aggregation endpoint with day buckets per channel/provider/status; rendered as count cards now, charts later |
|
|
47
|
+
|
|
48
|
+
## Architecture
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
@terreno/comms
|
|
52
|
+
src/routes/commsDashboard.ts # list/detail/retry/retryMany/stats (replaces commsExplorer.ts)
|
|
53
|
+
src/commsService.ts # retryMessage({messageId, req}) added to the facade
|
|
54
|
+
|
|
55
|
+
@terreno/admin-frontend
|
|
56
|
+
src/comms/CommsDashboardScreen.tsx # cards + filter bar + DataTable
|
|
57
|
+
src/comms/CommsMessageDetail.tsx # attempt timeline + raw metadata + retry
|
|
58
|
+
src/comms/CommsStatusBadge.tsx # status → Badge mapping
|
|
59
|
+
src/comms/useCommsDashboardApi.tsx # RTK Query hooks (pattern: useAdminApi)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Retry flow
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Admin clicks Retry
|
|
66
|
+
→ POST /comms/messages/:id/retry (IsAdmin)
|
|
67
|
+
→ commsService.retryMessage: validates retryability, loads retained payload
|
|
68
|
+
→ normal send path (beforeSend/onSend/onError hooks fire with isRetry: true)
|
|
69
|
+
→ new CommsMessage row {retriedFromId: original._id}
|
|
70
|
+
→ original row gets {retriedById: new._id}
|
|
71
|
+
→ response returns the new row; UI navigates to it
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Models
|
|
75
|
+
|
|
76
|
+
No new models. Uses the `CommsMessage` fields added by the comms-abstraction IP
|
|
77
|
+
(`errorCode`, `errorClass`, `attempts[]`, `attemptCount`, `lastAttemptAt`,
|
|
78
|
+
`retriedFromId`, `retriedById`, `templateId`, retained `payload`).
|
|
79
|
+
|
|
80
|
+
## APIs
|
|
81
|
+
|
|
82
|
+
All routes `Permissions.IsAdmin`, built with `createOpenApiBuilder`, in `/openapi.json`.
|
|
83
|
+
|
|
84
|
+
| Method | Path | Notes |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| GET | `/comms/messages` | Paginated list. Filters: `channel`, `provider`, `status`, `errorClass`, `errorCode`, `userId`, `to`, `templateId`, `retriedFromId`, `startDate`, `endDate`, free-text `q`; sort `-created` default |
|
|
87
|
+
| GET | `/comms/messages/:id` | Full detail: attempts array, raw provider `metadata`, retained payload (post-redaction), linked retry rows |
|
|
88
|
+
| POST | `/comms/messages/:id/retry` | Re-send; 400 with a stable `code` (`comms-retry-not-retryable`, `comms-retry-payload-expired`, `comms-retry-channel-unconfigured`) when not retryable |
|
|
89
|
+
| POST | `/comms/messages/retryMany` | Body: same filter object as list + `limit` (cap 100); returns `{retried, skipped: [{id, reason}]}` |
|
|
90
|
+
| GET | `/comms/stats` | Aggregation: counts by `channel` × `provider` × `status` with day buckets over `startDate`/`endDate` (default 7d); includes failure rate per provider |
|
|
91
|
+
|
|
92
|
+
## Notifications
|
|
93
|
+
|
|
94
|
+
None. (A failure-rate alert notifier is Future Work — see below.)
|
|
95
|
+
|
|
96
|
+
## UI
|
|
97
|
+
|
|
98
|
+
All screens live in `@terreno/admin-frontend`, use `@terreno/ui` components only, and
|
|
99
|
+
render on the `AdminShellLayout` canvas (`Page` with `color="transparent"`, `padding={0}`).
|
|
100
|
+
|
|
101
|
+
- **`CommsDashboardScreen`**
|
|
102
|
+
- Summary `Card` row from `/comms/stats`: sent / delivered / failed / bounced for the
|
|
103
|
+
selected range, failure rate per provider, with an error-colored card when the failure
|
|
104
|
+
rate exceeds 5%.
|
|
105
|
+
- Filter bar: `SelectField`s for channel/provider/status/errorClass, `TextField`
|
|
106
|
+
(search icon) for `q`, `DateTimeField` pair for the range. Filters map 1:1 to list
|
|
107
|
+
query params and persist in the URL so views are shareable.
|
|
108
|
+
- `DataTable`: created, channel, provider, to (redacted), subject/preview, status
|
|
109
|
+
(`CommsStatusBadge`), errorCode, attemptCount; row click → detail; inline Retry
|
|
110
|
+
`IconButton` on retryable rows.
|
|
111
|
+
- Loading, error, and empty states per admin-frontend conventions.
|
|
112
|
+
- **`CommsMessageDetail`**
|
|
113
|
+
- Header: status badge, channel/provider, recipient, timestamps, linked user
|
|
114
|
+
(`AdminRefField` → User), `retriedFromId`/`retriedById` links.
|
|
115
|
+
- **Attempt timeline**: one row per `attempts[]` entry — timestamp, provider,
|
|
116
|
+
`providerMessageId` (rendered as an external link to the provider console when the
|
|
117
|
+
adapter supplies `consoleUrl` in metadata), errorCode + error message.
|
|
118
|
+
- **Log digging**: collapsible JSON viewer (`Accordion` + `MarkdownView` code block) for
|
|
119
|
+
retained payload and raw provider `metadata` — everything the adapter recorded, post
|
|
120
|
+
redaction.
|
|
121
|
+
- **Retry** `Button` (`withConfirmation`) — disabled with a tooltip reason when not
|
|
122
|
+
retryable.
|
|
123
|
+
|
|
124
|
+
## Phases
|
|
125
|
+
|
|
126
|
+
1. **Backend routes:** upgrade the explorer to `commsDashboard.ts` (filters, detail,
|
|
127
|
+
stats), `retryMessage` on the facade, retry + retryMany routes, supertest coverage for
|
|
128
|
+
every filter and every non-retryable reason.
|
|
129
|
+
2. **Admin screens:** `CommsDashboardScreen`, `CommsMessageDetail`, badge + hooks; wire
|
|
130
|
+
into example-frontend admin routes and admin-spa nav; regenerate SDK; frontend
|
|
131
|
+
verification with screenshots.
|
|
132
|
+
3. **Bulk retry + docs:** `retryMany` UI (filtered "Retry all failed" with count
|
|
133
|
+
confirmation), `docs/reference/comms.md` dashboard section, rulesync.
|
|
134
|
+
|
|
135
|
+
## Feature Flags & Migrations
|
|
136
|
+
|
|
137
|
+
None. New routes and screens only; `CommsMessage` fields ship in comms-abstraction.
|
|
138
|
+
|
|
139
|
+
## Activity Log & User Updates
|
|
140
|
+
|
|
141
|
+
Every retry is itself a `CommsMessage` row (with `retriedFromId` and the acting admin in
|
|
142
|
+
`metadata.retriedByUserId`), so the audit trail is the same surface the dashboard shows.
|
|
143
|
+
|
|
144
|
+
## Not Included / Future Work
|
|
145
|
+
|
|
146
|
+
- Failure-rate alerting (Slack/Google Chat notifier when a provider's failure rate spikes)
|
|
147
|
+
— small follow-up once `/comms/stats` exists.
|
|
148
|
+
- Charts on the stats endpoint (`charts-and-dashboards`).
|
|
149
|
+
- Editing a message before retry ("retry with edits").
|
|
150
|
+
- Automatic scheduled retries with backoff — arrives with `job-queues`; this IP is
|
|
151
|
+
explicitly *manual, admin-driven* retry.
|
|
152
|
+
|
|
153
|
+
## Files to Create / Modify
|
|
154
|
+
|
|
155
|
+
- `comms/src/routes/commsDashboard.ts` (+ tests) — replaces `commsExplorer.ts`
|
|
156
|
+
- `comms/src/commsService.ts` — `retryMessage`
|
|
157
|
+
- `admin-frontend/src/comms/*` (+ tests)
|
|
158
|
+
- `example-frontend/app/admin/comms/*` — routes; `store/openApiSdk.ts` via `bun run sdk`
|
|
159
|
+
- `admin-spa` — nav entry
|
|
160
|
+
- `docs/reference/comms.md`, `docs/reference/admin.md`
|
|
161
|
+
|
|
162
|
+
## Task List
|
|
163
|
+
|
|
164
|
+
See [docs/tasks/comms-admin-dashboard.md](../tasks/comms-admin-dashboard.md).
|
|
165
|
+
|
|
166
|
+
## Acceptance Criteria
|
|
167
|
+
|
|
168
|
+
- [ ] An admin can filter delivery logs by channel, provider, status, error class, error
|
|
169
|
+
code, user, recipient, template, date range, and free text — server-side, paginated.
|
|
170
|
+
- [ ] The detail view shows every attempt with timestamp, provider message id, error code
|
|
171
|
+
and message, plus the raw provider metadata and retained payload in a JSON viewer.
|
|
172
|
+
- [ ] Retry re-sends through the configured provider, creates a linked `CommsMessage` row,
|
|
173
|
+
fires the normal lifecycle hooks with `isRetry: true`, and records the acting admin.
|
|
174
|
+
- [ ] Non-retryable messages (permanent failure, expired payload, unconfigured channel,
|
|
175
|
+
verification channel) show a disabled retry with the specific reason; the route
|
|
176
|
+
returns 400 with the matching stable error `code`.
|
|
177
|
+
- [ ] `retryMany` re-drives at most the cap per call and reports skipped ids with reasons.
|
|
178
|
+
- [ ] `/comms/stats` counts match the filtered list totals for the same range.
|
|
179
|
+
- [ ] All routes are `IsAdmin`; non-admin requests get 403 (supertest coverage).
|
|
180
|
+
- [ ] Screens verified in the running example app with screenshots attached to the PR.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Implementation Plan: Deploy to GCP (Generalized) + `deploy-gcp` Skill
|
|
2
2
|
|
|
3
3
|
**Status:** Approved — decisions recorded (2026-07-29)
|
|
4
|
+
**Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1005
|
|
4
5
|
**Priority:** High
|
|
5
6
|
**Effort:** Big batch
|
|
6
7
|
**Owner:** unassigned
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Implementation Plan: Deploy to Vercel + `deploy-vercel` Skill
|
|
2
2
|
|
|
3
3
|
**Status:** Draft — open Vercel spike TODOs; other decisions recorded (2026-07-29)
|
|
4
|
+
**Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1012
|
|
4
5
|
**Priority:** High
|
|
5
6
|
**Effort:** Big batch
|
|
6
7
|
**Owner:** unassigned
|