@terreno/mcp 57.3.0 → 57.4.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 (250) hide show
  1. package/README.md +7 -1
  2. package/dist/bootstrap.d.ts +0 -18
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +10 -2428
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
  7. package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
  8. package/dist/docs/resources/syncdb.md +4 -1
  9. package/dist/docs/upgrades/57.4.0.md +140 -0
  10. package/dist/docs/versioned/next/README.md +3 -0
  11. package/dist/docs/versioned/next/explanation/README.md +4 -1
  12. package/dist/docs/versioned/next/explanation/admin-interface.md +20 -13
  13. package/dist/docs/versioned/next/explanation/charts.md +29 -0
  14. package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
  15. package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  16. package/dist/docs/versioned/next/explanation/example-coverage.md +43 -0
  17. package/dist/docs/versioned/next/explanation/local-first-data.md +5 -2
  18. package/dist/docs/versioned/next/explanation/loop-engineering.md +9 -6
  19. package/dist/docs/versioned/next/explanation/model-router-actions.md +19 -2
  20. package/dist/docs/versioned/next/explanation/organizations.md +88 -0
  21. package/dist/docs/versioned/next/explanation/planning-plugins.md +114 -0
  22. package/dist/docs/versioned/next/explanation/positioning.md +3 -3
  23. package/dist/docs/versioned/next/explanation/repository-settings.md +6 -5
  24. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
  25. package/dist/docs/versioned/next/explanation/source-rules.md +1 -1
  26. package/dist/docs/versioned/next/explanation/static-analysis.md +76 -16
  27. package/dist/docs/versioned/next/how-to/README.md +10 -0
  28. package/dist/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
  29. package/dist/docs/versioned/next/how-to/add-organizations.md +204 -0
  30. package/dist/docs/versioned/next/how-to/admin-add-model.md +9 -1
  31. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
  32. package/dist/docs/versioned/next/how-to/audit-log.md +85 -0
  33. package/dist/docs/versioned/next/how-to/background-jobs.md +296 -0
  34. package/dist/docs/versioned/next/how-to/build-admin-screens.md +42 -13
  35. package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
  36. package/dist/docs/versioned/next/how-to/circleci.md +136 -27
  37. package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
  38. package/dist/docs/versioned/next/how-to/configure-better-auth.md +4 -0
  39. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
  40. package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
  41. package/dist/docs/versioned/next/how-to/github-actions-ci.md +4 -3
  42. package/dist/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
  43. package/dist/docs/versioned/next/how-to/in-app-notifications.md +147 -0
  44. package/dist/docs/versioned/next/how-to/install-agent-skills.md +24 -12
  45. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
  46. package/dist/docs/versioned/next/how-to/product-announcements.md +155 -0
  47. package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
  48. package/dist/docs/versioned/next/how-to/run-tests-locally.md +102 -0
  49. package/dist/docs/versioned/next/how-to/seed-a-database.md +7 -2
  50. package/dist/docs/versioned/next/implementationPlans/README.md +8 -5
  51. package/dist/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
  52. package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
  53. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
  54. package/dist/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
  55. package/dist/docs/versioned/next/implementationPlans/announcements.md +289 -0
  56. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
  57. package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
  58. package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
  59. package/dist/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
  60. package/dist/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
  61. package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
  62. package/dist/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
  63. package/dist/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
  64. package/dist/docs/versioned/next/implementationPlans/job-queues.md +330 -0
  65. package/dist/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
  66. package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
  67. package/dist/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
  68. package/dist/docs/versioned/next/implementationPlans/notification-center.md +185 -0
  69. package/dist/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
  70. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  71. package/dist/docs/versioned/next/reference/README.md +4 -0
  72. package/dist/docs/versioned/next/reference/admin-backend.md +83 -5
  73. package/dist/docs/versioned/next/reference/admin-config.md +7 -0
  74. package/dist/docs/versioned/next/reference/admin-frontend.md +178 -5
  75. package/dist/docs/versioned/next/reference/admin-spa.md +18 -1
  76. package/dist/docs/versioned/next/reference/ai.md +2 -0
  77. package/dist/docs/versioned/next/reference/announcements.md +179 -0
  78. package/dist/docs/versioned/next/reference/api.md +343 -4
  79. package/dist/docs/versioned/next/reference/comms.md +33 -0
  80. package/dist/docs/versioned/next/reference/create-terreno-app.md +44 -0
  81. package/dist/docs/versioned/next/reference/environment-variables.md +29 -0
  82. package/dist/docs/versioned/next/reference/jobs.md +319 -0
  83. package/dist/docs/versioned/next/reference/legacy/rtk.md +5 -1
  84. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
  85. package/dist/docs/versioned/next/reference/mcp-server.md +2 -43
  86. package/dist/docs/versioned/next/reference/scan-plugin.md +169 -0
  87. package/dist/docs/versioned/next/reference/syncdb.md +38 -12
  88. package/dist/docs/versioned/next/reference/test.md +57 -6
  89. package/dist/docs/versioned/next/reference/ui-performance.md +2 -1
  90. package/dist/docs/versioned/next/reference/ui.md +257 -3
  91. package/dist/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
  92. package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
  93. package/dist/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
  94. package/dist/docs/versioned/next/tasks/announcements.md +173 -0
  95. package/dist/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
  96. package/dist/docs/versioned/next/tasks/create-terreno-app.md +82 -0
  97. package/dist/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
  98. package/dist/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
  99. package/dist/docs/versioned/next/tasks/framework-audit-log.md +86 -0
  100. package/dist/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
  101. package/dist/docs/versioned/next/tasks/job-queues.md +144 -0
  102. package/dist/docs/versioned/next/tasks/knip-cleanup.md +146 -0
  103. package/dist/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
  104. package/dist/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
  105. package/dist/docs/versioned/next/tasks/notification-center.md +102 -0
  106. package/dist/docs/versioned/next/tasks/org-management-ui.md +14 -14
  107. package/dist/docs/versioned/next/tutorials/README.md +1 -1
  108. package/dist/docs/versioned/next/tutorials/getting-started.md +62 -22
  109. package/dist/help/excerpt.d.ts +2 -0
  110. package/dist/help/excerpt.d.ts.map +1 -0
  111. package/dist/help/excerpt.js +8 -0
  112. package/dist/help/excerpt.js.map +1 -0
  113. package/dist/help/updateNotes.d.ts +42 -0
  114. package/dist/help/updateNotes.d.ts.map +1 -0
  115. package/dist/help/updateNotes.js +235 -0
  116. package/dist/help/updateNotes.js.map +1 -0
  117. package/dist/index.js +1 -1
  118. package/dist/index.js.map +1 -1
  119. package/dist/local/tools/databaseQuery.d.ts +1 -0
  120. package/dist/local/tools/databaseQuery.d.ts.map +1 -1
  121. package/dist/local/tools/databaseQuery.js +1 -0
  122. package/dist/local/tools/databaseQuery.js.map +1 -1
  123. package/dist/packageGuidelines.d.ts +2 -2
  124. package/dist/packageGuidelines.d.ts.map +1 -1
  125. package/dist/packageGuidelines.js +2 -8
  126. package/dist/packageGuidelines.js.map +1 -1
  127. package/dist/resources.d.ts +3 -0
  128. package/dist/resources.d.ts.map +1 -1
  129. package/dist/resources.js +3 -0
  130. package/dist/resources.js.map +1 -1
  131. package/dist/search/chunker.d.ts +1 -0
  132. package/dist/search/chunker.d.ts.map +1 -1
  133. package/dist/search/chunker.js +1 -0
  134. package/dist/search/chunker.js.map +1 -1
  135. package/dist/search/docIndex.d.ts +1 -5
  136. package/dist/search/docIndex.d.ts.map +1 -1
  137. package/dist/search/docIndex.js +1 -0
  138. package/dist/search/docIndex.js.map +1 -1
  139. package/dist/search/docVersion.d.ts +2 -1
  140. package/dist/search/docVersion.d.ts.map +1 -1
  141. package/dist/search/docVersion.js +3 -1
  142. package/dist/search/docVersion.js.map +1 -1
  143. package/dist/tools.d.ts +2 -2
  144. package/dist/tools.d.ts.map +1 -1
  145. package/dist/tools.js +113 -2
  146. package/dist/tools.js.map +1 -1
  147. package/package.json +28 -27
  148. package/src/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
  149. package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
  150. package/src/docs/resources/syncdb.md +4 -1
  151. package/src/docs/upgrades/57.4.0.md +140 -0
  152. package/src/docs/versioned/next/README.md +3 -0
  153. package/src/docs/versioned/next/explanation/README.md +4 -1
  154. package/src/docs/versioned/next/explanation/admin-interface.md +20 -13
  155. package/src/docs/versioned/next/explanation/charts.md +29 -0
  156. package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
  157. package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  158. package/src/docs/versioned/next/explanation/example-coverage.md +43 -0
  159. package/src/docs/versioned/next/explanation/local-first-data.md +5 -2
  160. package/src/docs/versioned/next/explanation/loop-engineering.md +9 -6
  161. package/src/docs/versioned/next/explanation/model-router-actions.md +19 -2
  162. package/src/docs/versioned/next/explanation/organizations.md +88 -0
  163. package/src/docs/versioned/next/explanation/planning-plugins.md +114 -0
  164. package/src/docs/versioned/next/explanation/positioning.md +3 -3
  165. package/src/docs/versioned/next/explanation/repository-settings.md +6 -5
  166. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
  167. package/src/docs/versioned/next/explanation/source-rules.md +1 -1
  168. package/src/docs/versioned/next/explanation/static-analysis.md +76 -16
  169. package/src/docs/versioned/next/how-to/README.md +10 -0
  170. package/src/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
  171. package/src/docs/versioned/next/how-to/add-organizations.md +204 -0
  172. package/src/docs/versioned/next/how-to/admin-add-model.md +9 -1
  173. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
  174. package/src/docs/versioned/next/how-to/audit-log.md +85 -0
  175. package/src/docs/versioned/next/how-to/background-jobs.md +296 -0
  176. package/src/docs/versioned/next/how-to/build-admin-screens.md +42 -13
  177. package/src/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
  178. package/src/docs/versioned/next/how-to/circleci.md +136 -27
  179. package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
  180. package/src/docs/versioned/next/how-to/configure-better-auth.md +4 -0
  181. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
  182. package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
  183. package/src/docs/versioned/next/how-to/github-actions-ci.md +4 -3
  184. package/src/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
  185. package/src/docs/versioned/next/how-to/in-app-notifications.md +147 -0
  186. package/src/docs/versioned/next/how-to/install-agent-skills.md +24 -12
  187. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
  188. package/src/docs/versioned/next/how-to/product-announcements.md +155 -0
  189. package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
  190. package/src/docs/versioned/next/how-to/run-tests-locally.md +102 -0
  191. package/src/docs/versioned/next/how-to/seed-a-database.md +7 -2
  192. package/src/docs/versioned/next/implementationPlans/README.md +8 -5
  193. package/src/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
  194. package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
  195. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
  196. package/src/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
  197. package/src/docs/versioned/next/implementationPlans/announcements.md +289 -0
  198. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
  199. package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
  200. package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
  201. package/src/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
  202. package/src/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
  203. package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
  204. package/src/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
  205. package/src/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
  206. package/src/docs/versioned/next/implementationPlans/job-queues.md +330 -0
  207. package/src/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
  208. package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
  209. package/src/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
  210. package/src/docs/versioned/next/implementationPlans/notification-center.md +185 -0
  211. package/src/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
  212. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  213. package/src/docs/versioned/next/reference/README.md +4 -0
  214. package/src/docs/versioned/next/reference/admin-backend.md +83 -5
  215. package/src/docs/versioned/next/reference/admin-config.md +7 -0
  216. package/src/docs/versioned/next/reference/admin-frontend.md +178 -5
  217. package/src/docs/versioned/next/reference/admin-spa.md +18 -1
  218. package/src/docs/versioned/next/reference/ai.md +2 -0
  219. package/src/docs/versioned/next/reference/announcements.md +179 -0
  220. package/src/docs/versioned/next/reference/api.md +343 -4
  221. package/src/docs/versioned/next/reference/comms.md +33 -0
  222. package/src/docs/versioned/next/reference/create-terreno-app.md +44 -0
  223. package/src/docs/versioned/next/reference/environment-variables.md +29 -0
  224. package/src/docs/versioned/next/reference/jobs.md +319 -0
  225. package/src/docs/versioned/next/reference/legacy/rtk.md +5 -1
  226. package/src/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
  227. package/src/docs/versioned/next/reference/mcp-server.md +2 -43
  228. package/src/docs/versioned/next/reference/scan-plugin.md +169 -0
  229. package/src/docs/versioned/next/reference/syncdb.md +38 -12
  230. package/src/docs/versioned/next/reference/test.md +57 -6
  231. package/src/docs/versioned/next/reference/ui-performance.md +2 -1
  232. package/src/docs/versioned/next/reference/ui.md +257 -3
  233. package/src/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
  234. package/src/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
  235. package/src/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
  236. package/src/docs/versioned/next/tasks/announcements.md +173 -0
  237. package/src/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
  238. package/src/docs/versioned/next/tasks/create-terreno-app.md +82 -0
  239. package/src/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
  240. package/src/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
  241. package/src/docs/versioned/next/tasks/framework-audit-log.md +86 -0
  242. package/src/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
  243. package/src/docs/versioned/next/tasks/job-queues.md +144 -0
  244. package/src/docs/versioned/next/tasks/knip-cleanup.md +146 -0
  245. package/src/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
  246. package/src/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
  247. package/src/docs/versioned/next/tasks/notification-center.md +102 -0
  248. package/src/docs/versioned/next/tasks/org-management-ui.md +14 -14
  249. package/src/docs/versioned/next/tutorials/README.md +1 -1
  250. package/src/docs/versioned/next/tutorials/getting-started.md +62 -22
