@terreno/mcp 57.0.0-beta.1 → 57.1.0

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.
Files changed (222) hide show
  1. package/dist/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +381 -191
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
  6. package/dist/docs/resources/overview.md +6 -1
  7. package/dist/docs/resources/rtk.md +3 -1
  8. package/dist/docs/resources/syncdb.md +43 -0
  9. package/dist/docs/upgrades/56.0.0-beta.2.md +34 -0
  10. package/dist/docs/upgrades/57.0.0.md +49 -0
  11. package/dist/docs/upgrades/57.1.0.md +126 -0
  12. package/dist/docs/versioned/next/README.md +15 -3
  13. package/dist/docs/versioned/next/explanation/README.md +3 -0
  14. package/dist/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
  15. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
  16. package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
  17. package/dist/docs/versioned/next/explanation/authentication.md +51 -58
  18. package/dist/docs/versioned/next/explanation/local-first-data.md +52 -0
  19. package/dist/docs/versioned/next/explanation/loop-engineering.md +117 -0
  20. package/dist/docs/versioned/next/explanation/repository-settings.md +8 -0
  21. package/dist/docs/versioned/next/explanation/roadmap-process.md +22 -9
  22. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
  23. package/dist/docs/versioned/next/how-to/README.md +3 -0
  24. package/dist/docs/versioned/next/how-to/admin-add-model.md +41 -0
  25. package/dist/docs/versioned/next/how-to/admin-custom-home.md +31 -0
  26. package/dist/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
  27. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
  28. package/dist/docs/versioned/next/how-to/circleci.md +173 -0
  29. package/dist/docs/versioned/next/how-to/configure-better-auth.md +13 -10
  30. package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
  31. package/dist/docs/versioned/next/how-to/install-agent-skills.md +46 -0
  32. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
  33. package/dist/docs/versioned/next/how-to/websocket-integration.md +2 -1
  34. package/dist/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
  35. package/dist/docs/versioned/next/implementationPlans/README.md +31 -17
  36. package/dist/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
  37. package/dist/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
  38. package/dist/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
  39. package/dist/docs/versioned/next/implementationPlans/admin-only.md +3 -1
  40. package/dist/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
  41. package/dist/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
  42. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
  43. package/dist/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
  44. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
  45. package/dist/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
  46. package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
  47. package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
  48. package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
  49. package/dist/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
  50. package/dist/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
  51. package/dist/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
  52. package/dist/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
  53. package/dist/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
  54. package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
  55. package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
  56. package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
  57. package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
  58. package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
  59. package/dist/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
  60. package/dist/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
  61. package/dist/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
  62. package/dist/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
  63. package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
  64. package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
  65. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  66. package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
  67. package/dist/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
  68. package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
  69. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
  70. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
  71. package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
  72. package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
  73. package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
  74. package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
  75. package/dist/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
  76. package/dist/docs/versioned/next/reference/README.md +6 -1
  77. package/dist/docs/versioned/next/reference/admin-config.md +51 -0
  78. package/dist/docs/versioned/next/reference/ai.md +15 -1
  79. package/dist/docs/versioned/next/reference/api.md +18 -0
  80. package/dist/docs/versioned/next/reference/comms.md +88 -7
  81. package/dist/docs/versioned/next/reference/environment-variables.md +9 -1
  82. package/dist/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
  83. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
  84. package/dist/docs/versioned/next/reference/mcp-server.md +7 -0
  85. package/dist/docs/versioned/next/reference/syncdb.md +30 -3
  86. package/dist/docs/versioned/next/tasks/README.md +21 -10
  87. package/dist/docs/versioned/next/tasks/admin-improvements.md +189 -353
  88. package/dist/docs/versioned/next/tasks/admin-only.md +2 -0
  89. package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
  90. package/dist/docs/versioned/next/tasks/comms-abstraction.md +30 -24
  91. package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
  92. package/dist/docs/versioned/next/tasks/consent-forms.md +2 -0
  93. package/dist/docs/versioned/next/tasks/feature-flags.md +2 -0
  94. package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
  95. package/dist/docs/versioned/next/tasks/model-router-actions.md +2 -0
  96. package/dist/docs/versioned/next/tasks/model-router-mcp.md +27 -27
  97. package/dist/docs/versioned/next/tasks/offline-mode.md +2 -0
  98. package/dist/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
  99. package/dist/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
  100. package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
  101. package/dist/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
  102. package/dist/docs/versioned/next/tasks/upgrade-banner.md +2 -0
  103. package/dist/index.js +24 -55
  104. package/dist/index.js.map +1 -1
  105. package/dist/local/localServer.d.ts.map +1 -1
  106. package/dist/local/localServer.js +9 -8
  107. package/dist/local/localServer.js.map +1 -1
  108. package/dist/local/localTools.d.ts +1 -1
  109. package/dist/local/localTools.d.ts.map +1 -1
  110. package/dist/prompts.d.ts.map +1 -1
  111. package/dist/prompts.js +44 -45
  112. package/dist/prompts.js.map +1 -1
  113. package/dist/resources.d.ts.map +1 -1
  114. package/dist/resources.js +9 -2
  115. package/dist/resources.js.map +1 -1
  116. package/dist/tools.d.ts +1 -1
  117. package/dist/tools.d.ts.map +1 -1
  118. package/dist/tools.js +4 -2
  119. package/dist/tools.js.map +1 -1
  120. package/package.json +7 -3
  121. package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
  122. package/src/docs/resources/overview.md +6 -1
  123. package/src/docs/resources/rtk.md +3 -1
  124. package/src/docs/resources/syncdb.md +43 -0
  125. package/src/docs/upgrades/56.0.0-beta.2.md +34 -0
  126. package/src/docs/upgrades/57.0.0.md +49 -0
  127. package/src/docs/upgrades/57.1.0.md +126 -0
  128. package/src/docs/versioned/next/README.md +15 -3
  129. package/src/docs/versioned/next/explanation/README.md +3 -0
  130. package/src/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
  131. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
  132. package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
  133. package/src/docs/versioned/next/explanation/authentication.md +51 -58
  134. package/src/docs/versioned/next/explanation/local-first-data.md +52 -0
  135. package/src/docs/versioned/next/explanation/loop-engineering.md +117 -0
  136. package/src/docs/versioned/next/explanation/repository-settings.md +8 -0
  137. package/src/docs/versioned/next/explanation/roadmap-process.md +22 -9
  138. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
  139. package/src/docs/versioned/next/how-to/README.md +3 -0
  140. package/src/docs/versioned/next/how-to/admin-add-model.md +41 -0
  141. package/src/docs/versioned/next/how-to/admin-custom-home.md +31 -0
  142. package/src/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
  143. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
  144. package/src/docs/versioned/next/how-to/circleci.md +173 -0
  145. package/src/docs/versioned/next/how-to/configure-better-auth.md +13 -10
  146. package/src/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
  147. package/src/docs/versioned/next/how-to/install-agent-skills.md +46 -0
  148. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
  149. package/src/docs/versioned/next/how-to/websocket-integration.md +2 -1
  150. package/src/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
  151. package/src/docs/versioned/next/implementationPlans/README.md +31 -17
  152. package/src/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
  153. package/src/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
  154. package/src/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
  155. package/src/docs/versioned/next/implementationPlans/admin-only.md +3 -1
  156. package/src/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
  157. package/src/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
  158. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
  159. package/src/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
  160. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
  161. package/src/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
  162. package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
  163. package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
  164. package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
  165. package/src/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
  166. package/src/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
  167. package/src/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
  168. package/src/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
  169. package/src/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
  170. package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
  171. package/src/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
  172. package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
  173. package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
  174. package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
  175. package/src/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
  176. package/src/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
  177. package/src/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
  178. package/src/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
  179. package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
  180. package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
  181. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  182. package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
  183. package/src/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
  184. package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
  185. package/src/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
  186. package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
  187. package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
  188. package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
  189. package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
  190. package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
  191. package/src/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
  192. package/src/docs/versioned/next/reference/README.md +6 -1
  193. package/src/docs/versioned/next/reference/admin-config.md +51 -0
  194. package/src/docs/versioned/next/reference/ai.md +15 -1
  195. package/src/docs/versioned/next/reference/api.md +18 -0
  196. package/src/docs/versioned/next/reference/comms.md +88 -7
  197. package/src/docs/versioned/next/reference/environment-variables.md +9 -1
  198. package/src/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
  199. package/src/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
  200. package/src/docs/versioned/next/reference/mcp-server.md +7 -0
  201. package/src/docs/versioned/next/reference/syncdb.md +30 -3
  202. package/src/docs/versioned/next/tasks/README.md +21 -10
  203. package/src/docs/versioned/next/tasks/admin-improvements.md +189 -353
  204. package/src/docs/versioned/next/tasks/admin-only.md +2 -0
  205. package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
  206. package/src/docs/versioned/next/tasks/comms-abstraction.md +30 -24
  207. package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
  208. package/src/docs/versioned/next/tasks/consent-forms.md +2 -0
  209. package/src/docs/versioned/next/tasks/feature-flags.md +2 -0
  210. package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
  211. package/src/docs/versioned/next/tasks/model-router-actions.md +2 -0
  212. package/src/docs/versioned/next/tasks/model-router-mcp.md +27 -27
  213. package/src/docs/versioned/next/tasks/offline-mode.md +2 -0
  214. package/src/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
  215. package/src/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
  216. package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
  217. package/src/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
  218. package/src/docs/versioned/next/tasks/upgrade-banner.md +2 -0
  219. package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
  220. package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
  221. package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
  222. package/src/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
