@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
@@ -86,15 +86,15 @@ The repo states that `example-frontend` and `example-backend` serve as documenta
86
86
  | Admin panel (embedded) | yes | yes | — |
87
87
  | Admin SPA | yes | n/a | — |
88
88
  | AI streaming chat | yes | yes | — |
89
- | AI structured output | ? | ? | verify |
89
+ | AI structured output | no | no | **gap** — chat streaming only; see [example-coverage.md](../explanation/example-coverage.md) |
90
90
  | Feature flags + live updates | yes | yes | — |
91
91
  | Websockets / realtime | yes | yes | — |
92
92
  | Consent forms | yes | yes | — |
93
93
  | File upload / GCS | yes | yes | — |
94
- | Better Auth | ? | ? | verify |
95
- | syncdb local-first | (from #869) | (from #869) | verify after merge |
96
- | RBAC | no | no | not shipped |
97
- | Background jobs | no | no | not shipped |
94
+ | Better Auth | yes | yes | — |
95
+ | syncdb local-first | yes | yes | shipped in examples; #869 closed |
96
+ | RBAC | yes | yes | shipped; see [rbac-permissions.md](rbac-permissions.md) |
97
+ | Background jobs | no | no | **gap** — no generic queue example |
98
98
 
99
99
  Every `?` must be resolved by reading the example source, and every real gap either filled or recorded as a known gap.
100
100
 
@@ -149,15 +149,15 @@ See [`docs/tasks/examples-demo-coverage.md`](../tasks/examples-demo-coverage.md)
149
149
 
150
150
  ## Acceptance Criteria
151
151
 
152
- - [ ] Every component exported from `ui/src/index.tsx` has a demo story or an allowlist entry with a stated reason.
153
- - [ ] `bun run scripts/check-demo-coverage.ts` fails when a newly exported component has no story and no allowlist entry, and runs in `ui-demo-ci.yml`.
154
- - [ ] `demo/package.json`'s `test:ci` runs something real.
155
- - [ ] Stories exist for all P0 components, each showing multiple states (default, loading, error, disabled) where applicable.
156
- - [ ] Every published package's CI runs `scripts/check-coverage.ts` at the 95% threshold.
157
- - [ ] Packages currently below 95% have a tracked catch-up plan; no package is permanently exempt.
158
- - [ ] Every published package is covered by CI running its tests and coverage check, either through a dedicated workflow or the matrix job.
159
- - [ ] Codecov receives uploads from every package's CI and reports per-PR deltas.
160
- - [ ] `README.md` shows a coverage badge that reflects reality.
161
- - [ ] `docs/explanation/example-coverage.md` resolves every `?` in the capability matrix, and each real gap is either filled with an example or recorded as a known gap with an issue link.
162
- - [ ] `CONTRIBUTING.md` states the 95% coverage expectation and how to run coverage locally per package.
163
- - [ ] `bun run lint`, `bun run compile`, and the full test suite pass.
152
+ - [x] Every component exported from `ui/src/index.tsx` has a demo story or an allowlist entry with a stated reason.
153
+ - [x] `bun run scripts/check-demo-coverage.ts` fails when a newly exported component has no story and no allowlist entry, and runs in `ui-demo-ci.yml`.
154
+ - [x] `demo/package.json`'s `test:ci` runs something real.
155
+ - [x] Stories exist for all P0 components, each showing multiple states (default, loading, error, disabled) where applicable.
156
+ - [x] Every published package's CI runs `scripts/check-coverage.ts` at the 95% threshold.
157
+ - [x] Packages currently below 95% have a tracked catch-up plan; no package is permanently exempt.
158
+ - [x] Every published package is covered by CI running its tests and coverage check, either through a dedicated workflow or the matrix job.
159
+ - [x] Codecov receives uploads from every package's CI and reports per-PR deltas.
160
+ - [x] `README.md` shows a coverage badge that reflects reality.
161
+ - [x] `docs/explanation/example-coverage.md` resolves every `?` in the capability matrix, and each real gap is either filled with an example or recorded as a known gap with an issue link.
162
+ - [x] `CONTRIBUTING.md` states the 95% coverage expectation and how to run coverage locally per package.
163
+ - [x] `bun run lint`, `bun run compile`, and the full test suite pass.
@@ -0,0 +1,247 @@
1
+ # Implementation Plan: Framework-level audit log
2
+
3
+ **Status:** Approved
4
+ **Branch:** `cursor/framework-audit-log-grow-0d6c`
5
+ **Owner:** —
6
+ **Created:** 2026-09-10
7
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1186 (implementation PRs use `Fixes #1186`)
8
+ **Task list:** [framework-audit-log.md](../tasks/framework-audit-log.md)
9
+ **Program:** [B2B platform](b2b-platform-program.md)
10
+ **Depends on:** — (soft: `organizationId` now; org-admin list filter waits on [org-management-ui](org-management-ui.md))
11
+ **RTK deprecation flag:** None — admin viewer uses generated OpenAPI / `useAdminApi`, not syncdb
12
+
13
+ ## Goal
14
+
15
+ Opt-in `AuditApp` in `@terreno/api` persists append-only `AuditEvent` rows for successful mutations: who changed which record, with changed-field before/after diffs. The existing admin Platform **Audit Log** lists them. Default retention is forever. This is the B2B compliance log; it generalizes example `AdminAuditLog` without replacing consent `ConsentResponse` rows.
16
+
17
+ ## Non-Goals
18
+
19
+ - Waiting on `org-management-ui` to start (no `OrgsApp` in tree today).
20
+ - Default-on `modelRouter` product CRUD.
21
+ - Consent signing history (`ConsentResponse` + `GET /consents/audit/:userId` stay).
22
+ - Login, failed auth, permission denials (except existing `RbacAudit` `denied: true` fanned through `auditSink`).
23
+ - Sync mutate / outbox / socket writes (`@terreno/syncdb`). HTTP `modelRouter` only.
24
+ - Admin hard-delete of events; SIEM export; legal hold.
25
+ - Full-document snapshots.
26
+ - Per-org-admin filtered lists (store `organizationId` now; filter when org context ships).
27
+
28
+ ## Decisions
29
+
30
+ | ID | Question | Choice |
31
+ | --- | --- | --- |
32
+ | Q1 | Org dependency | Ship now. Optional `organizationId` from the mutated document or `req.organization` when present. |
33
+ | Q2 | Default on | Off. `AuditApp` plugin + per-router `audit: true`. **AdminApp writes `AuditEvent` automatically when `AuditApp` is registered.** |
34
+ | Q3 | Events | Successful create/update/delete and array mutations on audited routers; AdminApp → same collection; RBAC via existing `auditSink`; consent stays domain-specific. |
35
+ | Q4 | Mutability / retention | HTTP list+read only. Create only via framework writers. No PATCH/DELETE. **Default retain forever.** Optional `retentionDays > 0` Mongo TTL. |
36
+ | Q5 | Diffs | Changed fields only. Auto-redact `password`, `hash`, `salt`, `token`, `secret`, `refreshToken` (case-insensitive last path segment) plus per-router `redact`. Never log full request bodies. |
37
+
38
+ **Recorded assumptions (not grilled):** No default-connection singleton (same as `RbacAudit`: `createAuditEventModel(connection)`). Example-backend **replaces** `AdminAuditLog` with `AuditEvent`. `onAdminAudit` remains an extra sink. Writes are best-effort (`logger.error`, mutation still succeeds). Persist is fire-and-forget on the request path. Optional Cloud Tasks enqueue (`GCP_TASKS_AUDIT_QUEUE` + `AUDIT_TASKS_URL`) writes Mongo on a worker via `POST /internal/audit-events`. `AuditEvent` is never audited. `audit: true` without `AuditApp` skips the write and logs an error once per process. Array mutations use `verb: "updated"` and `operation` `arrayPush` / `arrayUpdate` / `arrayRemove`. `organizationId` is stored as string. Admin list uses `Permissions.IsAdmin` until org RBAC lands.
39
+
40
+ ## Architecture
41
+
42
+ ```
43
+ AuditApp({retentionDays?})
44
+ → createAuditEventModel(connection)
45
+ → set process recorder
46
+ → modelRouter("/audit-events", AuditEvent, {create/update/delete: [], list/read: IsAdmin})
47
+ → adminContribution() for Platform Audit Log
48
+
49
+ modelRouter(..., {audit: true | {redact?: string[]}})
50
+ → after successful create/update/delete/array *
51
+ → recordAuditEvent({actorId, modelName, recordId, recordLabel?, organizationId?, verb, operation, before, after, source: "modelRouter"})
52
+
53
+ AdminApp
54
+ → after successful admin CRUD, if recorder present
55
+ → recordAuditEvent({..., source: "admin"})
56
+ → then onAdminAudit? (unchanged extra sink)
57
+
58
+ createAccess({auditSink: persistRbacAuditToAuditEvent})
59
+ → source: "rbac"; before/after from permissionDelta when present
60
+ ```
61
+
62
+ `AuditApp` implements `TerrenoPlugin` (+ `adminContribution` like FeatureFlags). Importing `@terreno/api` does **not** register `AuditEvent` on `mongoose.connection`.
63
+
64
+ ### Config shape
65
+
66
+ ```typescript
67
+ interface AuditAppOptions {
68
+ enqueue?: (write: AuditEventWrite) => Promise<void>;
69
+ processQueuePath?: string;
70
+ processQueueSecret?: string;
71
+ /** Mongo TTL in days. Omit or 0 = forever (no TTL index). */
72
+ retentionDays?: number;
73
+ }
74
+
75
+ interface ModelRouterAuditOptions {
76
+ redact?: string[]; // extra field names; merged with defaults
77
+ }
78
+
79
+ // modelRouter options:
80
+ audit?: boolean | ModelRouterAuditOptions;
81
+ ```
82
+
83
+ `audit: true` equals `{redact: []}`.
84
+
85
+ ### Writer contract
86
+
87
+ ```typescript
88
+ interface AuditEventWrite {
89
+ actorId?: string;
90
+ after?: Record<string, unknown>;
91
+ before?: Record<string, unknown>;
92
+ modelName: string;
93
+ operation: "create" | "update" | "delete" | "arrayPush" | "arrayUpdate" | "arrayRemove";
94
+ organizationId?: string;
95
+ recordId?: string;
96
+ recordLabel?: string;
97
+ source: "admin" | "modelRouter" | "rbac";
98
+ verb: "created" | "deleted" | "updated";
99
+ }
100
+ ```
101
+
102
+ Diff helper: shallow (plus one-level nested objects) comparison of previous JSON vs next JSON after redaction. Unchanged keys omitted. Create: `before` omitted, `after` is redacted new fields. Delete: `after` omitted, `before` is redacted last state.
103
+
104
+ `organizationId` resolution order: `req.organization?.id` / `_id`, else string of `doc.organizationId` when present.
105
+
106
+ ## Models
107
+
108
+ `AuditEvent` (collection `auditevents`):
109
+
110
+ | Field | Type | Notes |
111
+ | --- | --- | --- |
112
+ | `actorId` | ObjectId, ref User, indexed | Optional (system / missing user) |
113
+ | `modelName` | string, required, indexed | |
114
+ | `recordId` | string, indexed | |
115
+ | `recordLabel` | string | Short label (admin list fields / `title` / `name` / `email`) |
116
+ | `organizationId` | string, indexed | Optional |
117
+ | `verb` | `created` \| `updated` \| `deleted`, indexed | Widget-compatible |
118
+ | `operation` | string, required | Fine-grained verb |
119
+ | `source` | `admin` \| `modelRouter` \| `rbac`, indexed | |
120
+ | `before` / `after` | Mixed | Changed fields only, redacted |
121
+ | `created` / `updated` | dates | `createdUpdatedPlugin`; no `isDeletedPlugin` (append-only) |
122
+
123
+ Indexes: `{created: -1}`, `{modelName: 1, recordId: 1, created: -1}`. Optional TTL on `created` when `retentionDays > 0`. Every field has `description`. Five-type pattern co-located with the schema. `strict: "throw"`.
124
+
125
+ HTTP: `create`/`update`/`delete` empty permission arrays. No public POST/PATCH/DELETE even for admins. Denied list/create (including non-admin GET list) returns **405**, matching `permissionMiddleware`; instance-level denials after a loaded document remain **403**.
126
+
127
+ ## APIs
128
+
129
+ | Method | Path | Who | Behavior |
130
+ | --- | --- | --- | --- |
131
+ | GET | `/audit-events` | IsAdmin | Paginated list, default sort `-created`, `queryFields`: `modelName`, `recordId`, `actorId`, `verb`, `source`, `organizationId` |
132
+ | GET | `/audit-events/:id` | IsAdmin | Read one |
133
+ | POST/PATCH/DELETE | `/audit-events` | — | Disabled |
134
+
135
+ Admin mounts the same model via `adminContribution` (or example registers it once — do **not** double-mount). Route path includes `audit-log` **or** model name `AuditEvent` so `AdminShell.isAuditLogModel` lifts it into Platform.
136
+
137
+ RBAC helper `persistRbacAuditToAuditEvent(model)` returns an `RbacAuditSink`. Built-in `RbacAudit` collection unchanged.
138
+
139
+ ## Notifications
140
+
141
+ None.
142
+
143
+ ## UI
144
+
145
+ Reuse Platform Audit Log + `RecentActivityWidget`. Update `isAuditLogModel` to match `AuditEvent` (keep `AdminAuditLog` and `audit-log` path for leftover apps). List columns: `created`, `verb`, `modelName`, `recordLabel`, `actorId`. Detail form is read-only; show `before`/`after` as JSON (existing object field rendering is enough). No new custom screen.
146
+
147
+ example-backend: register `AuditApp`, enable `audit: true` on `Todo`, drop `AdminAuditLog` model and `onAdminAudit` persistence (optional `onAdminAudit` may remain no-op or be removed). Point home `recentActivity` at `AuditEvent`. RBAC `auditSink` uses the helper. Scripts that count/wipe `AdminAuditLog` switch to `AuditEvent` (wipe only in test/dev scripts — production HTTP still cannot delete).
148
+
149
+ ## Docs in this slice
150
+
151
+ | Page | Change |
152
+ | --- | --- |
153
+ | `docs/how-to/audit-log.md` | **New.** Enable `AuditApp`, per-router `audit`, AdminApp auto-write, redaction, TTL, append-only, org field. |
154
+ | `docs/reference/api.md` | `AuditApp`, `audit` option, `persistRbacAuditToAuditEvent`, no default-connection model. |
155
+ | `docs/explanation/admin-interface.md` | Platform Audit Log is `AuditEvent` when `AuditApp` is registered. |
156
+ | `docs/reference/admin-backend.md` | `onAdminAudit` is extra; framework write when `AuditApp` present. |
157
+ | `docs/explanation/roadmap-seed-issues.md` | IP + task URLs; soft org depend. |
158
+ | `docs/implementationPlans/b2b-platform-program.md` | Row for this IP. |
159
+ | `.rulesync/rules/api/00-api.mdc` | AuditApp + `audit` option. |
160
+ | `CHANGELOG.md` | Added opt-in framework audit log. |
161
+
162
+ ## Testing
163
+
164
+ Tracer: `TerrenoApp` + `AuditApp` + test model with `audit: true` → authenticated PATCH changes `title` → `GET /audit-events` as admin returns one row with `verb: "updated"`, `before.title` / `after.title`, no `password` if present.
165
+
166
+ Cases:
167
+
168
+ - Plugin omitted: `audit: true` does not throw on CRUD; no collection on default connection; error logged once.
169
+ - Create/update/delete each write one event; actorId matches `req.user`.
170
+ - Unchanged fields absent from `before`/`after`.
171
+ - Default redact list strips secrets even if not in `redact`.
172
+ - Per-router `redact: ["ssn"]` strips `ssn`.
173
+ - Array push writes `operation: "arrayPush"`, `verb: "updated"`.
174
+ - Admin POST writes `source: "admin"` without `onAdminAudit`.
175
+ - `onAdminAudit` still fires when both configured; mutation 201 if either sink throws.
176
+ - AuditEvent HTTP POST is 404/not registered; PATCH/DELETE disabled.
177
+ - `organizationId` copied from document; from `req.organization` when set.
178
+ - `retentionDays` omitted: no TTL index. `retentionDays: 1`: TTL index present (assert index spec; do not sleep a day).
179
+ - Example RBAC role change fans into `AuditEvent` with `source: "rbac"`.
180
+ - Existing `onAdminAudit` tests stay green (best-effort extra sink).
181
+ - Admin frontend: `isAuditLogModel` true for `AuditEvent`; Recent Activity still skips when no audit model.
182
+
183
+ Skills: `terreno-backend-api`, `mongoose-schema-safety`, `backend-test-env` (if tests touch env), `building-admin-interfaces`, `verify-ui-changes` (example `/admin` audit log after Todo/admin mutation), `update-docs`, `generate-sdk` if example OpenAPI admin SDK lists the new route.
184
+
185
+ ## Risks
186
+
187
+ | Risk | Mitigation |
188
+ | --- | --- |
189
+ | Write volume / PII | Opt-in per router; redaction; append-only HTTP. |
190
+ | Missing plugin | No-op + once-per-process error; docs. |
191
+ | Example `AdminAuditLog` break | Replace in the same slice; shell still matches old name. |
192
+ | TTL index change later | How-to: drop old TTL index when changing `retentionDays`. |
193
+ | Sync bypass | Documented non-goal; follow-up IP. |
194
+ | Org filter incomplete | Field stored; list filter is `org-management-ui`. |
195
+
196
+ ## Phases
197
+
198
+ 1. Tracer: model + `AuditApp` + `audit: true` create/update/delete + list/read API + redaction.
199
+ 2. Array mutations + `organizationId`.
200
+ 3. AdminApp auto-write + example migration + shell matcher + RBAC helper.
201
+ 4. Optional TTL.
202
+ 5. Docs, changelog, rulesync, seed, B2B table.
203
+
204
+ ## Feature Flags & Migrations
205
+
206
+ No feature flag. New collection. example-backend drops `AdminAuditLog` (example-only; no published schema). Apps that copied `AdminAuditLog` keep it until they register `AuditApp`.
207
+
208
+ ## Activity Log & User Updates
209
+
210
+ This IP **is** the activity log. No `UserUpdate` spam.
211
+
212
+ ## Not Included / Future Work
213
+
214
+ - Org-admin scoped list (`org-management-ui`).
215
+ - Sync-path audit.
216
+ - Auth / login events.
217
+ - SIEM export, legal hold, immutable object storage.
218
+ - Custom diff UI beyond JSON fields.
219
+
220
+ ## Files to Create / Modify
221
+
222
+ | File | Role |
223
+ | --- | --- |
224
+ | `api/src/audit/*` | Model, recorder, diff/redact, `AuditApp`, tests |
225
+ | `api/src/api.ts` | `audit` option + post hooks |
226
+ | `api/src/index.ts` | Exports |
227
+ | `admin-backend/src/adminApp.ts` | Auto-write when recorder present |
228
+ | `admin-frontend/src/AdminShell.tsx`, `AdminHome.tsx` | `isAuditLogModel` |
229
+ | `example-backend/src/server.ts`, `access.ts`, models, scripts | `AuditApp`; drop `AdminAuditLog` |
230
+ | Docs listed above | Same slice |
231
+
232
+ ## Task List
233
+
234
+ [docs/tasks/framework-audit-log.md](../tasks/framework-audit-log.md)
235
+
236
+ ## Acceptance Criteria
237
+
238
+ - [ ] `AuditApp` omitted: no `AuditEvent` on the default connection; product CRUD unchanged.
239
+ - [ ] `audit: true` + `AuditApp`: create/update/delete persist one event with actor, model, record, changed-field diff, redacted secrets.
240
+ - [ ] HTTP `/audit-events` is list+read for admin only; create/update/delete disabled.
241
+ - [ ] Admin mutations write `AuditEvent` when the plugin is registered, without requiring `onAdminAudit`.
242
+ - [ ] `onAdminAudit` still runs as an extra best-effort sink.
243
+ - [ ] example-backend Platform Audit Log and Recent Activity read `AuditEvent`; `AdminAuditLog` removed.
244
+ - [ ] RBAC `auditSink` helper writes `source: "rbac"` without dropping `RbacAudit`.
245
+ - [ ] Default: no TTL. `retentionDays > 0` adds TTL on `created`.
246
+ - [ ] How-to + api reference + admin explanation match the shipped design.
247
+ - [ ] Merging the implementation PR closes https://github.com/FlourishHealth/terreno/issues/1186 (`Fixes #1186`).
@@ -0,0 +1,40 @@
1
+ # GPTChat optional mascot
2
+
3
+ **Status:** Complete
4
+ **Branch:** `cursor/gpt-chat-mascot-d39f`
5
+ **Owner:** cloud agent
6
+ **Created:** 2026-09-15
7
+
8
+ ## Goal
9
+
10
+ Let consuming apps brand the GPT screen with their own mascot. Terreno does not ship a default character.
11
+
12
+ ## Non-Goals
13
+
14
+ - Bundling artwork, Lottie files, or a Terreno fox in `@terreno/ui`
15
+ - Animating the mascot from `isStreaming` inside GPTChat (consumers wrap their own node)
16
+ - Showing the mascot beside assistant bubbles
17
+
18
+ ## Decisions
19
+
20
+ | Question | Decision |
21
+ |----------|----------|
22
+ | Who owns the asset? | Consumer. `mascot?: React.ReactNode` |
23
+ | When is it visible? | Only while `currentMessages.length === 0` |
24
+ | Default? | Omitted — existing empty chat unchanged |
25
+
26
+ ## UI
27
+
28
+ `GPTChat` empty-state hero: optional mascot slot, then existing suggested prompts.
29
+
30
+ ## Task List
31
+
32
+ [docs/tasks/gpt-chat-mascot.md](../tasks/gpt-chat-mascot.md)
33
+
34
+ ## Acceptance Criteria
35
+
36
+ - [ ] Omitting `mascot` leaves the empty chat unchanged
37
+ - [ ] Passing `mascot` shows it on an empty chat, including with suggested prompts
38
+ - [ ] Messages hide the mascot
39
+ - [ ] Demo story and example app show a consumer node
40
+ - [ ] Docs describe the optional slot
@@ -0,0 +1,330 @@
1
+ # Implementation Plan: Durable background jobs (`@terreno/jobs`)
2
+
3
+ **Status:** Complete — 2026-09-10
4
+ **Branch:** `cursor/durable-background-jobs-803d`
5
+ **Owner:** —
6
+ **Created:** 2026-09-10
7
+ **Approved:** 2026-09-10
8
+ **Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1188 (this feature **closes** that issue; implementation PRs use `Fixes #1188`)
9
+ **Task list:** [job-queues.md](../tasks/job-queues.md)
10
+ **Depends on:** —
11
+ **RTK deprecation flag:** Partial — admin screens use generated SDK / `useAdminApi`
12
+ **Program:** [B2B platform](b2b-platform-program.md)
13
+
14
+ ## Goal
15
+
16
+ Terreno apps can enqueue named jobs that survive process restarts: retries with
17
+ backoff, delayed and recurring schedules, dead-lettering, and an admin operations
18
+ UI. Persistence and the default worker are **Mongo**. Execution is a pluggable
19
+ `JobRunner` so the same job definitions run in-process, in a standalone worker,
20
+ on **GCP Cloud Tasks**, on **Vercel Queues**, or on a custom runner. Comms,
21
+ webhooks, and billing stay on their current paths; this IP ships the queue they
22
+ will enqueue onto later.
23
+
24
+ ## Non-Goals
25
+
26
+ - BullMQ, Redis, or any Redis-backed queue.
27
+ - Vercel Workflows (`"use workflow"` / `"use step"`).
28
+ - Migrating `@terreno/comms` sends, Expo receipt polling, `WebhooksApp`
29
+ 200-then-queue dispatch, or `@terreno/billing` sync onto the queue.
30
+ - Replacing or merging `BackgroundTask` / admin **scripts**.
31
+ - Changing in-process `cronjob()` behavior (hard-coded `America/Chicago`).
32
+ - Live GCP or Vercel integration tests in CI.
33
+ - Generic `modelRouter` CRUD as the operator UI.
34
+
35
+ ## Decisions
36
+
37
+ | Question | Decision |
38
+ |----------|----------|
39
+ | Package | New workspace package **`@terreno/jobs`**. `JobsApp` implements `TerrenoPlugin`. |
40
+ | Persistence | Mongo collections owned by this package. Default runner claims work from Mongo. |
41
+ | `BackgroundTask` | Unchanged. Scripts stay in-process bookkeeping. Jobs are a separate `Job` model. |
42
+ | v1 consumers | Framework + example only. Public `enqueue` is the seam later IPs call. |
43
+ | Workers | **Both:** `jobs.startWorker()` in the API process **or** a dedicated worker process. Explicit opt-in; registering `JobsApp` does not start a poll loop. |
44
+ | Schedules | Delayed `runAt` and recurring cron. Timezone is an **IANA** string on each schedule (default **`UTC`**). |
45
+ | Cloud dispatch | **GCP Cloud Tasks** and **Vercel Queues** adapters (mocked-SDK tests). Custom `JobRunner` is a first-class type. |
46
+ | Vercel product | **Queues** only. Job catalog and admin stay in Terreno. |
47
+ | Execute HTTP | `POST {basePath}/execute` for GCP/custom HTTP push (GCP: OIDC). Vercel uses private `handleCallback` consumers → `executeJobById`. Mongo runner does **not** mount execute unless a cloud/custom runner needs it. |
48
+ | Admin UI | Custom screen `jobs` (comms pattern): list/filter, detail/attempts, retry, DLQ requeue, cancel. Home widget. example-frontend + admin-spa. |
49
+ | Existing `cronjob()` | Leave it. Docs call it process-local. Durable work uses `JobsApp`. |
50
+ | Delivery | At-least-once. Handlers must be idempotent. Optional `idempotencyKey` unique with `name`. |
51
+ | Payload | JSON-serializable only. Admin redacts by default (`redactPayload`). |
52
+ | Recurring overlap | One in-flight instance per schedule `name`; skip the tick if still running. |
53
+
54
+ ## Architecture
55
+
56
+ ```
57
+ @terreno/jobs
58
+ JobsApp (TerrenoPlugin)
59
+ define(name, {handler, retry, schedule?})
60
+ enqueue({name, payload, runAt?, idempotencyKey?})
61
+ startWorker() // Mongo poll / runner.start
62
+ adminContribution() → screen "jobs"
63
+ optional POST /jobs/execute // cloud/custom HTTP
64
+
65
+ JobRunner
66
+ enqueue(job) → dispatch
67
+ start?(signal) → local poll (Mongo)
68
+ stop?()
69
+
70
+ MongoJobRunner — claim via findOneAndUpdate; lock TTL
71
+ GcpCloudTasksRunner — create HTTP task → POST /jobs/execute (OIDC)
72
+ VercelQueuesRunner — send(topic,{jobId}) → private handleCallback consumer → executeJobById
73
+ Custom — app-supplied JobRunner
74
+ ```
75
+
76
+ ```typescript
77
+ import {JobsApp, MongoJobRunner, getJobsService} from "@terreno/jobs";
78
+
79
+ const jobs = new JobsApp({
80
+ runner: new MongoJobRunner(),
81
+ timezone: "UTC",
82
+ });
83
+
84
+ jobs.define("send-welcome-email", {
85
+ handler: async (payload, ctx) => {
86
+ ctx.log.info("sending");
87
+ // work
88
+ },
89
+ retry: {maxAttempts: 5, backoffMs: 1000},
90
+ });
91
+
92
+ new TerrenoApp({userModel: User}).register(jobs);
93
+
94
+ // Explicit — API process or a worker entrypoint
95
+ await jobs.startWorker();
96
+
97
+ await getJobsService().enqueue({
98
+ name: "send-welcome-email",
99
+ payload: {userId: "..."},
100
+ });
101
+ ```
102
+
103
+ Standalone worker (example-backend): same `JobsApp` definitions, `build()` without
104
+ `listen`, then `startWorker()`. Do not start two poll loops against the same
105
+ queue unless lock TTL and claim are in place (they are).
106
+
107
+ ### Claim (Mongo)
108
+
109
+ `findOneAndUpdate` where `status` is `pending` or `scheduled`, `runAt <= now`,
110
+ and `lockedAt` is missing or older than `lockTtlMs` (default 15 minutes). Set
111
+ `status: "running"`, `lockedAt`, `lockedBy` (hostname + pid). On success, wrap
112
+ `runWithRequestContext({jobId})`. On handler success → `completed`. On throw →
113
+ increment `attemptCount`, set `lastError`, either `pending` with `runAt` backoff
114
+ or `dead` when `attemptCount >= maxAttempts`.
115
+
116
+ ### Retry
117
+
118
+ Exponential backoff from `retry.backoffMs` (default 1000) with jitter, cap
119
+ `retry.maxBackoffMs` (default 15 minutes). `maxAttempts` default 5. Dead-letter
120
+ status is **`dead`**. Admin **Retry** enqueues a new `Job` (`retriedFromId`);
121
+ **Requeue DLQ** resets the same row to `pending` with `runAt: now` and clears
122
+ the lock.
123
+
124
+ ### Recurring
125
+
126
+ `define(name, {schedule: {cron, timezone?}})` upserts a `JobSchedule` row.
127
+ The worker ticks due schedules and `enqueue`s a child `Job` with
128
+ `scheduleId`. Timezone defaults to `JobsApp` `timezone` then `UTC`. Invalid cron
129
+ fails `define` / startup, not silently.
130
+
131
+ ### `JobRunner`
132
+
133
+ ```typescript
134
+ interface JobRunner {
135
+ readonly id: string;
136
+ enqueue(job: JobDocument): Promise<void>;
137
+ start?(options: {jobs: JobsApp; signal: AbortSignal}): Promise<void>;
138
+ stop?(): Promise<void>;
139
+ }
140
+ ```
141
+
142
+ Mongo `enqueue` only persists (already saved) then no-ops dispatch — the poller
143
+ picks it up. Cloud runners persist then create a platform task whose HTTP body
144
+ is `{jobId}` (not the payload). Execute loads the Mongo row, claims if needed,
145
+ runs the handler.
146
+
147
+ GCP and Vercel SDKs are **optional peers** on subpath exports
148
+ (`@terreno/jobs/runners/gcpCloudTasks`, `@terreno/jobs/runners/vercelQueues`).
149
+ Core `@terreno/jobs` has zero cloud SDKs. Tests inject fakes.
150
+
151
+ ### Execute HTTP
152
+
153
+ Mounted only when `JobsApp` `mountExecuteRoute: true` or the runner requests it
154
+ (GCP default true; Vercel uses private `handleCallback` consumers; Mongo default false).
155
+
156
+ | Status | When |
157
+ |--------|------|
158
+ | 200 | Handler finished (success or recorded failure/dead) |
159
+ | 401 | Missing/invalid OIDC or platform signature |
160
+ | 404 | Unknown `jobId` |
161
+ | 409 | Job already `completed` / `cancelled` (idempotent no-op 200 preferred for provider retries — **200** if terminal success; **409** only if still `running` under a live lock) |
162
+
163
+ Do not skip rate limiting on this path. Do not add it to generated SDK consumer
164
+ hooks (admin uses dedicated jobs routes). OpenAPI: document execute as
165
+ internal/operator; omit from public SDK codegen if tagged internal.
166
+
167
+ ### Logging
168
+
169
+ `createScopedLogger({prefix: "[Job]", labels: {jobName, jobId}})`. Never log
170
+ full payloads or secrets. Correlation: `runWithRequestContext({jobId})`.
171
+
172
+ ## Models
173
+
174
+ Five-type pattern, field `description`s, `createdUpdatedPlugin`. **No**
175
+ `isDeletedPlugin` on live queue rows (use `cancelled`). Not `modelRouter`
176
+ models.
177
+
178
+ **Job** — `name` (string, indexed), `payload` (Mixed), `status` (`pending` \|
179
+ `scheduled` \| `running` \| `completed` \| `failed` \| `cancelled` \| `dead`),
180
+ `runAt` (date, indexed), `attemptCount` (number), `maxAttempts` (number),
181
+ `lockedAt` / `lockedBy`, `lastError`, `attempts[]` (`{at, error, errorClass}`),
182
+ `scheduleId` (optional ref), `idempotencyKey` (optional), `retriedFromId` /
183
+ `retriedById`, `payloadRedacted` (boolean). Unique sparse index
184
+ `(name, idempotencyKey)`. Compound index `(status, runAt)`.
185
+
186
+ **JobSchedule** — `name` (unique), `cron`, `timezone`, `nextRunAt`, `enabled`,
187
+ `handlerName` (= `name`).
188
+
189
+ ## APIs
190
+
191
+ Default `basePath` `/jobs`. Admin routes require `admin:access` plus
192
+ `admin:jobs` when `accessControl` is configured; legacy `user.admin` when not.
193
+
194
+ | Method | Path | Purpose |
195
+ |--------|------|---------|
196
+ | GET | `/jobs` | Paginated list: `name`, `status`, `scheduleId`, date range, `q` |
197
+ | GET | `/jobs/:id` | Detail: attempts, redacted payload, lock |
198
+ | POST | `/jobs/:id/retry` | New job from payload; original `retriedById` |
199
+ | POST | `/jobs/:id/requeue` | DLQ/`failed`/`cancelled` → `pending` now |
200
+ | POST | `/jobs/:id/cancel` | `pending`/`scheduled`/`running` → `cancelled` (handler `ctx.signal`) |
201
+ | GET | `/jobs/schedules` | Defined schedules + next run |
202
+ | POST | `/jobs/schedules/:name/pause` | `enabled: false` |
203
+ | POST | `/jobs/schedules/:name/resume` | `enabled: true` |
204
+ | POST | `/jobs/execute` | Cloud/custom HTTP execute (not admin session) |
205
+
206
+ Enqueue from app code only (`getJobsService().enqueue`). No public
207
+ `POST /jobs` create for arbitrary names from the browser.
208
+
209
+ ## Notifications
210
+
211
+ None. Failures are `Job` rows + `logger`. Downstream comms can `enqueue` later.
212
+
213
+ ## UI
214
+
215
+ `JobsApp.adminContribution()`: custom screen `name: "jobs"`, display **Jobs**,
216
+ icon `clock`. Built-in widget in `@terreno/admin-frontend` (`JOBS_ADMIN_WIDGETS`)
217
+ so hosts do not re-map (comms pattern).
218
+
219
+ | Piece | Role |
220
+ |-------|------|
221
+ | `JobsDashboardScreen` | Filters + table + pause/resume schedules |
222
+ | `JobsJobDetail` | Attempts, error, retry / requeue / cancel |
223
+ | Home widget `jobs` | Counts of `dead` + `running`; link to screen |
224
+
225
+ example-frontend: `/admin/jobs`, `/admin/jobs/[id]`.
226
+ admin-spa: `/jobs`, `/jobs/[id]`.
227
+ `verify-ui-changes` on the example admin.
228
+
229
+ RBAC: add `jobs` to `terrenoStatements.admin` (alongside `runScripts`).
230
+
231
+ ## Phases
232
+
233
+ 1. **Tracer:** package, `define`/`enqueue`, Mongo claim, `startWorker`, one example job.
234
+ 2. **Reliability:** retries, DLQ, lock TTL, cancel signal, delayed `runAt`, cron schedules.
235
+ 3. **Runners:** `JobRunner`, custom fake, GCP Cloud Tasks, Vercel Queues, execute HTTP.
236
+ 4. **Admin:** routes + UI + example + spa.
237
+ 5. **Docs + publish:** Diátaxis, changelog, CI, `publish-on-tag`, seed/positioning.
238
+
239
+ ## Feature Flags & Migrations
240
+
241
+ New collections only. No user-data migration. Opt-in: apps that never register
242
+ `JobsApp` are unchanged.
243
+
244
+ ## Activity Log & User Updates
245
+
246
+ Each attempt is on `Job.attempts`. No end-user notifications.
247
+
248
+ ## Not Included / Future Work
249
+
250
+ - Comms automatic scheduled retries (`comms-abstraction` / admin dashboard).
251
+ - Webhook 200-then-queue (`inbound-webhooks`).
252
+ - Billing Stripe handler offload (`billing-stripe`).
253
+ - Vercel Workflows runner.
254
+ - Redis/BullMQ runner.
255
+ - Per-queue concurrency caps / rate limits beyond claim locks.
256
+ - Streaming logs over sockets (poll admin GET).
257
+
258
+ ## Docs in this slice
259
+
260
+ | Page | Change |
261
+ |------|--------|
262
+ | `docs/how-to/background-jobs.md` | **New.** Define, enqueue, worker vs API, Mongo vs GCP vs Vercel, execute auth, idempotency. |
263
+ | `docs/reference/jobs.md` | **New.** Options, models, routes, runners, env. |
264
+ | `docs/how-to/README.md` | Link the how-to. |
265
+ | `docs/reference/api.md` | Contrast `cronjob()` / `wrapScript` as process-local. |
266
+ | `docs/reference/environment-variables.md` | Jobs / Cloud Tasks / Vercel / execute audience. |
267
+ | `docs/how-to/admin-import-prebuilt.md` | `JobsApp` screen `jobs`. |
268
+ | `docs/explanation/roadmap-seed-issues.md` | IP + task URLs; `IP=job-queues`. |
269
+ | `docs/explanation/positioning.md` | Celery/ActiveJob row → shipped via `@terreno/jobs` when Complete. |
270
+ | `changelog/unreleased/job-queues.md` | Added. |
271
+ | `.rulesync/rules/jobs/00-jobs.md` | Package rule; `bun run rules` / `skills:sync`. |
272
+ | `.rulesync/skills/terreno-backend-api/SKILL.md` | Jobs vs scripts vs cron. |
273
+
274
+ ## Testing
275
+
276
+ Bun + `@terreno/test` Mongo. Do not mock `@terreno/api` or models. Cloud adapters:
277
+ inject fake Task/Queue clients. Execute route: signed vs unsigned. Claim: two
278
+ workers cannot run the same job. Restart: `running` with expired lock is
279
+ reclaimed. Recurring: timezone `America/New_York` vs `UTC` tick. Admin: 403
280
+ without `admin:jobs`. UI: `verify-ui-changes`.
281
+
282
+ ## Files to Create / Modify
283
+
284
+ - `jobs/` — new package (models, service, runners, routes, tests)
285
+ - Root `package.json` workspace + `jobs:*` scripts
286
+ - `api/src/rbac/statements.ts` — `admin: ["access", "runScripts", "viewBackgroundTasks", "jobs"]`
287
+ - `admin-frontend` — jobs screens + widgets + tests
288
+ - `admin-spa` — routes
289
+ - `example-backend` — `JobsApp` + worker entry + demo job
290
+ - `example-frontend` — admin routes + SDK regen if jobs admin paths are OpenAPI
291
+ - `.github/workflows/jobs-ci.yml`, `publish-on-tag.yml`, `cd.yml` path filters
292
+ - Docs/rules/changelog as in the table
293
+
294
+ ## Task List
295
+
296
+ See [docs/tasks/job-queues.md](../tasks/job-queues.md).
297
+
298
+ ## Acceptance Criteria
299
+
300
+ - [x] `enqueue` then kill the Node process; a new `startWorker()` runs the job.
301
+ Verified by bun test that simulates crash by leaving `pending` and starting
302
+ a second worker.
303
+ - [x] Handler throw retries until `maxAttempts` then `status: "dead"`. Admin
304
+ requeue returns it to `pending` and the worker runs it. Verified by bun tests
305
+ + admin UI verification.
306
+ - [x] Delayed `runAt` in the future is not claimed early; cron in a named IANA
307
+ timezone fires `nextRunAt` correctly. Verified by bun tests with frozen clocks
308
+ (Luxon).
309
+ - [x] Two overlapping `startWorker()` loops claim a job once. Verified by bun test.
310
+ - [x] Custom `JobRunner` receives `enqueue`; GCP and Vercel adapters create a
311
+ mocked platform task and `POST /jobs/execute` with valid auth runs the
312
+ handler, invalid auth 401. Verified by bun tests (no live cloud).
313
+ - [x] Admin Jobs screen lists jobs, opens detail, retries, requeues DLQ, cancels.
314
+ Verified by `verify-ui-changes` artifacts.
315
+ - [x] Apps without `JobsApp` behave as today (`cronjob`, scripts). Verified by
316
+ existing api/admin-backend tests remaining green.
317
+ - [x] How-to + jobs reference match the shipped API. Verified in Roast;
318
+ `bun run website:build` on the docs task.
319
+
320
+ ## Named assumptions
321
+
322
+ 1. Handlers are registered in every process that executes jobs (API and worker).
323
+ 2. JSON payloads only; no Buffer/stream jobs in v1.
324
+ 3. Unique `(name, idempotencyKey)` makes duplicate enqueue a no-op returning the
325
+ existing row.
326
+ 4. `failed` is a transient row state between attempts if we surface it; terminal
327
+ exhaustion is `dead`. Pick may use `pending` between retries only — then omit
328
+ `failed` as terminal. Prefer **pending-with-future-`runAt`** between attempts
329
+ and **`dead`** at exhaustion so the admin filter is simple.
330
+ 5. Positioning Celery row updates when the IP is **Complete**, not while Draft.