@@ -9,24 +9,32 @@ REST API framework built on Express and Mongoose. Provides modelRouter (CRUD end
9
9
  - [Authentication](#authentication)
10
10
  - [Model Schema Conventions](#model-schema-conventions)
11
11
  - [Mongoose Plugins](#mongoose-plugins)
12
+ - [Organizations](#organizations)
12
13
  - [Request Validation](#request-validation)
13
14
  - [Middleware](#middleware)
14
15
  - [Logging & Tracing](#logging--tracing)
15
16
  - [Extensibility](#extensibility)
17
+ - [In-app notifications](#in-app-notifications)
16
18
  - [Webhooks & Notifications](#webhooks--notifications)
17
19
  - [HTTP Client](#http-client)
18
20
  - [Utilities](#utilities)
19
21
  - [Script Helpers](#script-helpers)
22
+ - [Migrations](#migrations)
20
23
 
21
24
  ## Key exports
22
25
 
23
26
  - `TerrenoApp`, `setupServer`, `modelRouter`, `Permissions`, `OwnerQueryFilter`
27
+ - Organizations: `Organization`, `Membership`, `OrgsApp`, `orgScopedPlugin`,
28
+ `orgContextMiddleware`, `getOrgContext`, `OrgQueryFilter`,
29
+ `Permissions.IsOrganizationMember`, `organizationSlugFromName`
30
+ - `AuditApp`, `createAuditEventModel`, `persistRbacAuditToAuditEvent`
24
31
  - `registerMCPTool`, `getMCPRegistry`
25
32
  - `APIError`, `logger`, `asyncHandler`, `authenticateMiddleware`
26
33
  - Logging: `logger`, `createScopedLogger`, `createFeatureFlaggedLogger`, `setupLogging`, `formatLogContextSuffix`
27
34
  - Correlation: `runWithRequestContext`, `getCurrentLogContext`, `requestContextMiddleware`, `REQUEST_CONTEXT_ATTRIBUTE_NAMES`
28
35
  - `createOpenApiBuilder`
29
36
  - Seeds: `runSeeds`, `runSeedCli`, `seedBetterAuthUser`
37
+ - Migrations: `assertMigrationsAllowed`, `runMigrations`, `runDownMigrations`, `checkMigrationFiles`, `resolveMigrationDir`, `exerciseReversibleMigrations`, `MIGRATIONS_COLLECTION` (`terreno-migrate` bin)
30
38
  - `githubUserPlugin`, `setupGitHubAuth`, `addGitHubAuthRoutes`
31
39
  - `AuthToken`, `AUTH_TOKEN_TTL` (hashed single-use password-reset / email-verification tokens)
32
40
  - Mongoose plugins: `findExactlyOne`, `findOneOrNone`, `upsertPlugin`, `DateOnly`, `emailVerificationPlugin`
@@ -77,7 +85,16 @@ const app = new TerrenoApp({userModel: User})
77
85
  - `register(registration)` — Register `ModelRouterRegistration` or `TerrenoPlugin`
78
86
  - `addMiddleware(fn)` — Add Express middleware
79
87
  - `build()` — Build Express app without listening
80
- - `start()` — Build and start server
88
+ `start()` — Build and start server. Pass `httpServer` to attach to an already-listening `http.Server` instead of binding a new port (Cloud Run: bind `PORT` before MongoDB connect).
89
+
90
+ `migrations.runOnStart` defaults to **false**. When `true`, `start()` binds listen first, then runs `ensureSyncIndexes`, then wet `up`. Production still requires `ALLOW_MIGRATIONS=true` (boot counts as `--force`). Omitted `migrations` never reads `terreno_migrations`.
91
+
92
+ ```typescript
93
+ new TerrenoApp({
94
+ userModel: User,
95
+ migrations: {dir: "./migrations", runOnStart: true},
96
+ });
97
+ ```
81
98
 
82
99
  ### HTTP rate limiting
83
100
 
@@ -116,6 +133,41 @@ new TerrenoApp({
116
133
 
117
134
  `mcpServiceTokens: true` is `{enabled: true}`. When enabled, TerrenoApp mounts the self-serve routes (passing its OpenAPI bundle) and sets `mcpServiceTokens` on MCP auth. Operator steps: [Connect an MCP client with a service token](../how-to/connect-mcp-service-token.md).
118
135
 
136
+ ### Audit log (`AuditApp`)
137
+
138
+ Opt-in append-only log. Register the plugin; importing `@terreno/api` does **not** compile `AuditEvent` onto the default mongoose connection.
139
+
140
+ ```typescript
141
+ import {
142
+ AuditApp,
143
+ persistRbacAuditToAuditEvent,
144
+ TerrenoApp,
145
+ modelRouter,
146
+ } from "@terreno/api";
147
+
148
+ new TerrenoApp({userModel: User})
149
+ .register(new AuditApp()) // optional {retentionDays: 90}
150
+ .register(
151
+ modelRouter("/todos", Todo, {
152
+ audit: true, // or {redact: ["ssn"]}
153
+ permissions: {/* ... */},
154
+ })
155
+ )
156
+ .start();
157
+ ```
158
+
159
+ | Surface | How it writes |
160
+ | --- | --- |
161
+ | `modelRouter` | `audit: true` or `{redact?: string[]}` after successful HTTP create/update/delete and array push/update/remove |
162
+ | `AdminApp` | Auto when `AuditApp` is registered (`source: "admin"`). `onAdminAudit` is extra |
163
+ | RBAC | `createAccess({auditSink: persistRbacAuditToAuditEvent})` (`source: "rbac"`) |
164
+
165
+ HTTP is list+read only: `GET /audit-events` with `Permissions.IsAdmin`. Empty create/update/delete permission arrays mean POST/PATCH/DELETE return **405**, including `/admin/audit-events` (`admin.adminPermissions`). Non-admin list is also **405** (`permissionMiddleware`). There is no `isDeletedPlugin`; rows are not soft-deleted.
166
+
167
+ `createAuditEventModel(connection, {retentionDays?})` is the factory for tests and scripts. Never audit `AuditEvent` itself. Diffs run on the request; persist is fire-and-forget (the HTTP handler does not await Mongo). Recorder failures (including serialization) log and leave the mutation 2xx. Secret field names (`password`, `hash`, `salt`, `token`, `secret`, `refreshToken`) are stripped at every object and array depth, including compound keys such as `tokenHash`. AdminApp diffs also omit `hiddenFields` and `excludeFields`.
168
+
169
+ When `GCP_TASKS_AUDIT_QUEUE`, `AUDIT_TASKS_URL`, `GCP_PROJECT`, `GCP_LOCATION`, and `AUDIT_TASKS_SECRET` are set, `AuditApp` enqueues the write with Cloud Tasks (`@google-cloud/tasks` optional install) instead of writing Mongo in-process, and mounts `POST /internal/audit-events` (header `X-Terreno-Audit-Secret`) so the worker persists. Pass `enqueue` yourself to use any other queue. Default retention is forever (no TTL index). `new AuditApp({retentionDays: n})` for `n > 0` replaces the plugin `{created: 1}` index with `{created: 1, expireAfterSeconds: n * 86400}`. Drop that index yourself if you later remove TTL. Operator steps: [Enable the framework audit log](../how-to/audit-log.md).
170
+
119
171
  ### setupServer (Legacy)
120
172
 
121
173
  Callback-based pattern:
@@ -137,6 +189,40 @@ Both patterns create the same middleware stack (CORS, auth, logging, OpenAPI). H
137
189
 
138
190
  `modelRouter("/path", Model, options)` writes one catalog record per route path. MCP, realtime, and sync surfaces read that record; TerrenoApp calls `replaceCollectionOptions` once when access control is injected. Test helpers `clearMCPRegistry`, `clearRealtimeRegistry`, and `clearSyncRegistry` clear the entire catalog.
139
191
 
192
+ ## modelRouter actions
193
+
194
+ Named GET/POST operations on a collection or document. Use these instead of `app.get` /
195
+ `app.post` / `router.get` / `router.post` for application APIs.
196
+
197
+ See [modelRouter actions](../explanation/model-router-actions.md). Example:
198
+
199
+ ```typescript
200
+ export const todoRouter = modelRouter("/todos", Todo, {
201
+ collectionActions: {
202
+ bulkComplete: {
203
+ method: "POST",
204
+ permissions: [Permissions.IsAuthenticated],
205
+ body: z.object({ids: z.array(z.string()).min(1)}).strict(),
206
+ handler: async ({body, user}) => {
207
+ return {matched: 0, modified: 0};
208
+ },
209
+ },
210
+ },
211
+ instanceActions: {
212
+ markComplete: {
213
+ method: "POST",
214
+ permissions: [Permissions.IsOwner],
215
+ handler: async ({doc}) => {
216
+ return doc;
217
+ },
218
+ },
219
+ },
220
+ permissions: { /* CRUD */ },
221
+ });
222
+ ```
223
+
224
+ Do not add `endpoints: (router) => { router.get(...) }` when an action fits.
225
+
140
226
  ## MCP tools
141
227
 
142
228
  Opt a model into Model Context Protocol tools with `mcp` on `modelRouter`. `TerrenoApp` mounts `POST /mcp` when any model has `mcp` or a custom tool is registered.
@@ -322,6 +408,12 @@ Better Auth password and deletes those sessions when `BetterAuthApp` is register
322
408
  Optional `authMailTemplates` overrides
323
409
  subject/text/html per template id.
324
410
 
411
+ In-process seed helpers (for example `seedBetterAuthUserInProcess`) should pass
412
+ `disableRateLimit: true` on that throwaway Better Auth instance. Better Auth
413
+ turns its built-in limiter on when `NODE_ENV=production`, so seeding six demo
414
+ users during a preview smoke test otherwise 429s on the fourth signup. Do not
415
+ set this on the public `BetterAuthApp`.
416
+
325
417
  **Endpoints (when enabled):**
326
418
  - `POST /api/auth/signup/email` — Email/password signup
327
419
  - `POST /api/auth/signin/email` — Email/password signin
@@ -387,6 +479,20 @@ Field descriptions appear in:
387
479
 
388
480
  @terreno/api provides several Mongoose plugins to extend model functionality with common patterns.
389
481
 
482
+ ### orgScopedPlugin
483
+
484
+ Adds a required indexed `organizationId` (ref `Organization`) to consumer schemas. Save fails
485
+ without an organization. Use this on tenant-scoped models; query scoping lands with org context.
486
+ After creation, `organizationId` is immutable — REST PATCH, admin writes, sync updates, and
487
+ direct `doc.save()` / `updateOne()` attempts to retarget another organization return HTTP 400
488
+ with title `organizationId cannot be changed`.
489
+
490
+ ``````typescript
491
+ import {orgScopedPlugin} from "@terreno/api";
492
+
493
+ projectSchema.plugin(orgScopedPlugin);
494
+ ``````
495
+
390
496
  ### findExactlyOne & findOneOrNone
391
497
 
392
498
  **Critical:** Never use `Model.findOne` directly. Use these plugins instead to prevent ambiguous queries.
@@ -574,6 +680,138 @@ export const addDefaultPlugins = (schema) => {
574
680
  todoSchema.plugin(addDefaultPlugins);
575
681
  ``````
576
682
 
683
+ ## Organizations
684
+
685
+ Organizations are **opt-in**. Existing and single-tenant apps omit them: do not pass
686
+ `organizations: true` to `createAccess` or `TerrenoApp`, and do not register `OrgsApp`.
687
+ New apps from `create-terreno-app` enable organizations by default.
688
+
689
+ | App kind | What to do |
690
+ | --- | --- |
691
+ | Existing / single-tenant | Leave `createAccess` and `TerrenoApp` without `organizations`. No `operator` seed, no membership grants, no `/orgs` routes. |
692
+ | New app (bootstrap) | Generated `backend/src/access.ts` uses `createAccess({ organizations: true })`. `TerrenoApp({ organizations: true, accessControl: access })` mounts `OrgsApp`. Seed creates a default org and makes `admin@example.com` an `operator` and `org-admin`. |
693
+ | Existing app adopting orgs | `createAccess({ organizations: true })`, `await access.roles.seedDefaults()`, and `TerrenoApp({ organizations: true, accessControl })` or `.register(new OrgsApp({ access, userModel }))`. |
694
+
695
+ `Organization` and `Membership` models register on first use, not when you import `@terreno/api`.
696
+
697
+ Native `Organization` and `Membership` models live in `@terreno/api`.
698
+
699
+ | Model | Fields |
700
+ | --- | --- |
701
+ | `Organization` | `name` (required), `slug` (unique, generated from name), `ownerId`, `settings` (app-defined; Mixed until you register a nested schema), `disabled` |
702
+ | `Membership` | `organizationId`, `userId`, `roleName` (`org-admin` \| `member`, default `member`), `status` (`active` \| `suspended`) |
703
+
704
+ Compound unique index: `(organizationId, userId)`. Duplicate memberships throw a Mongo duplicate-key error.
705
+
706
+ `Membership` statics: `findActiveForUser`, `isOrgAdmin`, `isMember` (active rows only). Per-org
707
+ `org-admin` is stored on Membership, not on `user.roles`.
708
+
709
+ ### Organization.settings
710
+
711
+ Do not add top-level fields to the native `Organization` model. Extend `settings`:
712
+
713
+ | Helper | Role |
714
+ | --- | --- |
715
+ | `createOrganizationSettingsSchema(definition)` | Nested schema with `_id: false` and `strict: "throw"` |
716
+ | `registerOrganizationSettings(schema)` | Validate settings on create/save/PATCH. Call with no argument to clear |
717
+ | `organizationSettingsOf(organization)` | Type-safe read; defaults to `{}` |
718
+ | `OrganizationSettings` | Empty interface for `declare module "@terreno/api"` merging |
719
+
720
+ Pass `settingsSchema` on `TerrenoApp({ organizations: { settingsSchema } })` or `new OrgsApp({ settingsSchema })`. Keep settings fields optional or defaulted so existing documents still save. See [Add organizations](../how-to/add-organizations.md#3-type-organization-settings).
721
+
722
+ ### OrgsApp routes
723
+
724
+ | Method | Path | Required access |
725
+ | --- | --- | --- |
726
+ | `POST` | `/orgs` | `organization:create` |
727
+ | `GET` | `/orgs` | `organization:list` |
728
+ | `GET` | `/orgs/mine` | Active org-admin membership, operator, or superadmin (disabled orgs omitted) |
729
+ | `GET` | `/orgs/:id` | `organization:read` in that org |
730
+ | `PATCH` | `/orgs/:id` | `organization:update`; disabling also requires `organization:disable` |
731
+ | `DELETE` | `/orgs/:id` | `organization:delete` |
732
+ | `GET` / `POST` | `/orgs/:id/members` | `organization:manageMembers` |
733
+ | `PATCH` / `DELETE` | `/orgs/:id/members/:memberId` | `organization:manageMembers`; cannot remove or demote the last org-admin |
734
+
735
+ Creating an organization does not create a membership automatically. Member
736
+ attach accepts an existing `userId` or email (case-insensitive); it does not send
737
+ an invitation. Disabling or deleting an organization suspends its memberships.
738
+ Member attach, update, and remove routes reject disabled organizations with 403.
739
+ Operators may still `GET` and `PATCH /orgs/:id` on a disabled organization to
740
+ re-enable it; `GET /orgs` continues to list disabled organizations.
741
+
742
+ ### Request organization context
743
+
744
+ Tenant-scoped routes run `orgContextMiddleware({required: true})` after auth, then
745
+ `queryFilter: OrgQueryFilter`.
746
+
747
+ | Condition | Result |
748
+ | --- | --- |
749
+ | `X-Organization-Id` present, organization disabled | 403 `Organization is disabled` |
750
+ | `X-Organization-Id` present, caller is `operator`/`superadmin` or an active member, org enabled | `req.organization` set |
751
+ | `X-Organization-Id` present, caller is not a member and not a platform org actor | 403 |
752
+ | Tenant-scoped route, `operator`/`superadmin`, header omitted | 400 |
753
+ | Caller is `org-admin` of exactly one enabled org, header omitted | that org is inferred |
754
+ | Caller is `org-admin` of many orgs, header omitted | 400 |
755
+ | Otherwise on tenant-scoped routes | 403 |
756
+
757
+ `OrgQueryFilter` always ANDs `{organizationId: context.id}`. Client `organizationId` query params
758
+ and `$or` cannot list another org.
759
+
760
+ `user.admin` is not operator. Platform org actors are `user.roles` containing `operator` or
761
+ `superadmin`.
762
+
763
+ Use `Permissions.IsOrganizationMember` on read, update, and delete methods for
764
+ tenant models. It requires the object's `organizationId` to match the active
765
+ request organization context (AsyncLocalStorage from `orgContextMiddleware`, and
766
+ from `X-Organization-Id` on `POST /sync/mutate`) for every caller. Platform actors (`operator` / `superadmin`) then pass; members
767
+ must also hold an active Membership in that organization. Direct `GET` / `PATCH`
768
+ by document id does not apply `OrgQueryFilter`, so this object-level check is
769
+ what blocks cross-tenant reads and writes. `getOrgContext()` exposes the
770
+ resolved organization to create hooks so they can overwrite client-provided
771
+ organization ids. Duplicate organization names that generate the same slug,
772
+ including slugs still held by soft-deleted organizations, return **409**
773
+ `Organization name already in use` on `POST /orgs` and on rename
774
+ via `PATCH /orgs/:id`. See [Add organizations](../how-to/add-organizations.md)
775
+ for complete route wiring.
776
+
777
+ ### Organization RBAC
778
+
779
+ `terrenoStatements.organization` actions: `create`, `list`, `read`, `update`, `delete`,
780
+ `manageMembers`, `disable`.
781
+
782
+ | Role | Where it lives | Organization grants |
783
+ | --- | --- | --- |
784
+ | `superadmin` | `user.roles` | `*` (includes every organization action) |
785
+ | `operator` | seeded locked `user.roles` when `createAccess({ organizations: true })` | all `organization` actions, plus `admin:access` and `user:list\|read\|update` |
786
+ | `org-admin` | `Membership.roleName` in the current org context | `organization:read\|update\|manageMembers` and `admin:access` |
787
+ | `admin` | `user.roles` | none of `organization:*` |
788
+
789
+ `createAccess({ organizations: true })` prepends a membership permission source (`ttlMs: 0`) and
790
+ seeds the locked `operator` role. Without that flag, membership `org-admin` grants nothing extra
791
+ and `operator` is not seeded. Putting `org-admin` on `user.roles` does not grant those permissions.
792
+ Permission cache keys include the current organization id and membership role so org-admin grants
793
+ do not leak across orgs.
794
+
795
+ ``````typescript
796
+ import {createAccess, Membership, Organization, terrenoStatements} from "@terreno/api";
797
+
798
+ const access = createAccess({
799
+ connection: mongoose.connection,
800
+ organizations: true,
801
+ statements: terrenoStatements,
802
+ userModel: User,
803
+ });
804
+
805
+ const org = await Organization.create({name: "Acme Corp", ownerId: user._id});
806
+ // org.slug === "acme-corp"
807
+
808
+ await Membership.create({
809
+ organizationId: org._id,
810
+ roleName: "org-admin",
811
+ userId: user._id,
812
+ });
813
+ ``````
814
+
577
815
  ## Request Validation
578
816
 
579
817
  @terreno/api provides runtime validation of incoming requests against OpenAPI schemas using [AJV](https://ajv.js.org/). Validation is opt-in and can be enabled globally or per-route.
@@ -617,7 +855,9 @@ setupServer({
617
855
 
618
856
  ### Using with modelRouter
619
857
 
620
- When validation is enabled globally, modelRouter automatically validates create and update requests:
858
+ When validation is enabled globally, modelRouter automatically validates create and update requests.
859
+
860
+ Create (POST) enforces Mongoose required fields. Update (PATCH) is partial: present fields are type-checked, but omitted required fields are allowed so a client can send `{completed: true}` without repeating `title`.
621
861
 
622
862
  ``````typescript
623
863
  import {modelRouter, Permissions} from "@terreno/api";
@@ -1121,6 +1361,79 @@ setupServer({
1121
1361
  - Optional/configurable functionality
1122
1362
  - Clean separation of concerns
1123
1363
 
1364
+ ## In-app notifications
1365
+
1366
+ Owner-scoped inbox and channel preferences via `NotificationsApp` (`ConsentApp` pattern).
1367
+ This is separate from inbound webhooks and outbound Slack/Chat/Zoom notifiers below.
1368
+
1369
+ ### Register
1370
+
1371
+ ```typescript
1372
+ import {NotificationsApp, getNotificationService, notificationsBeforeSend} from "@terreno/api";
1373
+
1374
+ new TerrenoApp({userModel: User}).register(
1375
+ new NotificationsApp({
1376
+ getComms: getCommsService, // optional; duck-typed, no @terreno/comms import in api
1377
+ retainDays: 0,
1378
+ userModel: User,
1379
+ })
1380
+ );
1381
+ ```
1382
+
1383
+ ### Collections
1384
+
1385
+ | Model | Route | Sync | Client create |
1386
+ |---|---|---|---|
1387
+ | `Notification` | `/notifications` | owner | **No** (`create: []`) |
1388
+ | `NotificationPreference` | `/notification-preferences` | owner | Yes (lazy defaults) |
1389
+
1390
+ `Notification` fields: `ownerId`, `title`, `body`, `href?`, `kind?`, `readAt?` (null = unread),
1391
+ `archivedAt?` (null = active inbox).
1392
+ Index: `{ownerId: 1, created: -1}`.
1393
+
1394
+ `NotificationPreference` fields: `ownerId` (unique among non-deleted rows), `inapp`, `mail`,
1395
+ `push`, `sms` (default `true`). Missing preference row = all channels on. Preference updates
1396
+ accept only the four channel booleans; `ownerId` is immutable. Soft-deleting a preference
1397
+ row does not block a later create for the same owner.
1398
+
1399
+ `notify()` comms fan-out isolates mail, SMS, and push: a rejection from one provider is
1400
+ logged and does not skip later channels.
1401
+
1402
+ ### `notify(input)`
1403
+
1404
+ Server-only seam. Writes the inbox when `inapp` is on, then optionally calls
1405
+ `getComms().sendMail` / `sendSms` / `sendPushToUser` when that channel is on and a
1406
+ destination exists. Comms errors after the inbox write are logged and do not fail `notify()`.
1407
+
1408
+ ### HTTP
1409
+
1410
+ | Method | Path | Notes |
1411
+ |---|---|---|
1412
+ | GET/PATCH/DELETE | `/notifications/:id` + list | Owner-scoped; **create disabled** |
1413
+ | POST | `/notifications/mark-all-read` | Sets `readAt` on caller's unread rows |
1414
+ | CRUD | `/notification-preferences` | Owner-scoped sync |
1415
+
1416
+ PATCH on notifications: `readAt` (ISO date or `null` to unread) and `archivedAt` (ISO date
1417
+ or `null`). Other keys are stripped. Dismiss sets `archivedAt` via syncdb update so the
1418
+ row stays in the snapshot. DELETE remains a retention tombstone (`isDeletedPlugin`), not
1419
+ the inbox archive path.
1420
+
1421
+ The example backend uses todo router lifecycle hooks to call `notify()`: create emits
1422
+ `Todo added`, the first incomplete-to-complete update emits `Todo completed`, and delete
1423
+ emits `Todo deleted`.
1424
+
1425
+ ### `notificationsBeforeSend`
1426
+
1427
+ Duck-typed hook for `CommsApp({beforeSend})`. Cancels when the user's preference for that
1428
+ channel is `false`. Never cancels `verification`.
1429
+
1430
+ ### Retention
1431
+
1432
+ `retainDays` default `0` (no sweep). When `retainDays > 0`, `sweepExpired()` tombstones rows
1433
+ with `created` older than N days. No Mongo TTL index.
1434
+
1435
+ How-to: [In-app notifications](../how-to/in-app-notifications.md).
1436
+
1124
1437
  ## Webhooks & Notifications
1125
1438
 
1126
1439
  JSON and `application/x-www-form-urlencoded` parsers on `TerrenoApp` copy the original
@@ -1334,6 +1647,20 @@ for (let i = 0; i < 3; i++) {
1334
1647
  }
1335
1648
  ``````
1336
1649
 
1650
+ ## Migrations
1651
+
1652
+ `runMigrations({migrations, dryRun, connection, mongoose})` applies pending versioned `up` functions in array order. Dry-run calls `up({dryRun: true})` and does **not** write history. Wet runs insert `{id, checksum, appliedAt}` into Mongo collection `terreno_migrations`. Already-applied ids are skipped; a checksum change after apply throws `Migration checksum mismatch` (409). Apply is serialized with a lock document `_id: "_lock"` in the same collection: wait, heartbeat, steal after **10 minutes**.
1653
+
1654
+ `runDownMigrations({migrations, dryRun, connection, mongoose, steps})` rolls back the last `steps` applied files (default 1 via the CLI). Dry-run calls `down({dryRun: true})` and leaves history in place. Wet deletes the history row after `down`. Missing `down` throws `Migration has no down` (400) and does not change history.
1655
+
1656
+ Files are `migrations/<YYYYMMDDHHmmss>-<slug>.ts`. `checkMigrationFiles({dir})` (alias `loadMigrations`) loads them in filename order without connecting to Mongo; the exported `id` must match the filename stem. Optional `down` is allowed. Duplicate ids and invalid names fail with 400 `APIError`. A missing directory fails with 404 `Migration directory not found`. Paths on Bun's compile-time virtual FS (`$bunfs`) are remapped with `resolveMigrationDir` to `MIGRATIONS_DIR` or `<cwd>/migrations`. `exerciseReversibleMigrations({dir, connect})` applies every file on a connected database, then rolls back in reverse until a file without `down` stops the chain (that id is recorded; earlier files stay applied).
1657
+
1658
+ `assertMigrationsAllowed({isProduction, allowEnv, force, dryRun})` gates wet apply. Dry-run is always allowed. Production wet requires `ALLOW_MIGRATIONS=true` and `--force` (or a later boot/admin Apply equivalent).
1659
+
1660
+ `buildSchemaCatalog({models})` snapshots Mongoose paths, required/unique flags, and indexes. `diffSchemaCatalog({before, after})` marks optional fields and non-unique index adds as safe, and required fields, unique indexes, same-type rename heuristics, removed paths, and same-path type changes as unsafe (fail-closed generate stubs). Mixed diffs still throw, and keep safe index calls as comments in the stub.
1661
+
1662
+ The `@terreno/api` bin `terreno-migrate` runs `check`, `generate`, `status`, `up`, and `down`. See [Run MongoDB migrations](../how-to/run-mongodb-migrations.md).
1663
+
1337
1664
  ## Script Helpers
1338
1665
 
1339
1666
  ### runSeeds and runSeedCli
@@ -1402,6 +1729,13 @@ cronjob("0 * * * *", async () => {
1402
1729
  - `"0 0 * * *"` — Daily at midnight
1403
1730
  - `"0 */6 * * *"` — Every 6 hours
1404
1731
 
1732
+ **Process-local only:** `cronjob()` runs inside the current Node process. It does not
1733
+ persist work, retry across crashes, or share state between API and worker replicas. For
1734
+ durable queues, retries, dead-lettering, cron schedules stored in MongoDB, and admin
1735
+ visibility, use [`@terreno/jobs`](jobs.md) — see
1736
+ [Durable background jobs](../how-to/background-jobs.md). Use `wrapScript` (above) for
1737
+ one-shot CLI entrypoints, not queued work.
1738
+
1405
1739
  ## Deprecations
1406
1740
 
1407
1741
  ### transformer (modelRouter option)
@@ -1459,6 +1793,7 @@ Complete reference of environment variables used by @terreno/api:
1459
1793
  | `PORT` | No | `3000` | HTTP server port |
1460
1794
  | `NODE_ENV` | No | `development` | Environment: `development`, `production`, `test` |
1461
1795
  | `MONGO_URI` or `MONGO_CONNECTION` | Yes | — | MongoDB connection string |
1796
+ | `ALLOW_MIGRATIONS` | No | unset | Set to `"true"` plus CLI `--force` for production wet `up`/`down` |
1462
1797
  | `ENABLE_SWAGGER` | No | — | Set to `"true"` to enable Swagger UI at `/docs` |
1463
1798
  | `WEBSOCKET_PORT` | No | `PORT + 1` | Socket.io server port (if using WebSockets) |
1464
1799
 
@@ -1489,8 +1824,11 @@ Complete reference of environment variables used by @terreno/api:
1489
1824
  | `GCP_PROJECT` | No | — | Google Cloud project ID (for Cloud Tasks, etc.) |
1490
1825
  | `GCP_LOCATION` | No | — | GCP region (e.g., "us-central1") |
1491
1826
  | `GCP_SERVICE_ACCOUNT_EMAIL` | No | — | Service account email for authentication |
1492
- | `GCP_TASKS_NOTIFICATIONS_QUEUE` | No | — | Cloud Tasks queue name for notifications |
1493
- | `GCP_TASK_PROCESSOR_QUEUE` | No | — | Cloud Tasks queue name for background jobs |
1827
+ | `GCP_TASKS_NOTIFICATIONS_QUEUE` | No | — | Legacy name in docs/tests only — **not** read by `@terreno/api` or `@terreno/jobs` |
1828
+ | `GCP_TASK_PROCESSOR_QUEUE` | No | — | Legacy name in docs/tests only — configure Cloud Tasks via `GcpCloudTasksRunner` options |
1829
+ | `GCP_TASKS_AUDIT_QUEUE` | No | — | Cloud Tasks queue name for off-process `AuditEvent` writes |
1830
+ | `AUDIT_TASKS_URL` | No | — | Worker URL Cloud Tasks POSTs audit writes to |
1831
+ | `AUDIT_TASKS_SECRET` | No | — | Shared secret for `X-Terreno-Audit-Secret` on the audit worker route |
1494
1832
 
1495
1833
  ### Other
1496
1834
 
@@ -1519,6 +1857,7 @@ SENTRY_DSN=https://...@sentry.io/...
1519
1857
 
1520
1858
  ## Learn more
1521
1859
 
1860
+ - [Enable the framework audit log](../how-to/audit-log.md)
1522
1861
  - [How to create a model](../how-to/create-a-model.md)
1523
1862
  - [Add GitHub OAuth](../how-to/add-github-oauth.md)
1524
1863
  - [Authentication architecture](../explanation/authentication.md)
@@ -70,6 +70,39 @@ A throwing `beforeSend` is logged and treated as no-op (send continues). Adapter
70
70
  call `recordDeliveryEvent()` / `recordOptOut()` to update the log and fire
71
71
  `onDeliveryEvent` / `onOptOut`.
72
72
 
73
+ ### Notification preferences hook
74
+
75
+ When you register `NotificationsApp`, compose `notificationsBeforeSend` from `@terreno/api`
76
+ into `CommsApp({beforeSend})` so outbound mail, SMS, and push respect per-user channel
77
+ toggles. Missing `NotificationPreference` rows default to all channels on. The
78
+ `verification` channel is never cancelled by this hook.
79
+
80
+ ```typescript
81
+ import {notificationsBeforeSend} from "@terreno/api";
82
+
83
+ new CommsApp({
84
+ beforeSend: async (context) => {
85
+ const pref = await notificationsBeforeSend({
86
+ channel: context.channel,
87
+ userId: context.userId,
88
+ });
89
+ if (pref?.cancel) {
90
+ return {cancel: true};
91
+ }
92
+ return existingBeforeSend?.(context);
93
+ },
94
+ });
95
+ ```
96
+
97
+ `getNotificationService().notify()` fans out to comms independently. Register this hook and
98
+ pass `{userId}` as the second argument to direct `sendMail` / `sendSms` calls so they honor
99
+ preferences; `sendPushToUser` already supplies its user id.
100
+
101
+ ```typescript
102
+ await getCommsService().sendMail(message, {userId});
103
+ await getCommsService().sendSms(message, {userId});
104
+ ```
105
+
73
106
  Each send stores one `CommsMessage` row with `attempts[]`. Rendered payloads are retained
74
107
  for `retainPayloadDays` (default 30, `0` disables) after `redactPayload`. Mail payloads
75
108
  keep `to`, `from`, `subject`, `text`, `html`, `replyTo`, `templateId`, and
@@ -0,0 +1,44 @@
1
+ # create-terreno-app
2
+
3
+ Unscoped npm CLI that writes a deployable Terreno backend + Expo frontend to disk.
4
+
5
+ Operator walkthrough (Mongo replica set, seed, SDK, web, deploy): [Create a Terreno app](../how-to/create-a-terreno-app.md). First-run tutorial: [Getting started](../tutorials/getting-started.md).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bunx create-terreno-app my-app --display-name "My App"
11
+ ```
12
+
13
+ npm (note the `--` before flags):
14
+
15
+ ```bash
16
+ npm create terreno-app my-app -- --display-name "My App"
17
+ ```
18
+
19
+ The published package name is `create-terreno-app`. Generated `@terreno/*`
20
+ dependencies pin `^<create-terreno-app version>`.
21
+
22
+ ## CLI
23
+
24
+ ```bash
25
+ bunx create-terreno-app <appName> --display-name <string> [--description <string>] [--mcp-server-url <url>] [--yes]
26
+ ```
27
+
28
+ | Flag | Required | Default |
29
+ | --- | --- | --- |
30
+ | `appName` (positional) | yes | kebab-case (`^[a-z][a-z0-9-]*$`) |
31
+ | `--display-name` | unless `--yes` | derived from `appName` when `--yes` |
32
+ | `--description` | no | empty |
33
+ | `--mcp-server-url` | no | hosted Terreno MCP URL |
34
+ | `--yes` | no | off |
35
+
36
+ Writes `<cwd>/<appName>/`. Refuses a non-empty target except `.git` / `.gitignore`. Does not run `bun install`, Mongo, or seed.
37
+
38
+ Printed next steps install, seed, then start the backend. SDK and web run in a second terminal while the backend is up.
39
+
40
+ The generated Profile tab uses `@terreno/ui` `TapToEdit` with `PATCH /auth/me`. Fonts come from `@terreno/ui`. `app.json` leaves icon/splash/favicon unset. `metro.config.js` pins `jspdf` so Metro can bundle admin PDF export. `tsconfig.json` has no `baseUrl` (TypeScript 6). Auth routes use `<Stack.Protected>`.
41
+
42
+ ## Library
43
+
44
+ `generateAllFiles` returns the file list; `writeScaffold` writes it to disk. The CLI is the only supported way to scaffold an app. MCP `terreno_bootstrap_ai_rules` can add editor rules after the CLI runs.
@@ -69,6 +69,8 @@ Comprehensive guide to environment variables used across Terreno packages and ex
69
69
  | `FRONTEND_URL` | example-backend CORS and `authOptions.publicAppUrl` | ❌ | `http://localhost:8082` | No | server |
70
70
  | `API_URL` | microservice split | ❌ | — | No | server |
71
71
  | `DISABLE_LOG_ALL_REQUESTS` | `@terreno/api` logging | ❌ | — | No | server |
72
+ | `ALLOW_MIGRATIONS` | `@terreno/api` `terreno-migrate` | ❌ | unset | No | server |
73
+ | `MIGRATIONS_DIR` | example-backend Dockerfile / `resolveMigrationDir` | ❌ | `<cwd>/migrations` when `dir` is `$bunfs` | No | server |
72
74
 
73
75
  ## Client / build-time
74
76
 
@@ -237,6 +239,24 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
237
239
  | `TERRENO_MCP_EVAL` | MCP eval | ❌ | — | No | tooling |
238
240
  | `TERRENO_PROJECT_ROOT` | MCP local | ❌ | — | No | tooling |
239
241
 
242
+ ## Background jobs
243
+
244
+ | Variable | Read by | Required | Default | Secret | Scope |
245
+ |----------|---------|----------|---------|--------|-------|
246
+ | `JOBS_START_WORKER` | example-backend (`jobsStartWorker.ts`) | ❌ | `true` (unset) | No | server |
247
+
248
+ `@terreno/jobs` itself does **not** read environment variables — pass `JobsApp` and runner
249
+ options explicitly. `JOBS_START_WORKER` only gates whether the example API process calls
250
+ `jobsApp.startWorker()` after listen. Set `false` when running the standalone
251
+ `bun run jobs:worker` process against the same MongoDB.
252
+
253
+ `JOB_TRACE_LOGS` is an optional app convention for verbose worker logging (see
254
+ [API logging & tracing](api.md#logging--tracing)); not read by the jobs package.
255
+
256
+ Legacy names `GCP_TASKS_NOTIFICATIONS_QUEUE` and `GCP_TASK_PROCESSOR_QUEUE` appear in
257
+ example-backend **test** setup only. They are not read by `@terreno/jobs`. Use
258
+ `GcpCloudTasksRunner` constructor config instead. See [Jobs reference](jobs.md).
259
+
240
260
  ## Example backend (app-specific)
241
261
 
242
262
  | Variable | Read by | Required | Default | Secret | Scope |
@@ -244,6 +264,14 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
244
264
  | `OTEL_SERVICE_NAME` | OpenTelemetry (example-backend) | ❌ | `example-backend` | No | server |
245
265
  | `PR_NUMBER` | PR preview deploy | ❌ | — | No | server |
246
266
  | `PR_SERVICE_URL` | PR preview deploy | ❌ | — | No | server |
267
+ | `JOBS_RUNNER` | example-backend | ❌ | `mongo` | No | server |
268
+ | `JOBS_START_WORKER` | example-backend | ❌ | `true` with Mongo | No | server |
269
+ | `GCP_TASKS_PROJECT` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
270
+ | `GCP_TASKS_LOCATION` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
271
+ | `GCP_TASKS_QUEUE` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
272
+ | `GCP_TASKS_PUBLIC_URL` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
273
+ | `GCP_TASKS_OIDC_AUDIENCE` | example-backend Cloud Tasks runner | ❌ | `{publicUrl}/jobs/execute`; deployed Cloud Run uses the canonical tasks-service root URL so tagged PR callbacks pass platform IAM | No | server |
274
+ | `GCP_TASKS_SERVICE_ACCOUNT_EMAIL` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
247
275
  | `DEFAULT_PAGE_SIZE` | Configuration model | ❌ | `20` | No | server |
248
276
  | `CRON_SECRET_KEY` | tests | ❌ | — | Yes | tooling |
249
277
  | `WIDGET_CLIENT_SECRET` | widgets | ❌ | — | Yes | server |
@@ -278,3 +306,4 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
278
306
  - [Build for web](../how-to/build-for-web.md)
279
307
  - [Configure Better Auth](../how-to/configure-better-auth.md)
280
308
  - [Rate limiting](../how-to/rate-limiting.md)
309
+ - [Durable background jobs](../how-to/background-jobs.md)