@@ -1,14 +1,18 @@
1
1
  # Implementation Plan: Pluggable communications layer (@terreno/comms)
2
2
 
3
- **Status:** Draft
3
+ **Status:** Approved — Phase 1 gap-fill implemented
4
4
  **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1018
5
5
  **Priority:** High
6
6
  **Effort:** Big batch
7
7
  **Owner:** unassigned
8
8
  **Created:** 2026-08-09
9
+ **Approved:** 2026-08-20
9
10
  **Program:** [B2B platform](b2b-platform-program.md)
10
11
  **Depends on:** none (inbound-webhooks is needed only by adapter delivery-callback phases)
11
12
  **RTK deprecation flag:** None — backend-only package
13
+ **Shipped:** Phases 2–3 and most of Phase 1 in #1037; SendGrid adapter in #1050 (separate IP)
14
+ **Remaining:** none — Phase 1 gap-fill landed on this branch
15
+ **Research:** [comms-abstraction-research.md](comms-abstraction-research.md)
12
16
 
13
17
  ## Goal
14
18
 
@@ -26,14 +30,16 @@ consumer apps install only the SDKs they use.
26
30
 
27
31
  ## Non-Goals
28
32
 
29
- - Any concrete provider SDK integration (see `comms-adapter-*` IPs).
33
+ - Any concrete provider SDK integration (see `comms-adapter-*` IPs). The SendGrid adapter
34
+ already lives at `@terreno/comms/adapters/sendgrid` under that IP; this IP does not own it.
30
35
  - In-app notification center, inbox UI, or user notification preferences
31
36
  (`notification-center` item).
32
37
  - Password reset / email verification flows (`password-reset-and-email-verification` item
33
38
  — a consumer of this layer).
34
- - Inbound webhook handling (`inbound-webhooks` item); this IP only defines the
35
- `DeliveryEvent` type adapters will feed from callbacks.
39
+ - Inbound webhook HTTP handling (`inbound-webhooks` item); this IP ships
40
+ `recordDeliveryEvent` / `recordOptOut` for adapters to call once those webhooks land.
36
41
  - Broadcast/bulk campaign tooling.
42
+ - Admin retry UI and rich explorer filters ([comms-admin-dashboard](comms-admin-dashboard.md)).
37
43
 
38
44
  ## Decisions
39
45
 
@@ -48,6 +54,13 @@ consumer apps install only the SDKs they use.
48
54
  | Consumer lifecycle hooks | `CommsApp` options expose `beforeSend` / `onSend` / `onError` / `onRetry` / `onOptOut` / `onDeliveryEvent`. Hooks are awaited but wrapped: a throwing hook is logged (`logger.error`) and never breaks the send. `beforeSend` may mutate or cancel |
49
55
  | Error taxonomy | Every `SendResult` failure carries `errorCode` (provider-native code as a string) and `errorClass` (`"permanent"` \| `"transient"` \| `"config"`). Adapters own the mapping; the facade uses `errorClass` to decide its single inline retry (transient only) and the admin dashboard uses it to gate manual retries |
50
56
  | Payload retention | `retainPayloadDays` option (default 30) keeps the rendered message on the `CommsMessage` row (post `redactPayload` hook) so the admin dashboard can retry; a TTL-style cleanup clears expired payloads |
57
+ | `onRetry` signature (published) | Keep shipped `(context, result: SendResult)`. Put 1-based `attempt` on `CommsHookContext` (2 = the inline retry). Do not change the callback to `(context, attempt)` |
58
+ | Provider throws | Facade never rethrows from `sendMail` / `sendSms` / `sendPushToUser` / `startVerification` / `checkVerification`. A throwing provider becomes `errorClass: "transient"`, `errorCode: "provider-throw"`, then follows the same retry rules |
59
+ | Push retry | Retry **only tokens** whose first `SendResult` is `errorClass: "transient"` via a second `sendPush`; merge. Do not re-send accepted tokens |
60
+ | Push prune | Deactivate when `errorClass === "permanent"` **or** `isPermanentFailure === true` (shipped alias; adapters may set both) |
61
+ | Delivery / opt-out intake | `CommsService.recordDeliveryEvent` / `recordOptOut` update the matching log row (by `providerMessageId`) and invoke hooks. Missing rows: `logger.warn`, still fire the hook |
62
+ | Payload expiry | `payloadExpiresAt` on the row. Mongo TTL indexes must **not** be used (they would delete the log). `CommsMessage.clearExpiredPayloads()` unsets `payload` only; `logSend` / `appendAttempt` call it best-effort (limit 50) |
63
+ | `checkVerification` | Log a verification row; no inline retry; never persist the code |
51
64
 
52
65
  ## Architecture
53
66
 
@@ -56,12 +69,12 @@ consumer apps install only the SDKs they use.
56
69
  src/
57
70
  types.ts # Provider interfaces + message/result types
58
71
  commsApp.ts # CommsApp TerrenoPlugin
59
- commsService.ts # sendMail/sendSms/sendPush/startVerification/checkVerification
72
+ commsService.ts # send* + recordDeliveryEvent/recordOptOut
60
73
  models/
61
74
  commsMessage.ts # Delivery log
62
75
  pushToken.ts # Device push token registry
63
76
  routes/
64
- pushTokens.ts # modelRouter for token registration
77
+ pushTokens.ts # custom upsert/list/deactivate + owner read
65
78
  commsExplorer.ts # Admin-only delivery log explorer
66
79
  adapters/
67
80
  console.ts # Dev adapters for all four channels
@@ -71,26 +84,39 @@ consumer apps install only the SDKs they use.
71
84
  ### Provider interfaces (types.ts)
72
85
 
73
86
  ```typescript
87
+ export type CommsErrorClass = "config" | "permanent" | "transient";
88
+ export type CommsChannel = "mail" | "push" | "sms" | "verification";
89
+ export type CommsMessageStatus =
90
+ | "bounced"
91
+ | "cancelled"
92
+ | "delivered"
93
+ | "failed"
94
+ | "sent";
95
+
74
96
  export interface SendResult {
75
97
  providerMessageId?: string;
76
98
  accepted: boolean;
77
99
  error?: string;
78
- errorCode?: string; // provider-native code, e.g. "21610", "sendgrid-400"
79
- errorClass?: "permanent" | "transient" | "config"; // drives retry eligibility
100
+ errorCode?: string;
101
+ errorClass?: CommsErrorClass;
102
+ isPermanentFailure?: boolean; // alias: treat as errorClass "permanent" when that is unset
103
+ metadata?: Record<string, unknown>;
80
104
  }
81
105
 
82
106
  export interface MailMessage {
83
107
  to: string | string[];
84
- from?: string; // default from CommsApp options
108
+ from?: string;
85
109
  subject: string;
86
110
  text?: string;
87
111
  html?: string;
88
112
  replyTo?: string;
113
+ templateId?: string;
114
+ dynamicTemplateData?: Record<string, unknown>;
89
115
  metadata?: Record<string, string>;
90
116
  }
91
117
 
92
118
  export interface MailProvider {
93
- readonly id: string; // "console" | "sendgrid" | ...
119
+ readonly id: string;
94
120
  sendMail(message: MailMessage): Promise<SendResult>;
95
121
  }
96
122
 
@@ -110,7 +136,7 @@ export interface PushMessage {
110
136
 
111
137
  export interface PushProvider {
112
138
  readonly id: string;
113
- sendPush(message: PushMessage): Promise<SendResult[]>; // one per token
139
+ sendPush(message: PushMessage): Promise<SendResult[]>; // one per token, same order
114
140
  }
115
141
 
116
142
  export interface VerificationProvider {
@@ -126,29 +152,34 @@ export interface DeliveryEvent {
126
152
  channel: "mail" | "sms" | "push";
127
153
  providerMessageId: string;
128
154
  status: "delivered" | "bounced" | "failed" | "opened";
129
- errorCode?: string; // provider failure code from the callback, when present
155
+ errorCode?: string;
156
+ errorClass?: CommsErrorClass;
130
157
  raw?: unknown;
131
158
  }
132
159
 
133
160
  export interface CommsHookContext {
134
- channel: "mail" | "sms" | "push" | "verification";
161
+ channel: CommsChannel;
135
162
  provider: string;
136
- message: MailMessage | {to: string; body: string} | PushMessage;
163
+ message?: MailMessage | {to: string; body: string} | PushMessage | {
164
+ to: string;
165
+ channel: "sms" | "email";
166
+ };
137
167
  userId?: string;
138
- isRetry: boolean; // true for facade inline retries and admin-dashboard retries
139
- messageId?: string; // CommsMessage._id once the log row exists
168
+ isRetry: boolean;
169
+ attempt: number; // 1 = first provider call, 2 = inline retry
170
+ messageId?: string; // CommsMessage._id once the log row exists
140
171
  }
141
172
 
142
173
  export interface OptOutEvent {
143
174
  channel: "mail" | "sms";
144
175
  to: string;
145
176
  provider: string;
146
- reason: string; // e.g. "sms-stop", "unsubscribe", "spam-report"
177
+ reason: string; // e.g. "sms-stop", "unsubscribe", "spam-report"
147
178
  raw?: unknown;
148
179
  }
149
180
  ```
150
181
 
151
- Verification start attempts store `metadata.verificationChannel` while recipient values remain
182
+ Verification start attempts store `metadata.verificationChannel`. Recipients stay
152
183
  redacted. Verification codes are never persisted.
153
184
 
154
185
  ### CommsApp (TerrenoPlugin)
@@ -159,18 +190,17 @@ export interface CommsOptions {
159
190
  sms?: SmsProvider;
160
191
  push?: PushProvider;
161
192
  verification?: VerificationProvider;
162
- defaultFrom?: string; // mail from fallback
163
- logMessages?: boolean; // default true → CommsMessage
164
- retainPayloadDays?: number; // default 30; 0 disables retention (and admin retry)
165
- redactPayload?: (context: CommsHookContext, payload: unknown) => unknown; // before storage
193
+ defaultFrom?: string;
194
+ logMessages?: boolean; // default true
195
+ retainPayloadDays?: number; // default 30; 0 stores no payload
196
+ redactRecipients?: boolean; // default true — `to` at rest
197
+ redactPayload?: (context: CommsHookContext, payload: unknown) => unknown;
166
198
 
167
- // Lifecycle hooks — all optional, all awaited, all wrapped so a throwing
168
- // hook is logged and never breaks (or blocks) the send itself.
169
199
  beforeSend?: (context: CommsHookContext) =>
170
200
  Promise<{cancel?: boolean; message?: CommsHookContext["message"]} | void>;
171
201
  onSend?: (context: CommsHookContext, result: SendResult) => Promise<void>;
172
202
  onError?: (context: CommsHookContext, result: SendResult) => Promise<void>;
173
- onRetry?: (context: CommsHookContext, attempt: number) => Promise<void>;
203
+ onRetry?: (context: CommsHookContext, result: SendResult) => Promise<void>;
174
204
  onOptOut?: (event: OptOutEvent) => Promise<void>;
175
205
  onDeliveryEvent?: (event: DeliveryEvent) => Promise<void>;
176
206
  }
@@ -181,10 +211,10 @@ new TerrenoApp({userModel: User})
181
211
  ```
182
212
 
183
213
  `CommsApp.register()` mounts the push-token routes and admin explorer;
184
- `getCommsService()` (module-level accessor, mirroring how `FeatureFlagsApp` exposes
185
- evaluation) gives routes and other plugins `sendMail` / `sendSms` / `sendPushToUser` /
186
- `startVerification` / `checkVerification`. `sendPushToUser(userId, message)` resolves the
187
- user's active `PushToken`s and prunes tokens the provider reports dead.
214
+ `getCommsService()` (module-level accessor) gives routes and other plugins `sendMail` /
215
+ `sendSms` / `sendPushToUser` / `startVerification` / `checkVerification` /
216
+ `recordDeliveryEvent` / `recordOptOut`. `sendPushToUser(userId, message)` resolves the
217
+ user's active `PushToken`s and prunes tokens the provider reports permanently dead.
188
218
 
189
219
  Unconfigured channel behavior: in production, throw `APIError({status: 501, title:
190
220
  "Comms channel not configured"})`; in development, fall back to the console adapter with a
@@ -192,22 +222,54 @@ Unconfigured channel behavior: in production, throw `APIError({status: 501, titl
192
222
 
193
223
  ### Hook order and semantics
194
224
 
195
- For every send (including retries) the facade runs:
225
+ For every send (including the inline retry) the facade runs:
196
226
 
197
- 1. `beforeSend` — may replace the message (`{message}`) or cancel (`{cancel: true}`,
198
- logged as a `CommsMessage` with status `cancelled`). The consuming app uses this for
199
- quiet hours, per-user notification preferences, and suppression lists.
200
- 2. Provider `send*` → on transient failure (`errorClass: "transient"`), `onRetry` fires
201
- and the facade retries once inline.
227
+ 1. `beforeSend` — once, **before the first** provider call. May replace the message
228
+ (`{message}`) or cancel (`{cancel: true}` → `CommsMessage` with `status: "cancelled"`,
229
+ no provider call). Quiet hours, preferences, and suppression lists use this hook.
230
+ 2. Provider `send*` → on `errorClass: "transient"` (or a wrapped provider throw),
231
+ `onRetry(context, failedResult)` fires with `context.attempt === 2` and `isRetry: true`,
232
+ then the facade retries once.
202
233
  3. `onSend` (accepted) or `onError` (final failure) — `onError` receives the full
203
- `SendResult` (`errorCode`, `errorClass`) so apps can alert, escalate, or mark domain
204
- objects without polling the delivery log.
205
- 4. Asynchronously, adapters feed `onDeliveryEvent` (delivery callbacks) and `onOptOut`
206
- (STOP replies, unsubscribes, spam reports) once their webhook phases land.
234
+ `SendResult`.
235
+ 4. Later, adapters call `recordDeliveryEvent` / `recordOptOut`, which fire
236
+ `onDeliveryEvent` / `onOptOut`.
237
+
238
+ `checkVerification` skips `beforeSend` and inline retry. It still logs and fires
239
+ `onSend` / `onError`.
207
240
 
208
241
  Hooks are observation and shaping points, not error channels: exceptions thrown inside a
209
- hook are caught, logged with `logger.error`, and recorded in `CommsMessage.metadata` —
210
- they never change the send outcome.
242
+ hook are caught, logged with `logger.error`, and recorded in `CommsMessage.metadata.hookErrors`
243
+ — they never change the send outcome. A throwing `beforeSend` is treated as “no mutation,
244
+ do not cancel.”
245
+
246
+ ### Attempt logging
247
+
248
+ One `CommsMessage` row per send invocation (not per retry):
249
+
250
+ 1. After attempt 1, `logSend` creates the row (`attempts[0]`, `attemptCount: 1`).
251
+ 2. After the inline retry, `appendAttempt` pushes `attempts[1]` and updates top-level
252
+ `status`, `error`, `errorCode`, `errorClass`, `providerMessageId`, `attemptCount`,
253
+ `lastAttemptAt`.
254
+ 3. Cancelled sends create a row with `status: "cancelled"`, empty `attempts`, no provider.
255
+
256
+ `logSend` and `appendAttempt` never throw.
257
+
258
+ ### Payload stored per channel
259
+
260
+ After `redactPayload` (identity if unset), when `retainPayloadDays > 0`:
261
+
262
+ | Channel | `payload` |
263
+ |---|---|
264
+ | mail | `{to, from, subject, text, html, replyTo, templateId, dynamicTemplateData}` |
265
+ | sms | `{to, body}` |
266
+ | push | `{title, body, data, badge, sound}` (tokens omitted) |
267
+ | verification start | `{channel}` only |
268
+ | verification check | none |
269
+ | cancelled | the mutated-or-original message, same shape as the channel |
270
+
271
+ `payloadExpiresAt = now + retainPayloadDays`. Expired payload is treated as absent for
272
+ retry even before cleanup runs.
211
273
 
212
274
  ## Models
213
275
 
@@ -216,7 +278,7 @@ and live per the five-type pattern (`CommsMessageDocument`, etc.).
216
278
 
217
279
  **PushToken** — `userId` (ref User, required, indexed), `token` (string, required, unique),
218
280
  `platform` ("ios" | "android" | "web"), `deviceId` (string, optional), `active` (boolean,
219
- default true), `lastSeenAt` (date).
281
+ default true), `lastSeenAt` (date). Shipped.
220
282
 
221
283
  **CommsMessage** — `channel` ("mail" | "sms" | "push" | "verification"), `provider`
222
284
  (string), `to` (string, redacted-at-rest for sms/mail per option), `subject` (string,
@@ -224,11 +286,12 @@ optional), `userId` (ref User, optional), `status` ("sent" | "failed" | "deliver
224
286
  "bounced" | "cancelled"), `providerMessageId` (string, indexed), `error` (string,
225
287
  optional), `errorCode` (string, indexed), `errorClass` ("permanent" | "transient" |
226
288
  "config"), `templateId` (string, optional), `attempts` (array of `{at, provider,
227
- providerMessageId, errorCode, error}` — one entry per facade attempt including the inline
228
- retry), `attemptCount` (number), `lastAttemptAt` (date), `retriedFromId` / `retriedById`
229
- (ref CommsMessage — links between an original and its admin-dashboard retries), `payload`
230
- (Mixed, retained per `retainPayloadDays` after `redactPayload`, cleared on expiry),
231
- `metadata` (Mixed). Static `logSend()` mirrors `AIRequest.logRequest`.
289
+ providerMessageId, errorCode, errorClass, error}` — one entry per facade attempt including
290
+ the inline retry), `attemptCount` (number), `lastAttemptAt` (date), `retriedFromId` /
291
+ `retriedById` (ref CommsMessage — written later by admin-dashboard retries; schema exists
292
+ now), `payload` (Mixed, retained per `retainPayloadDays` after `redactPayload`, cleared on
293
+ expiry), `payloadExpiresAt` (date), `metadata` (Mixed). Statics: `logSend()`,
294
+ `appendAttempt()`, `clearExpiredPayloads()`.
232
295
 
233
296
  These fields exist so the [comms-admin-dashboard](comms-admin-dashboard.md) IP can filter
234
297
  on error codes/classes, show per-attempt history, and re-send from the retained payload
@@ -250,6 +313,8 @@ The explorer uses `createOpenApiBuilder`, modeled on `addAiRequestsExplorerRoute
250
313
  deliberately minimal here — the [comms-admin-dashboard](comms-admin-dashboard.md) IP upgrades it
251
314
  with error-code/class filtering, free-text search, a detail route, stats, and retry endpoints.
252
315
 
316
+ No HTTP routes for `recordDeliveryEvent` / `recordOptOut` in this IP.
317
+
253
318
  ## Notifications
254
319
 
255
320
  None beyond the feature itself.
@@ -263,14 +328,18 @@ None in this IP. example-frontend gains a settings row that registers the Expo p
263
328
 
264
329
  1. **Package + contracts:** `@terreno/comms` package scaffold, `types.ts`, console
265
330
  adapters, `CommsMessage` + logging, `commsService`, unit tests.
331
+ **Shipped** including dashboard-ready fields, hooks, payload retention, and
332
+ channel-wide transient retry.
266
333
  2. **CommsApp + routes:** plugin registration, `PushToken` model + routes, admin explorer,
267
- OpenAPI, supertest coverage.
334
+ OpenAPI, supertest coverage. **Shipped.**
268
335
  3. **Integration:** example-backend registers `CommsApp` (console adapters), seed docs,
269
336
  `docs/reference/comms.md`, catalog entries, publish wiring in `publish-on-tag.yml`.
337
+ **Shipped.**
270
338
 
271
339
  ## Feature Flags & Migrations
272
340
 
273
- None. New collections only; no existing data touched.
341
+ None. New collections only; no existing data touched. Remaining schema fields are additive
342
+ on `CommsMessage` (`cancelled` status value, `attempts`, payload fields, retry refs).
274
343
 
275
344
  ## Activity Log & User Updates
276
345
 
@@ -280,7 +349,8 @@ updates.
280
349
  ## Not Included / Future Work
281
350
 
282
351
  - Adapter IPs: `comms-adapter-expo-push`, `comms-adapter-twilio-sms`,
283
- `comms-adapter-twilio-verify`, `comms-adapter-sendgrid`.
352
+ `comms-adapter-twilio-verify`, `comms-adapter-sendgrid` (Phase 1 of SendGrid already
353
+ shipped).
284
354
  - Admin operations dashboard (rich filtering, log digging, manual retries) —
285
355
  [comms-admin-dashboard](comms-admin-dashboard.md); this IP only ships the fields and
286
356
  hooks it needs.
@@ -288,15 +358,20 @@ updates.
288
358
  point they will plug into).
289
359
  - Queue-backed sending with automatic scheduled retries — arrives with `job-queues`;
290
360
  until then sends are inline with one transient-error retry plus manual admin retries.
361
+ - Webhook HTTP for delivery callbacks — `inbound-webhooks` plus adapter Phase 2.
291
362
 
292
363
  ## Files to Create / Modify
293
364
 
294
- - `comms/` (new workspace package: `package.json`, `tsconfig.json`, `biome.jsonc`, `src/*`
295
- as in Architecture)
296
- - Root `package.json` — workspace entry + scripts (`comms:compile|lint|test`)
297
- - `example-backend/src/server.ts` — register `CommsApp`
298
- - `docs/reference/comms.md`, `.cursor/rules/comms/00-comms.mdc` (via rulesync)
299
- - `.github/workflows/publish-on-tag.yml`, new `comms-ci` workflow
365
+ Remaining roast (gap-fill):
366
+
367
+ - `comms/src/types.ts`, `commsService.ts`, `models/commsMessage.ts`, `modelTypes.ts`
368
+ - Tests for hooks, retry-per-channel, attempts, payload expiry
369
+ - `docs/reference/comms.md`, `.rulesync/rules/comms/00-comms.md` (then `bun run rules`)
370
+
371
+ Already shipped:
372
+
373
+ - `comms/` package scaffold, console adapters, `CommsApp`, routes, CI/publish
374
+ - Root `package.json` scripts, example-backend registration
300
375
 
301
376
  ## Task List
302
377
 
@@ -307,12 +382,15 @@ See [docs/tasks/comms-abstraction.md](../tasks/comms-abstraction.md).
307
382
  - [x] A Terreno app with only console adapters can call `sendMail`, `sendSms`,
308
383
  `sendPushToUser`, and `startVerification`/`checkVerification` in development, each
309
384
  producing a logger line and a `CommsMessage` row.
310
- - [ ] `beforeSend` can cancel and mutate; `onSend`/`onError` fire with the final
311
- `SendResult`; `onRetry` fires on the inline transient retry — and a hook that throws
312
- is logged without changing the send outcome (test per hook).
313
- - [ ] Failed sends record `errorCode` and `errorClass` on both the `SendResult` and the
314
- `CommsMessage` row; only `transient` failures trigger the inline retry.
315
- - [ ] `CommsMessage.attempts` holds one entry per attempt with per-attempt error data;
385
+ - [x] `beforeSend` can cancel and mutate; `onSend`/`onError` fire with the final
386
+ `SendResult`; `onRetry` fires on the inline transient retry with
387
+ `context.attempt === 2` — and a hook that throws is logged without changing the send
388
+ outcome (test per hook, including `onOptOut` / `onDeliveryEvent` via the record
389
+ methods).
390
+ - [x] Failed sends record `errorCode` and `errorClass` on both the `SendResult` and the
391
+ `CommsMessage` row; only `transient` failures trigger the inline retry; provider
392
+ throws become transient and do not propagate.
393
+ - [x] `CommsMessage.attempts` holds one entry per attempt with per-attempt error data;
316
394
  `payload` is retained post-redaction and cleared after `retainPayloadDays`.
317
395
  - [x] `POST /comms/pushTokens` registers a device token for the authenticated user;
318
396
  re-posting the same token updates rather than duplicates; other users cannot list or
@@ -321,3 +399,15 @@ See [docs/tasks/comms-abstraction.md](../tasks/comms-abstraction.md).
321
399
  - [x] Production apps with an unconfigured channel get a 501 `APIError`, not a silent no-op.
322
400
  - [x] `@terreno/comms` core has zero provider SDKs in `dependencies`.
323
401
  - [x] All routes appear in `/openapi.json` and the generated SDK compiles.
402
+ - [x] Push retries re-send only transient-failed tokens; permanent failures deactivate
403
+ tokens via `errorClass` or `isPermanentFailure`.
404
+ - [x] `recordDeliveryEvent` updates status by `providerMessageId`; `recordOptOut` fires
405
+ `onOptOut` without sending.
406
+
407
+ ## Named assumptions (finish pass)
408
+
409
+ 1. Original Decisions table stands; remaining work implements the IP rather than shrinking
410
+ it to today’s partial code.
411
+ 2. Published `onRetry(context, result)` stays; `attempt` is added on context.
412
+ 3. Roadmap issue #1018 already exists; Blend does not open a second issue. Maintainer can
413
+ move Project status when roast of remaining tasks starts.
@@ -1,6 +1,6 @@
1
1
  # Implementation Plan: Comms adapter — transactional email (SendGrid)
2
2
 
3
- **Status:** Draft
3
+ **Status:** In progress — Phase 1 send path implemented; Phase 2 gated on inbound-webhooks
4
4
  **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1022
5
5
  **Priority:** High
6
6
  **Effort:** Small batch
@@ -135,22 +135,22 @@ See [docs/tasks/comms-adapter-sendgrid.md](../tasks/comms-adapter-sendgrid.md).
135
135
 
136
136
  ## Acceptance Criteria
137
137
 
138
- - [ ] `sendMail` delivers text/html mail with from-address fallback chain (provider option
138
+ - [x] `sendMail` delivers text/html mail with from-address fallback chain (provider option
139
139
  → `CommsApp.defaultFrom`), and passes `templateId`/`dynamicTemplateData` through when
140
140
  given.
141
- - [ ] Sandbox mode is on by default under test and produces accepted results without real
141
+ - [x] Sandbox mode is on by default under test and produces accepted results without real
142
142
  sends.
143
- - [ ] SendGrid errors surface as `accepted: false` with the SendGrid error message and a
143
+ - [x] SendGrid errors surface as `accepted: false` with the SendGrid error message and a
144
144
  correct `errorCode`/`errorClass` per the classification table; nothing throws
145
145
  through the facade.
146
- - [ ] 401/403 classify as `config` and `logger.error` at send time; 429/5xx classify as
146
+ - [x] 401/403 classify as `config` and `logger.error` at send time; 429/5xx classify as
147
147
  `transient` and retry once; other 4xx are `permanent` and never retried.
148
- - [ ] Each accepted send captures the `x-message-id` and stores a `metadata.consoleUrl`
148
+ - [x] Each accepted send captures the `x-message-id` and stores a `metadata.consoleUrl`
149
149
  Email Activity deep link.
150
- - [ ] Consumer `onError` fires with the classified `SendResult` on every failed send
150
+ - [x] Consumer `onError` fires with the classified `SendResult` on every failed send
151
151
  (mocked-client test).
152
- - [ ] Missing API key fails fast at `CommsApp` registration with a clear error.
153
- - [ ] Apps not using the adapter do not install `@sendgrid/mail`.
152
+ - [x] Missing API key fails fast at `CommsApp` registration with a clear error.
153
+ - [x] Apps not using the adapter do not install `@sendgrid/mail`.
154
154
  - [ ] (Phase 2) Event webhook updates `CommsMessage.status` per the mapping table only
155
155
  for signature-verified payloads; bounce/dropped reasons land as `errorCode`.
156
156
  - [ ] (Phase 2) `spamreport`/`unsubscribe` events fire `onOptOut`; later sends to the
@@ -1,7 +1,7 @@
1
1
  # Implementation Plan: Comms admin dashboard (errors, retries, log digging)
2
2
 
3
3
  **Status:** Draft
4
- **Roadmap issue:** *(not yet seeded — see [roadmap-seed-issues.md](../explanation/roadmap-seed-issues.md))*
4
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1091
5
5
  **Priority:** High
6
6
  **Effort:** Medium batch
7
7
  **Owner:** unassigned
@@ -1,5 +1,9 @@
1
1
  # Implementation Plan: Consent Forms System
2
2
 
3
+ **Status:** Complete
4
+ **Closed:** 2026-08-20 — Shipped via `ConsentApp`, `ConsentForm`/`ConsentResponse` models, and `@terreno/ui` consent screens (`ConsentNavigator`, `ConsentFormScreen`, `useConsentForms`).
5
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1078
6
+
3
7
  *When an engineer is assigned to a project but before you begin coding, you should fill in the implementation plan and get feedback from the engineering team. Once you have finished or you make any changes, tag Josh with the @ symbol so he can review. Also tag anyone else that needs to be notified, has conflicting work, etc.*
4
8
 
5
9
  ## **Models**
@@ -1,5 +1,6 @@
1
1
  # Implementation Plan: design-blend Skill
2
2
 
3
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1083
3
4
  ## Goal
4
5
 
5
6
  Create a new `design-blend` skill that converts a Claude design file into a Terreno implementation blueprint and execution steps, with these hard constraints:
@@ -1,5 +1,6 @@
1
1
  # Implementation Plan: Documentation Site, Versioned Docs, and Docs-Maintenance Skills
2
2
 
3
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1086
3
4
  *When an engineer is assigned to a project but before you begin coding, you should fill in the implementation plan and get feedback from the engineering team. Once you have finished or you make any changes, tag Josh with the @ symbol so he can review. Also tag anyone else that needs to be notified, has conflicting work, etc.*
4
5
 
5
6
  ## Overview
@@ -1,5 +1,6 @@
1
1
  # Implementation Plan: OpenFeature Adoption for `@terreno/feature-flags`
2
2
 
3
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1082
3
4
  ## Goal
4
5
 
5
6
  Migrate `@terreno/feature-flags` (backend) and `@terreno/rtk`'s `useFeatureFlags` (frontend) to be built on top of the OpenFeature SDKs (`@openfeature/server-sdk`, `@openfeature/react-sdk`) while preserving:
@@ -1,5 +1,9 @@
1
1
  # Implementation Plan: Feature Flags & A/B Testing
2
2
 
3
+ **Status:** Deferred
4
+ **Closed:** 2026-08-20 — Superseded by [feature-flags-openfeature.md](./feature-flags-openfeature.md) (OpenFeature provider, live updates). This document is retained for historical context only.
5
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1098
6
+
3
7
  > **OpenFeature migration:** For the OpenFeature-based provider, `/flagConfiguration`, live updates, and RTK hook details, see [feature-flags-openfeature.md](./feature-flags-openfeature.md) and the task list [feature-flags-openfeature.md](../tasks/feature-flags-openfeature.md).
4
8
 
5
9
  *When an engineer is assigned to a project but before you begin coding, you should fill in the implementation plan and get feedback from the engineering team. Once you have finished or you make any changes, tag Josh with the @ symbol so he can review. Also tag anyone else that needs to be notified, has conflicting work, etc.*
@@ -1,6 +1,7 @@
1
1
  # Research: Infrastructure MCP server (privileged tools gateway)
2
2
 
3
3
  **Status:** Complete — decisions made, IP written: [infra-mcp.md](infra-mcp.md)
4
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1090
4
5
  **Created:** 2026-08-09
5
6
 
6
7
  ## Scope statement
@@ -1,6 +1,7 @@
1
1
  # Implementation Plan: Infrastructure MCP server (`@terreno/infra-mcp`)
2
2
 
3
- **Status:** Draft — blocked on the RBAC module ([rbac-permissions.md](rbac-permissions.md)) landing in `@terreno/api`
3
+ **Status:** Draft — unblocked; RBAC module is Complete ([rbac-permissions.md](rbac-permissions.md))
4
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1090
4
5
  **Branch:** TBD
5
6
  **Owner:** TBD
6
7
  **Created:** 2026-08-09
@@ -228,7 +229,7 @@ No new frontend package. Admin surfaces reuse `@terreno/admin-backend` + `@terre
228
229
 
229
230
  - **No data migrations** — all collections are new.
230
231
  - Rollout is additive: new package, new Cloud Run service. Nothing existing changes until Phase 2's cloud-agent credential switch, which is coordinated: keep `GCP_SA_*` injection until the MCP path is verified, then remove.
231
- - Blocked dependency: the RBAC module PR must merge first (Phase 1 RBAC wiring consumes `createAccess`, `rbacUserPlugin`, `rbacRouter`, default-role seeding).
232
+ - RBAC dependency: the RBAC module is Complete ([rbac-permissions.md](rbac-permissions.md)). Phase 1 wiring consumes `createAccess`, `rbacUserPlugin`, `rbacRouter`, default-role seeding.
232
233
 
233
234
  ## Activity Log & User Updates
234
235
 
@@ -249,7 +250,6 @@ No new frontend package. Admin surfaces reuse `@terreno/admin-backend` + `@terre
249
250
  - `package.json` (root) — workspace entry, catalog: zod 4, `@modelcontextprotocol/server`, `@better-auth/oauth-provider`, `@better-auth/mcp`, Google API clients.
250
251
  - `terraform/main.tf`, `variables.tf`, `outputs.tf` — `infra_mcp_service` module instance + runtime SA IAM.
251
252
  - `.cursor/rules/infra-mcp/00-infra-mcp.mdc`, `AGENTS.md`/`CLAUDE.md` package lists, docs site page.
252
- - `docs/implementationPlans/PLAN_INDEX.md` — register this IP.
253
253
  - `ROADMAP.md` + `docs/explanation/roadmap-seed-issues.md` — roadmap entry and ready-to-paste tracking issue (done); `scripts/issueAreaLabels.ts` + bug-report package dropdown when the package lands.
254
254
 
255
255
  ## Task List
@@ -1,5 +1,6 @@
1
1
  # Implementation Plan: MCP Server — Boost Parity (Docs Search, Local Runtime Tools, Per-Package Guidelines, Upgrade Prompts)
2
2
 
3
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1085
3
4
  *When an engineer is assigned to a project but before you begin coding, you should fill in the implementation plan and get feedback from the engineering team. Once you have finished or you make any changes, tag Josh with the @ symbol so he can review. Also tag anyone else that needs to be notified, has conflicting work, etc.*
4
5
 
5
6
  ## Overview
@@ -1,6 +1,7 @@
1
1
  # Research: Migrate CI/CD to CircleCI
2
2
 
3
3
  **Status:** Research complete — decisions recorded in IP `migrate-cicd-to-circleci.md`
4
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1088
4
5
  **Date:** 2026-08-17
5
6
  **Repo:** FlourishHealth/terreno (`master` @ `1e3e79a6`)
6
7
  **Branch:** `cursor/migrate-cicd-circleci-2b6d`
@@ -1,8 +1,8 @@
1
1
  # Implementation Plan: Migrate CI/CD to CircleCI
2
2
 
3
- **Status:** Draft — decisions recorded; awaiting Approval
3
+ **Status:** In progress — Phase 1–3 CI config landed (deploys deferred per request)
4
4
  **Discussion:** _(none)_
5
- **Roadmap issue:** _(pending — hand off via `roadmap-item` when Approved)_
5
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1088
6
6
  **Linear:** _(none)_
7
7
  **Priority:** High
8
8
  **Effort:** Large (cross-cutting infra; many workflows)
@@ -1,5 +1,9 @@
1
1
  # Implementation Plan: modelRouter Actions
2
2
 
3
+ **Status:** Deferred
4
+ **Closed:** 2026-08-20 — Not pursued; custom `endpoints` callbacks remain the supported pattern for non-CRUD routes.
5
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1097
6
+
3
7
  ## Goal
4
8
 
5
9
  Add first-class support for **actions** in `modelRouter` — URLs that perform a named operation rather than CRUD. Actions live either at `/resource/action` (collection scope) or `/resource/:id/action` (instance scope). They inherit modelRouter's permissions system, are wrapped in `asyncHandler` + the standard error envelope automatically, and appear in the generated OpenAPI spec without extra wiring.
@@ -1,5 +1,6 @@
1
1
  # Implementation Plan: modelRouter MCP Tools
2
2
 
3
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1092
3
4
  *When an engineer is assigned to a project but before you begin coding, you should fill in the implementation plan and get feedback from the engineering team. Once you have finished or you make any changes, tag Josh with the @ symbol so he can review. Also tag anyone else that needs to be notified, has conflicting work, etc.*
4
5
 
5
6
  ## Overview
@@ -17,7 +18,8 @@ This makes every Terreno app instantly AI-native: any model becomes callable by
17
18
  - Auth-agnostic: works with both JWT and Better Auth
18
19
  - Tool naming: `{prefix}_list`, `{prefix}_read`, `{prefix}_create`, `{prefix}_update`, `{prefix}_delete`
19
20
  - Populate is an option on the read tool, not a separate tool
20
- - Also exports `getMCPTools()` returning Vercel `ai` SDK `CoreTool` objects for direct in-process use
21
+ - Also exports `getMCPTools()` from `@terreno/ai` returning Vercel `ai` SDK `CoreTool` objects for direct in-process use
22
+ - Custom tools via `registerMCPTool` (example: `users_todo_statuses`)
21
23
 
22
24
  **Related work:** The `ai-multimodal-tools-mcp` branch has `MCPService` (MCP client) and `AIService` in `@terreno/ai` — that's the consumer of what this plan builds.
23
25
 
@@ -112,7 +114,7 @@ Generated from model name, field names/types, and `queryFields`. Example:
112
114
 
113
115
  ### CoreTool Export
114
116
 
115
- `getMCPTools()` exported from `@terreno/api` returns all registered MCP tools as `Record<string, CoreTool>` for direct use with the Vercel `ai` SDK's `streamText()` / `generateText()`.
117
+ `getMCPTools()` exported from `@terreno/ai` returns all registered MCP tools as `Record<string, CoreTool>` for direct use with the Vercel `ai` SDK's `streamText()` / `generateText()`.
116
118
 
117
119
  ## Notifications
118
120
 
@@ -175,8 +177,7 @@ No activity logging specific to this feature. MCP tool calls go through the same
175
177
  ## Not Included / Future Work
176
178
 
177
179
  - **Rate limiting** — structured for future hooks but not implemented
178
- - **MCP resources/prompts** — only tools generated from modelRouter
179
- - **Custom MCP tools** — use modelRouter's `endpoints` for custom routes; MCP-specific custom tools deferred
180
+ - **MCP resources/prompts** — only tools generated from modelRouter plus `registerMCPTool`
180
181
  - **Array operation tools** — handled via update, no dedicated push/patch/delete array tools
181
182
  - **WebSocket transport** — HTTP/SSE only
182
183
  - **Admin dashboard** — use `getMCPTools()` programmatically or MCP client tool listing
@@ -1,6 +1,8 @@
1
1
  # Implementation Plan: Offline Mode
2
2
 
3
- **Status:** Approved for implementation tasking
3
+ **Status:** Deferred
4
+ **Closed:** 2026-08-20 — Superseded by [@terreno/syncdb](syncdb-local-first.md) local-first outbox and offline-capable sync.
5
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1096
4
6
  **Priority:** High
5
7
  **Effort:** Medium
6
8