@rudderhq/server 0.6.6-canary.8 → 0.7.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 (426) hide show
  1. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  2. package/dist/bootstrap/register-api-routes.js +6 -0
  3. package/dist/bootstrap/register-api-routes.js.map +1 -1
  4. package/dist/bundled-plugins/plugin-linear/dist/worker.js +152 -9
  5. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
  6. package/dist/routes/access.d.ts.map +1 -1
  7. package/dist/routes/access.js +6 -0
  8. package/dist/routes/access.js.map +1 -1
  9. package/dist/routes/agents.management-routes.d.ts.map +1 -1
  10. package/dist/routes/agents.management-routes.js +22 -0
  11. package/dist/routes/agents.management-routes.js.map +1 -1
  12. package/dist/routes/ai-search.d.ts +3 -0
  13. package/dist/routes/ai-search.d.ts.map +1 -0
  14. package/dist/routes/ai-search.js +17 -0
  15. package/dist/routes/ai-search.js.map +1 -0
  16. package/dist/routes/app-builder.d.ts +3 -0
  17. package/dist/routes/app-builder.d.ts.map +1 -0
  18. package/dist/routes/app-builder.js +305 -0
  19. package/dist/routes/app-builder.js.map +1 -0
  20. package/dist/routes/browser.d.ts.map +1 -1
  21. package/dist/routes/browser.js +6 -2
  22. package/dist/routes/browser.js.map +1 -1
  23. package/dist/routes/chats.annotation-routes.d.ts.map +1 -1
  24. package/dist/routes/chats.annotation-routes.js +10 -1
  25. package/dist/routes/chats.annotation-routes.js.map +1 -1
  26. package/dist/routes/chats.d.ts.map +1 -1
  27. package/dist/routes/chats.js +1 -0
  28. package/dist/routes/chats.js.map +1 -1
  29. package/dist/routes/chats.runtime-controls.d.ts +1 -0
  30. package/dist/routes/chats.runtime-controls.d.ts.map +1 -1
  31. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  32. package/dist/routes/chats.stream-routes.js +52 -4
  33. package/dist/routes/chats.stream-routes.js.map +1 -1
  34. package/dist/routes/chats.stream-support.d.ts +1 -0
  35. package/dist/routes/chats.stream-support.d.ts.map +1 -1
  36. package/dist/routes/chats.stream-support.js +8 -0
  37. package/dist/routes/chats.stream-support.js.map +1 -1
  38. package/dist/routes/health.d.ts.map +1 -1
  39. package/dist/routes/health.js +1 -0
  40. package/dist/routes/health.js.map +1 -1
  41. package/dist/routes/index.d.ts +2 -0
  42. package/dist/routes/index.d.ts.map +1 -1
  43. package/dist/routes/index.js +2 -0
  44. package/dist/routes/index.js.map +1 -1
  45. package/dist/routes/issues.mutations.d.ts +1 -1
  46. package/dist/routes/issues.mutations.d.ts.map +1 -1
  47. package/dist/routes/issues.mutations.js +71 -7
  48. package/dist/routes/issues.mutations.js.map +1 -1
  49. package/dist/routes/onboarding.d.ts.map +1 -1
  50. package/dist/routes/onboarding.js +35 -32
  51. package/dist/routes/onboarding.js.map +1 -1
  52. package/dist/routes/orgs.js +1 -1
  53. package/dist/routes/orgs.js.map +1 -1
  54. package/dist/routes/product-analytics-collector.d.ts +10 -0
  55. package/dist/routes/product-analytics-collector.d.ts.map +1 -0
  56. package/dist/routes/product-analytics-collector.js +28 -0
  57. package/dist/routes/product-analytics-collector.js.map +1 -0
  58. package/dist/routes/product-analytics.d.ts +3 -0
  59. package/dist/routes/product-analytics.d.ts.map +1 -0
  60. package/dist/routes/product-analytics.js +169 -0
  61. package/dist/routes/product-analytics.js.map +1 -0
  62. package/dist/routes/website-metadata.d.ts +2 -2
  63. package/dist/routes/website-metadata.d.ts.map +1 -1
  64. package/dist/routes/website-metadata.js +15 -2
  65. package/dist/routes/website-metadata.js.map +1 -1
  66. package/dist/services/activity.d.ts +3 -1
  67. package/dist/services/activity.d.ts.map +1 -1
  68. package/dist/services/activity.js +55 -6
  69. package/dist/services/activity.js.map +1 -1
  70. package/dist/services/agent-run-context.d.ts.map +1 -1
  71. package/dist/services/agent-run-context.js +8 -2
  72. package/dist/services/agent-run-context.js.map +1 -1
  73. package/dist/services/agents.d.ts +84 -13
  74. package/dist/services/agents.d.ts.map +1 -1
  75. package/dist/services/agents.js +35 -1
  76. package/dist/services/agents.js.map +1 -1
  77. package/dist/services/ai-search.d.ts +6 -0
  78. package/dist/services/ai-search.d.ts.map +1 -0
  79. package/dist/services/ai-search.js +238 -0
  80. package/dist/services/ai-search.js.map +1 -0
  81. package/dist/services/ai-search.test.d.ts +2 -0
  82. package/dist/services/ai-search.test.d.ts.map +1 -0
  83. package/dist/services/ai-search.test.js +52 -0
  84. package/dist/services/ai-search.test.js.map +1 -0
  85. package/dist/services/app-builder.d.ts +13 -0
  86. package/dist/services/app-builder.d.ts.map +1 -0
  87. package/dist/services/app-builder.js +241 -0
  88. package/dist/services/app-builder.js.map +1 -0
  89. package/dist/services/approvals.d.ts +3 -3
  90. package/dist/services/automations.d.ts +1 -0
  91. package/dist/services/automations.d.ts.map +1 -1
  92. package/dist/services/automations.js +33 -1
  93. package/dist/services/automations.js.map +1 -1
  94. package/dist/services/browser-broker.d.ts +1 -0
  95. package/dist/services/browser-broker.d.ts.map +1 -1
  96. package/dist/services/browser-broker.js +34 -2
  97. package/dist/services/browser-broker.js.map +1 -1
  98. package/dist/services/browser-broker.test.js +73 -0
  99. package/dist/services/browser-broker.test.js.map +1 -1
  100. package/dist/services/budgets.d.ts +1 -6
  101. package/dist/services/budgets.d.ts.map +1 -1
  102. package/dist/services/budgets.js +30 -103
  103. package/dist/services/budgets.js.map +1 -1
  104. package/dist/services/chat-annotation-copy-lineage.d.ts.map +1 -1
  105. package/dist/services/chat-annotation-copy-lineage.js +6 -1
  106. package/dist/services/chat-annotation-copy-lineage.js.map +1 -1
  107. package/dist/services/chat-assistant.annotations.d.ts.map +1 -1
  108. package/dist/services/chat-assistant.annotations.js +4 -1
  109. package/dist/services/chat-assistant.annotations.js.map +1 -1
  110. package/dist/services/chat-assistant.annotations.test.js +18 -2
  111. package/dist/services/chat-assistant.annotations.test.js.map +1 -1
  112. package/dist/services/chat-family-groups.d.ts +1 -1
  113. package/dist/services/chat-family-groups.d.ts.map +1 -1
  114. package/dist/services/chat-family-groups.js +17 -1
  115. package/dist/services/chat-family-groups.js.map +1 -1
  116. package/dist/services/chat-inline-annotation-validation.d.ts.map +1 -1
  117. package/dist/services/chat-inline-annotation-validation.js +301 -8
  118. package/dist/services/chat-inline-annotation-validation.js.map +1 -1
  119. package/dist/services/chat-inline-annotations.test.js +249 -1
  120. package/dist/services/chat-inline-annotations.test.js.map +1 -1
  121. package/dist/services/chat-message-branch.d.ts +5 -0
  122. package/dist/services/chat-message-branch.d.ts.map +1 -0
  123. package/dist/services/chat-message-branch.js +9 -0
  124. package/dist/services/chat-message-branch.js.map +1 -0
  125. package/dist/services/chat-work-manifest.d.ts.map +1 -1
  126. package/dist/services/chat-work-manifest.js +6 -0
  127. package/dist/services/chat-work-manifest.js.map +1 -1
  128. package/dist/services/chats.create.d.ts +2 -0
  129. package/dist/services/chats.create.d.ts.map +1 -1
  130. package/dist/services/chats.create.js +76 -4
  131. package/dist/services/chats.create.js.map +1 -1
  132. package/dist/services/chats.d.ts +33 -13
  133. package/dist/services/chats.d.ts.map +1 -1
  134. package/dist/services/chats.inline-visual-persistence.d.ts.map +1 -1
  135. package/dist/services/chats.inline-visual-persistence.js +7 -0
  136. package/dist/services/chats.inline-visual-persistence.js.map +1 -1
  137. package/dist/services/chats.js +46 -24
  138. package/dist/services/chats.js.map +1 -1
  139. package/dist/services/dashboard.d.ts +0 -1
  140. package/dist/services/dashboard.d.ts.map +1 -1
  141. package/dist/services/dashboard.js +0 -1
  142. package/dist/services/dashboard.js.map +1 -1
  143. package/dist/services/heartbeat-run-summary.d.ts.map +1 -1
  144. package/dist/services/heartbeat-run-summary.js +6 -0
  145. package/dist/services/heartbeat-run-summary.js.map +1 -1
  146. package/dist/services/index.d.ts +2 -0
  147. package/dist/services/index.d.ts.map +1 -1
  148. package/dist/services/index.js +2 -0
  149. package/dist/services/index.js.map +1 -1
  150. package/dist/services/instance-settings.d.ts.map +1 -1
  151. package/dist/services/instance-settings.js +4 -6
  152. package/dist/services/instance-settings.js.map +1 -1
  153. package/dist/services/issue-approvals.d.ts +1 -1
  154. package/dist/services/issues.d.ts +2 -2
  155. package/dist/services/issues.d.ts.map +1 -1
  156. package/dist/services/issues.js +69 -2
  157. package/dist/services/issues.js.map +1 -1
  158. package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
  159. package/dist/services/knowledge-portability/organization-portability.import.js +1 -0
  160. package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
  161. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  162. package/dist/services/knowledge-portability/organization-skills.js +6 -2
  163. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  164. package/dist/services/legacy-operator-state.d.ts +14 -0
  165. package/dist/services/legacy-operator-state.d.ts.map +1 -0
  166. package/dist/services/legacy-operator-state.js +251 -0
  167. package/dist/services/legacy-operator-state.js.map +1 -0
  168. package/dist/services/local-account-auth.d.ts.map +1 -1
  169. package/dist/services/local-account-auth.js +21 -3
  170. package/dist/services/local-account-auth.js.map +1 -1
  171. package/dist/services/messenger-custom-groups.d.ts +15 -0
  172. package/dist/services/messenger-custom-groups.d.ts.map +1 -0
  173. package/dist/services/messenger-custom-groups.js +155 -0
  174. package/dist/services/messenger-custom-groups.js.map +1 -0
  175. package/dist/services/messenger-saved-views.d.ts +32 -8
  176. package/dist/services/messenger-saved-views.d.ts.map +1 -1
  177. package/dist/services/messenger-saved-views.js +67 -2
  178. package/dist/services/messenger-saved-views.js.map +1 -1
  179. package/dist/services/messenger.d.ts +3 -3
  180. package/dist/services/messenger.d.ts.map +1 -1
  181. package/dist/services/messenger.js +110 -166
  182. package/dist/services/messenger.js.map +1 -1
  183. package/dist/services/orgs.d.ts +11 -2
  184. package/dist/services/orgs.d.ts.map +1 -1
  185. package/dist/services/orgs.js +23 -1
  186. package/dist/services/orgs.js.map +1 -1
  187. package/dist/services/product-analytics-collector.d.ts +86 -0
  188. package/dist/services/product-analytics-collector.d.ts.map +1 -0
  189. package/dist/services/product-analytics-collector.js +172 -0
  190. package/dist/services/product-analytics-collector.js.map +1 -0
  191. package/dist/services/product-analytics-collector.test.d.ts +2 -0
  192. package/dist/services/product-analytics-collector.test.d.ts.map +1 -0
  193. package/dist/services/product-analytics-collector.test.js +81 -0
  194. package/dist/services/product-analytics-collector.test.js.map +1 -0
  195. package/dist/services/product-analytics.d.ts +385 -0
  196. package/dist/services/product-analytics.d.ts.map +1 -0
  197. package/dist/services/product-analytics.js +698 -0
  198. package/dist/services/product-analytics.js.map +1 -0
  199. package/dist/services/product-analytics.test.d.ts +2 -0
  200. package/dist/services/product-analytics.test.d.ts.map +1 -0
  201. package/dist/services/product-analytics.test.js +136 -0
  202. package/dist/services/product-analytics.test.js.map +1 -0
  203. package/dist/services/projects.d.ts +1 -1
  204. package/dist/services/runtime-kernel/heartbeat.d.ts +4 -0
  205. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  206. package/dist/services/runtime-kernel/heartbeat.js +53 -1
  207. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  208. package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -1
  209. package/dist/services/runtime-kernel/heartbeat.misc.js +5 -10
  210. package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -1
  211. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +3 -0
  212. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
  213. package/dist/services/runtime-kernel/heartbeat.terminal.js +56 -1
  214. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
  215. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  216. package/dist/services/runtime-kernel/heartbeat.wakeup.js +9 -2
  217. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  218. package/dist/services/secrets.d.ts +1 -1
  219. package/dist/services/side-chats.d.ts +3 -0
  220. package/dist/services/side-chats.d.ts.map +1 -1
  221. package/dist/services/side-chats.js +30 -15
  222. package/dist/services/side-chats.js.map +1 -1
  223. package/dist/services/website-metadata.d.ts +3 -0
  224. package/dist/services/website-metadata.d.ts.map +1 -1
  225. package/dist/services/website-metadata.js +331 -88
  226. package/dist/services/website-metadata.js.map +1 -1
  227. package/package.json +16 -14
  228. package/resources/bundled-skills/app-builder/SKILL.md +104 -0
  229. package/resources/bundled-skills/app-builder/agents/openai.yaml +14 -0
  230. package/resources/bundled-skills/app-builder/assets/scaffold/.env.example +3 -0
  231. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/%5F%5Frudder/health/route.ts +20 -0
  232. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/contacts/[id]/route.ts +39 -0
  233. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/contacts/route.ts +31 -0
  234. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/data/export/route.ts +19 -0
  235. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/data/import/route.ts +40 -0
  236. package/resources/bundled-skills/app-builder/assets/scaffold/app/globals.css +32 -0
  237. package/resources/bundled-skills/app-builder/assets/scaffold/app/layout.tsx +15 -0
  238. package/resources/bundled-skills/app-builder/assets/scaffold/app/page.tsx +9 -0
  239. package/resources/bundled-skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +292 -0
  240. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/button.tsx +42 -0
  241. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/card.tsx +19 -0
  242. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/input.tsx +17 -0
  243. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/label.tsx +6 -0
  244. package/resources/bundled-skills/app-builder/assets/scaffold/data/.gitkeep +1 -0
  245. package/resources/bundled-skills/app-builder/assets/scaffold/drizzle.config.ts +18 -0
  246. package/resources/bundled-skills/app-builder/assets/scaffold/instrumentation.ts +5 -0
  247. package/resources/bundled-skills/app-builder/assets/scaffold/lib/data-transfer.ts +44 -0
  248. package/resources/bundled-skills/app-builder/assets/scaffold/lib/db/client.ts +78 -0
  249. package/resources/bundled-skills/app-builder/assets/scaffold/lib/db/schema.ts +35 -0
  250. package/resources/bundled-skills/app-builder/assets/scaffold/lib/domain.ts +18 -0
  251. package/resources/bundled-skills/app-builder/assets/scaffold/lib/jobs/runner.ts +56 -0
  252. package/resources/bundled-skills/app-builder/assets/scaffold/lib/utils.ts +6 -0
  253. package/resources/bundled-skills/app-builder/assets/scaffold/migrations/0000_app_builder_foundation.sql +27 -0
  254. package/resources/bundled-skills/app-builder/assets/scaffold/migrations/meta/_journal.json +13 -0
  255. package/resources/bundled-skills/app-builder/assets/scaffold/next-env.d.ts +6 -0
  256. package/resources/bundled-skills/app-builder/assets/scaffold/next.config.ts +8 -0
  257. package/resources/bundled-skills/app-builder/assets/scaffold/package.json +47 -0
  258. package/resources/bundled-skills/app-builder/assets/scaffold/playwright.config.ts +30 -0
  259. package/resources/bundled-skills/app-builder/assets/scaffold/pnpm-lock.yaml +2687 -0
  260. package/resources/bundled-skills/app-builder/assets/scaffold/postcss.config.mjs +5 -0
  261. package/resources/bundled-skills/app-builder/assets/scaffold/rudder.app.json +32 -0
  262. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/migrate.ts +21 -0
  263. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/seed.ts +31 -0
  264. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/snapshot.ts +17 -0
  265. package/resources/bundled-skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +50 -0
  266. package/resources/bundled-skills/app-builder/assets/scaffold/tests/unit/data-transfer.test.ts +28 -0
  267. package/resources/bundled-skills/app-builder/assets/scaffold/tests/unit/domain.test.ts +23 -0
  268. package/resources/bundled-skills/app-builder/assets/scaffold/tsconfig.json +41 -0
  269. package/resources/bundled-skills/app-builder/assets/scaffold/vitest.config.ts +14 -0
  270. package/resources/bundled-skills/app-builder/evals/evals.json +65 -0
  271. package/resources/bundled-skills/app-builder/references/data-safety.md +38 -0
  272. package/resources/bundled-skills/app-builder/references/design-guidelines.md +20 -0
  273. package/resources/bundled-skills/app-builder/references/migrations-and-promotion.md +36 -0
  274. package/resources/bundled-skills/app-builder/references/scaffold-contract.md +77 -0
  275. package/resources/bundled-skills/app-builder/references/verification.md +23 -0
  276. package/resources/bundled-skills/app-builder/scripts/scaffold.mjs +58 -0
  277. package/resources/bundled-skills/app-builder/scripts/validate-manifest.mjs +63 -0
  278. package/skills/app-builder/SKILL.md +104 -0
  279. package/skills/app-builder/agents/openai.yaml +14 -0
  280. package/skills/app-builder/assets/scaffold/.env.example +3 -0
  281. package/skills/app-builder/assets/scaffold/app/api/%5F%5Frudder/health/route.ts +20 -0
  282. package/skills/app-builder/assets/scaffold/app/api/contacts/[id]/route.ts +39 -0
  283. package/skills/app-builder/assets/scaffold/app/api/contacts/route.ts +31 -0
  284. package/skills/app-builder/assets/scaffold/app/api/data/export/route.ts +19 -0
  285. package/skills/app-builder/assets/scaffold/app/api/data/import/route.ts +40 -0
  286. package/skills/app-builder/assets/scaffold/app/globals.css +32 -0
  287. package/skills/app-builder/assets/scaffold/app/layout.tsx +15 -0
  288. package/skills/app-builder/assets/scaffold/app/page.tsx +9 -0
  289. package/skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +292 -0
  290. package/skills/app-builder/assets/scaffold/components/ui/button.tsx +42 -0
  291. package/skills/app-builder/assets/scaffold/components/ui/card.tsx +19 -0
  292. package/skills/app-builder/assets/scaffold/components/ui/input.tsx +17 -0
  293. package/skills/app-builder/assets/scaffold/components/ui/label.tsx +6 -0
  294. package/skills/app-builder/assets/scaffold/data/.gitkeep +1 -0
  295. package/skills/app-builder/assets/scaffold/drizzle.config.ts +18 -0
  296. package/skills/app-builder/assets/scaffold/instrumentation.ts +5 -0
  297. package/skills/app-builder/assets/scaffold/lib/data-transfer.ts +44 -0
  298. package/skills/app-builder/assets/scaffold/lib/db/client.ts +78 -0
  299. package/skills/app-builder/assets/scaffold/lib/db/schema.ts +35 -0
  300. package/skills/app-builder/assets/scaffold/lib/domain.ts +18 -0
  301. package/skills/app-builder/assets/scaffold/lib/jobs/runner.ts +56 -0
  302. package/skills/app-builder/assets/scaffold/lib/utils.ts +6 -0
  303. package/skills/app-builder/assets/scaffold/migrations/0000_app_builder_foundation.sql +27 -0
  304. package/skills/app-builder/assets/scaffold/migrations/meta/_journal.json +13 -0
  305. package/skills/app-builder/assets/scaffold/next-env.d.ts +6 -0
  306. package/skills/app-builder/assets/scaffold/next.config.ts +8 -0
  307. package/skills/app-builder/assets/scaffold/package.json +47 -0
  308. package/skills/app-builder/assets/scaffold/playwright.config.ts +30 -0
  309. package/skills/app-builder/assets/scaffold/pnpm-lock.yaml +2687 -0
  310. package/skills/app-builder/assets/scaffold/postcss.config.mjs +5 -0
  311. package/skills/app-builder/assets/scaffold/rudder.app.json +32 -0
  312. package/skills/app-builder/assets/scaffold/scripts/migrate.ts +21 -0
  313. package/skills/app-builder/assets/scaffold/scripts/seed.ts +31 -0
  314. package/skills/app-builder/assets/scaffold/scripts/snapshot.ts +17 -0
  315. package/skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +50 -0
  316. package/skills/app-builder/assets/scaffold/tests/unit/data-transfer.test.ts +28 -0
  317. package/skills/app-builder/assets/scaffold/tests/unit/domain.test.ts +23 -0
  318. package/skills/app-builder/assets/scaffold/tsconfig.json +41 -0
  319. package/skills/app-builder/assets/scaffold/vitest.config.ts +14 -0
  320. package/skills/app-builder/evals/evals.json +65 -0
  321. package/skills/app-builder/references/data-safety.md +38 -0
  322. package/skills/app-builder/references/design-guidelines.md +20 -0
  323. package/skills/app-builder/references/migrations-and-promotion.md +36 -0
  324. package/skills/app-builder/references/scaffold-contract.md +77 -0
  325. package/skills/app-builder/references/verification.md +23 -0
  326. package/skills/app-builder/scripts/scaffold.mjs +58 -0
  327. package/skills/app-builder/scripts/validate-manifest.mjs +63 -0
  328. package/ui-dist/assets/LibraryLiveSurface-DeUIV5N_.js +1 -0
  329. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BdM6grBK.js → abnfDiagram-VRR7QNED-BaoLM9Pd.js} +1 -1
  330. package/ui-dist/assets/{arc-B8Tukeix.js → arc-Cx0L6o2z.js} +1 -1
  331. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-DP_lYJIS.js → architectureDiagram-ZJ3FMSHR-zVfZP7v4.js} +1 -1
  332. package/ui-dist/assets/{blockDiagram-677ZJIJ3-D_QKZNjP.js → blockDiagram-677ZJIJ3-BiWWFP9R.js} +1 -1
  333. package/ui-dist/assets/{c4Diagram-LMCZKHZV-DJwVavN4.js → c4Diagram-LMCZKHZV-CX65GSJ6.js} +1 -1
  334. package/ui-dist/assets/channel-CHc3ibOz.js +1 -0
  335. package/ui-dist/assets/{chunk-2Q5K7J3B-BjdKg2cx.js → chunk-2Q5K7J3B-CH6PlelX.js} +1 -1
  336. package/ui-dist/assets/{chunk-32BRIVSS-C_u4l725.js → chunk-32BRIVSS-C7ZEXYO8.js} +1 -1
  337. package/ui-dist/assets/{chunk-5VM5RSS4-DtzEQCc9.js → chunk-5VM5RSS4-CyInWuue.js} +1 -1
  338. package/ui-dist/assets/{chunk-EX3LRPZG-Ds_gCBVz.js → chunk-EX3LRPZG-D58qFIdM.js} +1 -1
  339. package/ui-dist/assets/{chunk-JWPE2WC7-CHX9VuQf.js → chunk-JWPE2WC7-CJ3Qx30f.js} +1 -1
  340. package/ui-dist/assets/{chunk-MOJQB5TN-D_pj9OWe.js → chunk-MOJQB5TN-BO4bKVLH.js} +1 -1
  341. package/ui-dist/assets/{chunk-RYQCIY6F-aMYmnUaJ.js → chunk-RYQCIY6F-DxBmxcYc.js} +1 -1
  342. package/ui-dist/assets/{chunk-V7JOEXUC-Db3LrL-P.js → chunk-V7JOEXUC-Chdnd6Lq.js} +1 -1
  343. package/ui-dist/assets/{chunk-VR4S4FIN-BssuUpMr.js → chunk-VR4S4FIN-GkW_x_pD.js} +1 -1
  344. package/ui-dist/assets/{chunk-XXDRQBXY-DVzY-_Tv.js → chunk-XXDRQBXY-BJyBGcSF.js} +1 -1
  345. package/ui-dist/assets/classDiagram-OUVF2IWQ-9iLZpz3t.js +1 -0
  346. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-9iLZpz3t.js +1 -0
  347. package/ui-dist/assets/{cose-bilkent-JH36ORCC-Ch-fM2pW.js → cose-bilkent-JH36ORCC-BWP2r8TX.js} +1 -1
  348. package/ui-dist/assets/{cynefin-VYW2F7L2-C7gJeesr.js → cynefin-VYW2F7L2-C08dfI3w.js} +1 -1
  349. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-BwMpD6aZ.js → cynefinDiagram-TSTJHNR4-h6dXjTaO.js} +1 -1
  350. package/ui-dist/assets/{dagre-VKFMJZFB-8TgHu2tj.js → dagre-VKFMJZFB-CWDgoFQD.js} +1 -1
  351. package/ui-dist/assets/{diagram-FQU43EPY-ChODej8C.js → diagram-FQU43EPY-CK1j48d-.js} +1 -1
  352. package/ui-dist/assets/{diagram-G47NLZAW-CVq1GXvV.js → diagram-G47NLZAW-CoSiG57q.js} +1 -1
  353. package/ui-dist/assets/{diagram-NH7WQ7WH-D1IO7xe3.js → diagram-NH7WQ7WH-DRG-xXGQ.js} +1 -1
  354. package/ui-dist/assets/{diagram-OA4YK3LP-CKlttJto.js → diagram-OA4YK3LP-D04mMwYs.js} +1 -1
  355. package/ui-dist/assets/{diagram-WEI45ONY-DigjsORo.js → diagram-WEI45ONY-ZkC1ZQMq.js} +1 -1
  356. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-CZwO7RGO.js → ebnfDiagram-CCIWWBDH-BxMIw9PZ.js} +1 -1
  357. package/ui-dist/assets/{erDiagram-Q63AITRT-C3fFVPod.js → erDiagram-Q63AITRT-B4Zwbn84.js} +1 -1
  358. package/ui-dist/assets/{flowDiagram-23GEKE2U-CpccWfFo.js → flowDiagram-23GEKE2U-D5pYz8Px.js} +1 -1
  359. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-g3KL--4W.js → ganttDiagram-NO4QXBWP-RSramodc.js} +1 -1
  360. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-Bn59AW1B.js → gitGraphDiagram-IHSO6WYX-BFQfCkMd.js} +1 -1
  361. package/ui-dist/assets/{graph-LZSMW9ku.js → graph-DuSS8poS.js} +1 -1
  362. package/ui-dist/assets/{index-CPIoQKcG.js → index-5tzBDABj.js} +1 -1
  363. package/ui-dist/assets/index-9U78M9Np.js +1 -0
  364. package/ui-dist/assets/index-B-NP8fOI.js +1 -0
  365. package/ui-dist/assets/index-BADa8mxG.js +1 -0
  366. package/ui-dist/assets/index-BGcgy63z.js +1 -0
  367. package/ui-dist/assets/index-BKXUkeeU.js +1 -0
  368. package/ui-dist/assets/{index-CrU5g0XU.js → index-BU0KLNTt.js} +588 -558
  369. package/ui-dist/assets/index-BpvytaKr.js +1 -0
  370. package/ui-dist/assets/index-CHbXMgL0.js +7 -0
  371. package/ui-dist/assets/index-Cyq94-QT.js +1 -0
  372. package/ui-dist/assets/{index-BI9d3zXw.js → index-DRbt_Svi.js} +2 -2
  373. package/ui-dist/assets/index-De3vaw_P.js +1 -0
  374. package/ui-dist/assets/index-DzdRgH-y.js +1 -0
  375. package/ui-dist/assets/index-QrRs-r-4.css +1 -0
  376. package/ui-dist/assets/index-olsHIcXs.js +1 -0
  377. package/ui-dist/assets/index-wZlfBtwh.js +1 -0
  378. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-uxPSmmcv.js → infoDiagram-FWYZ7A6U-f5i4pO3J.js} +1 -1
  379. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BeKQmaaL.js → ishikawaDiagram-FXEZZL3T-CVEdv5i_.js} +1 -1
  380. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-Cnqh1Fyp.js → journeyDiagram-5HDEW3XC-Cvp7vWcy.js} +1 -1
  381. package/ui-dist/assets/{kanban-definition-HUTT4EX6-DRvoG8e7.js → kanban-definition-HUTT4EX6-C6dURjjK.js} +1 -1
  382. package/ui-dist/assets/{layout-By7bOuHZ.js → layout-DxdOu9MY.js} +1 -1
  383. package/ui-dist/assets/{linear-D-kfwm1O.js → linear-CRi8RB_7.js} +1 -1
  384. package/ui-dist/assets/{map-d3zaWROJ.js → map-BlpDrw_l.js} +1 -1
  385. package/ui-dist/assets/{mermaid.core-1JtOXpMA.js → mermaid.core-2Gz9YusH.js} +4 -4
  386. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-D8Jge-_m.js → mindmap-definition-LN4V7U3C-BmSjJLTI.js} +1 -1
  387. package/ui-dist/assets/{pegDiagram-2B236MQR-UCd9-Gfd.js → pegDiagram-2B236MQR-BTUboUw8.js} +1 -1
  388. package/ui-dist/assets/{pieDiagram-ENE6RG2P-DffPYn-P.js → pieDiagram-ENE6RG2P-CVPP8SYv.js} +1 -1
  389. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-Ds1lQ25G.js → quadrantDiagram-ABIIQ3AL-BG4FmirT.js} +1 -1
  390. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-P_pLStbY.js → railroadDiagram-RFXS5EU6-DFRNo9hF.js} +1 -1
  391. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-E_1U7w_T.js → requirementDiagram-TGXJPOKE-DpnVF9qP.js} +1 -1
  392. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-DFgwdZz7.js → sankeyDiagram-HTMAVEWB-D1ETFdSE.js} +1 -1
  393. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbI2EnTH.js → sequenceDiagram-DBY2YBRQ-CNqzB_AL.js} +1 -1
  394. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-Axl0diWc.js → sizeCapture-X5ZJPWSS-DuoMCjdH.js} +1 -1
  395. package/ui-dist/assets/{stateDiagram-2N3HPSRC-DxZLENhX.js → stateDiagram-2N3HPSRC-fmHeKL8l.js} +1 -1
  396. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DajAVikh.js +1 -0
  397. package/ui-dist/assets/swimlanes-5IMT3BWC-hjosaIhT.js +2 -0
  398. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DJjG9OJ-.js +8 -0
  399. package/ui-dist/assets/{timeline-definition-FHXFAJF6-B0Ha5bZS.js → timeline-definition-FHXFAJF6-xoMEClc-.js} +1 -1
  400. package/ui-dist/assets/{vennDiagram-L72KCM5P-BskD_1N5.js → vennDiagram-L72KCM5P-sdSXQfX-.js} +1 -1
  401. package/ui-dist/assets/{wardleyDiagram-EHGQE667-1wE4bYQ8.js → wardleyDiagram-EHGQE667-BbQBSKlD.js} +1 -1
  402. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-DfcOAfb8.js → xychartDiagram-FW5EYKEG-BfAtadyK.js} +1 -1
  403. package/ui-dist/index.html +2 -2
  404. package/ui-dist/assets/LibraryLiveSurface-CUgoFYvG.js +0 -1
  405. package/ui-dist/assets/channel-Cd4sqN24.js +0 -1
  406. package/ui-dist/assets/classDiagram-OUVF2IWQ-Bi3tsM-q.js +0 -1
  407. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Bi3tsM-q.js +0 -1
  408. package/ui-dist/assets/index-BG46qMXw.js +0 -1
  409. package/ui-dist/assets/index-BLUJslyp.js +0 -1
  410. package/ui-dist/assets/index-BQv2nBgV.js +0 -1
  411. package/ui-dist/assets/index-BkbdH2n-.js +0 -1
  412. package/ui-dist/assets/index-BqhkUdAT.js +0 -1
  413. package/ui-dist/assets/index-C-qWhcfC.css +0 -1
  414. package/ui-dist/assets/index-C3e6slEn.js +0 -1
  415. package/ui-dist/assets/index-C5hYxLM9.js +0 -1
  416. package/ui-dist/assets/index-CLETiubs.js +0 -7
  417. package/ui-dist/assets/index-CLnCobCG.js +0 -1
  418. package/ui-dist/assets/index-CQLLvEua.js +0 -1
  419. package/ui-dist/assets/index-CVwGLbgm.js +0 -1
  420. package/ui-dist/assets/index-ChTD9DV4.js +0 -1
  421. package/ui-dist/assets/index-CiyYjAsy.js +0 -1
  422. package/ui-dist/assets/index-EqVfudOJ.js +0 -1
  423. package/ui-dist/assets/index-ZFb9ZHAH.js +0 -6
  424. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KzYOpJ1T.js +0 -1
  425. package/ui-dist/assets/swimlanes-5IMT3BWC-1G26qvUm.js +0 -2
  426. package/ui-dist/assets/swimlanesDiagram-G3AALYLV--Vi3kUvi.js +0 -8
@@ -0,0 +1,698 @@
1
+ import { productAnalyticsConsentLedger, productAnalyticsEvents, productAnalyticsInstallations, productAnalyticsOutbox, productAnalyticsWorkCycles, } from "@rudderhq/db";
2
+ import { and, asc, desc, eq, gte, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
3
+ import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
4
+ import { forbidden, unauthorized, unprocessable } from "../errors.js";
5
+ export const PRODUCT_ANALYTICS_EVENT_NAMES = [
6
+ "organization_created",
7
+ "human_work_started",
8
+ "run_started",
9
+ "run_succeeded",
10
+ "run_failed",
11
+ "output_ready",
12
+ "review_decision_recorded",
13
+ "account_created",
14
+ "desktop_authorized",
15
+ "local_server_connected",
16
+ "first_agent_ready",
17
+ "work_loop_completed",
18
+ "work_loop_invalidated",
19
+ ];
20
+ export const PRODUCT_ANALYTICS_PRODUCED_EVENT_NAMES = [
21
+ "organization_created",
22
+ "human_work_started",
23
+ "run_started",
24
+ "run_succeeded",
25
+ "run_failed",
26
+ "output_ready",
27
+ "first_agent_ready",
28
+ "work_loop_completed",
29
+ "work_loop_invalidated",
30
+ ];
31
+ export const PRODUCT_ANALYTICS_DERIVED_EVENT_NAMES = ["review_decision_recorded"];
32
+ export const PRODUCT_ANALYTICS_DEFERRED_EVENT_NAMES = ["account_created", "desktop_authorized", "local_server_connected"];
33
+ export function productAnalyticsRunTerminalEventName(status) {
34
+ if (status === "succeeded")
35
+ return "run_succeeded";
36
+ if (status === "failed" || status === "cancelled" || status === "timed_out")
37
+ return "run_failed";
38
+ return null;
39
+ }
40
+ const EVENT_NAME_SET = new Set(PRODUCT_ANALYTICS_EVENT_NAMES);
41
+ const CONFIDENCE_SET = new Set(["exact", "derived", "unknown"]);
42
+ const ACTOR_TYPE_SET = new Set(["human", "agent", "system", "automation"]);
43
+ // Unknown keys are rejected by the event-specific allowlist. Keep this scan
44
+ // focused on content-bearing names so safe dimensions such as creation_path do
45
+ // not get rejected by a substring match.
46
+ const SENSITIVE_PROPERTY_KEY = /(prompt|transcript|title|description|body|content|url|token|secret|password|credential|email|hostname|username)/i;
47
+ const EVENT_PROPERTY_ALLOWLIST = {
48
+ organization_created: new Set([
49
+ "creation_path",
50
+ "template_kind",
51
+ "is_first_organization",
52
+ "is_user_initiated",
53
+ ]),
54
+ human_work_started: new Set(["work_surface", "origin"]),
55
+ run_started: new Set(["run_kind", "runtime", "attempt_kind"]),
56
+ run_succeeded: new Set(["run_kind", "runtime", "attempt_kind"]),
57
+ run_failed: new Set(["run_kind", "runtime", "attempt_kind", "terminal_status"]),
58
+ output_ready: new Set(["output_kind"]),
59
+ review_decision_recorded: new Set(["decision", "review_surface"]),
60
+ account_created: new Set(["auth_method", "is_migrated", "acquisition_source"]),
61
+ desktop_authorized: new Set(["client_kind", "release_channel", "coarse_platform", "authorization_kind"]),
62
+ local_server_connected: new Set(["connection_kind"]),
63
+ first_agent_ready: new Set(["runtime_adapter", "is_default_agent", "preflight_result_code", "is_first_for_installation"]),
64
+ work_loop_completed: new Set(["work_surface", "origin", "review_required", "attempt_count", "duration_bucket", "cost_bucket", "output_kind", "is_first_loop"]),
65
+ work_loop_invalidated: new Set(["reason_code", "duration_bucket"]),
66
+ };
67
+ async function resolveProductAnalyticsInstallationId(db, installationId) {
68
+ if (installationId)
69
+ return installationId;
70
+ if (typeof db.select !== "function")
71
+ return null;
72
+ const [registered] = await db.select({ installationId: productAnalyticsInstallations.installationId })
73
+ .from(productAnalyticsInstallations)
74
+ .orderBy(asc(productAnalyticsInstallations.createdAt))
75
+ .limit(1);
76
+ return registered?.installationId ?? null;
77
+ }
78
+ function assertBoundedText(value, field, maxLength) {
79
+ if (value.length === 0 || value.length > maxLength) {
80
+ throw unprocessable(`Product analytics ${field} is invalid`);
81
+ }
82
+ }
83
+ function validateProperties(eventName, properties) {
84
+ const allowed = EVENT_PROPERTY_ALLOWLIST[eventName];
85
+ for (const [key, value] of Object.entries(properties)) {
86
+ if (!allowed.has(key) || SENSITIVE_PROPERTY_KEY.test(key)) {
87
+ throw unprocessable(`Product analytics property is not allowed: ${key}`);
88
+ }
89
+ if (typeof value === "string")
90
+ assertBoundedText(value, `property ${key}`, 80);
91
+ if (typeof value === "number" && (!Number.isFinite(value) || Math.abs(value) > 1_000_000)) {
92
+ throw unprocessable(`Product analytics property is invalid: ${key}`);
93
+ }
94
+ if (typeof value === "object" && value !== null) {
95
+ throw unprocessable(`Product analytics property must be scalar: ${key}`);
96
+ }
97
+ }
98
+ return properties;
99
+ }
100
+ function validateEvent(input) {
101
+ if (!EVENT_NAME_SET.has(input.eventName)) {
102
+ throw unprocessable(`Unknown product analytics event: ${input.eventName}`);
103
+ }
104
+ if (input.schemaVersion !== undefined && input.schemaVersion !== 1) {
105
+ throw unprocessable("Unsupported product analytics schema version");
106
+ }
107
+ if (!CONFIDENCE_SET.has(input.confidence))
108
+ throw unprocessable("Product analytics confidence is invalid");
109
+ if (!ACTOR_TYPE_SET.has(input.actorType))
110
+ throw unprocessable("Product analytics actor type is invalid");
111
+ if (input.completionRevision !== undefined && input.completionRevision !== null && (!Number.isInteger(input.completionRevision) || input.completionRevision < 1)) {
112
+ throw unprocessable("Product analytics completion revision is invalid");
113
+ }
114
+ assertBoundedText(input.sourceTransition, "source transition", 120);
115
+ assertBoundedText(input.dedupeKey, "dedupe key", 256);
116
+ if (input.actorId !== undefined && input.actorId !== null)
117
+ assertBoundedText(input.actorId, "actor id", 200);
118
+ if (input.entityType !== undefined && input.entityType !== null)
119
+ assertBoundedText(input.entityType, "entity type", 80);
120
+ if (input.entityId !== undefined && input.entityId !== null)
121
+ assertBoundedText(input.entityId, "entity id", 200);
122
+ if (input.occurredAt && Number.isNaN(input.occurredAt.getTime())) {
123
+ throw unprocessable("Product analytics occurredAt is invalid");
124
+ }
125
+ validateProperties(input.eventName, input.properties ?? {});
126
+ }
127
+ export async function recordProductAnalyticsEvent(db, input) {
128
+ validateEvent(input);
129
+ const installationId = await resolveProductAnalyticsInstallationId(db, input.installationId);
130
+ const localUserId = input.localUserId ?? (input.actorType === "human" ? input.actorId ?? null : null);
131
+ const rows = await db
132
+ .insert(productAnalyticsEvents)
133
+ .values({
134
+ orgId: input.orgId,
135
+ installationId,
136
+ eventName: input.eventName,
137
+ schemaVersion: input.schemaVersion ?? 1,
138
+ occurredAt: input.occurredAt ?? new Date(),
139
+ sourceTransition: input.sourceTransition,
140
+ confidence: input.confidence,
141
+ actorType: input.actorType,
142
+ actorId: input.actorId ?? null,
143
+ localUserId,
144
+ environment: input.environment ?? (process.env.NODE_ENV === "production" ? "production" : "development"),
145
+ appVersion: input.appVersion ?? "unknown",
146
+ releaseChannel: input.releaseChannel ?? "stable",
147
+ deploymentMode: input.deploymentMode ?? "self_hosted",
148
+ origin: input.origin ?? (input.actorType === "human" ? "human" : input.actorType === "automation" ? "automation" : "system"),
149
+ workSurface: input.workSurface ?? null,
150
+ workId: input.workId ?? input.entityId ?? null,
151
+ workCycleId: input.workCycleId ?? null,
152
+ rootRunId: input.rootRunId ?? null,
153
+ runId: input.runId ?? null,
154
+ completionRevision: input.completionRevision ?? null,
155
+ entityType: input.entityType ?? null,
156
+ entityId: input.entityId ?? null,
157
+ dedupeKey: input.dedupeKey,
158
+ properties: input.properties ?? {},
159
+ isBackfill: input.isBackfill ?? false,
160
+ })
161
+ .onConflictDoNothing({ target: productAnalyticsEvents.dedupeKey })
162
+ .returning({ id: productAnalyticsEvents.id });
163
+ if (rows[0] && installationId) {
164
+ await enqueueProductAnalyticsEvent(db, {
165
+ eventId: rows[0].id,
166
+ installationId,
167
+ localUserId,
168
+ });
169
+ }
170
+ if (rows[0] && input.workCycleId && typeof db.update === "function") {
171
+ if (input.eventName === "output_ready") {
172
+ await db.update(productAnalyticsWorkCycles).set({ outputReadyAt: input.occurredAt ?? new Date(), updatedAt: new Date() })
173
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
174
+ }
175
+ if (input.eventName === "review_decision_recorded") {
176
+ const decision = input.properties?.decision;
177
+ await db.update(productAnalyticsWorkCycles).set({ reviewDecision: typeof decision === "string" ? decision : null, updatedAt: new Date() })
178
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
179
+ }
180
+ }
181
+ return rows[0] ?? null;
182
+ }
183
+ export async function enqueueProductAnalyticsEvent(db, input) {
184
+ const [installation] = await db.select({ mode: productAnalyticsInstallations.mode }).from(productAnalyticsInstallations)
185
+ .where(eq(productAnalyticsInstallations.installationId, input.installationId)).limit(1);
186
+ if (!installation || installation.mode === "off")
187
+ return null;
188
+ const scope = installation.mode === "account_linked" ? "account_linked_user" : "anonymous_installation";
189
+ const conditions = [eq(productAnalyticsConsentLedger.installationId, input.installationId), eq(productAnalyticsConsentLedger.scope, scope), eq(productAnalyticsConsentLedger.decision, "granted")];
190
+ conditions.push(scope === "account_linked_user" && input.localUserId ? eq(productAnalyticsConsentLedger.localUserId, input.localUserId) : isNull(productAnalyticsConsentLedger.localUserId));
191
+ const [consent] = await db.select().from(productAnalyticsConsentLedger).where(and(...conditions)).orderBy(desc(productAnalyticsConsentLedger.consentEpoch)).limit(1);
192
+ if (!consent)
193
+ return null;
194
+ const [row] = await db.insert(productAnalyticsOutbox).values({
195
+ eventId: input.eventId,
196
+ installationId: input.installationId,
197
+ deliveryMode: installation.mode,
198
+ consentScope: scope,
199
+ consentedLocalUserId: consent.localUserId ?? null,
200
+ consentVersion: consent.policyVersion,
201
+ consentEpoch: consent.consentEpoch,
202
+ }).onConflictDoNothing().returning();
203
+ return row ?? null;
204
+ }
205
+ export async function ensureProductAnalyticsWorkCycle(db, input) {
206
+ const [row] = await db
207
+ .insert(productAnalyticsWorkCycles)
208
+ .values({
209
+ orgId: input.orgId,
210
+ workSurface: input.workSurface,
211
+ workId: input.workId,
212
+ workCycleId: input.workCycleId,
213
+ actorId: input.actorId ?? null,
214
+ origin: input.origin ?? "human",
215
+ startedAt: input.startedAt ?? new Date(),
216
+ })
217
+ .onConflictDoNothing({ target: [productAnalyticsWorkCycles.orgId, productAnalyticsWorkCycles.workCycleId] })
218
+ .returning();
219
+ if (row)
220
+ return row;
221
+ const [existing] = await db
222
+ .select()
223
+ .from(productAnalyticsWorkCycles)
224
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)))
225
+ .limit(1);
226
+ return existing ?? null;
227
+ }
228
+ export async function completeProductAnalyticsWorkCycle(db, input) {
229
+ return db.transaction(async (tx) => {
230
+ const cycle = await ensureProductAnalyticsWorkCycle(tx, input);
231
+ if (!cycle)
232
+ throw unprocessable("Product analytics work cycle is unavailable");
233
+ const [lockedCycle] = await tx.select().from(productAnalyticsWorkCycles)
234
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)))
235
+ .for("update");
236
+ if (!lockedCycle)
237
+ throw unprocessable("Product analytics work cycle is unavailable");
238
+ const [successfulRuns] = await tx.select({ count: sql `count(*)` }).from(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.orgId, input.orgId), eq(productAnalyticsEvents.workCycleId, input.workCycleId), eq(productAnalyticsEvents.eventName, "run_succeeded")));
239
+ const [readyOutputs] = await tx.select({ count: sql `count(*)` }).from(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.orgId, input.orgId), eq(productAnalyticsEvents.workCycleId, input.workCycleId), eq(productAnalyticsEvents.eventName, "output_ready")));
240
+ const reviewRequired = input.properties?.review_required === true;
241
+ const [approvedReviews] = reviewRequired
242
+ ? await tx.select({ count: sql `count(*)` }).from(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.orgId, input.orgId), eq(productAnalyticsEvents.workCycleId, input.workCycleId), eq(productAnalyticsEvents.eventName, "review_decision_recorded"), sql `${productAnalyticsEvents.properties}->>'decision' = 'approve'`))
243
+ : [{ count: 1 }];
244
+ if (Number(successfulRuns?.count ?? 0) < 1 || Number(readyOutputs?.count ?? 0) < 1 || Number(approvedReviews?.count ?? 0) < 1) {
245
+ throw unprocessable("Product analytics work cycle does not satisfy completion gates");
246
+ }
247
+ if (lockedCycle.state === "completed" && lockedCycle.invalidatedAt === null) {
248
+ return { event: null, revision: lockedCycle.completionRevision };
249
+ }
250
+ const revision = (lockedCycle.completionRevision ?? 0) + 1;
251
+ const event = await recordProductAnalyticsEvent(tx, {
252
+ orgId: input.orgId,
253
+ eventName: "work_loop_completed",
254
+ occurredAt: new Date(),
255
+ sourceTransition: input.sourceTransition,
256
+ confidence: "exact",
257
+ actorType: input.actorType,
258
+ actorId: input.actorId ?? null,
259
+ entityType: input.workSurface,
260
+ entityId: input.workId,
261
+ workSurface: input.workSurface,
262
+ workId: input.workId,
263
+ workCycleId: input.workCycleId,
264
+ rootRunId: input.rootRunId ?? null,
265
+ completionRevision: revision,
266
+ origin: input.origin ?? "human",
267
+ dedupeKey: `work_loop_completed:${input.workCycleId}:${revision}`,
268
+ properties: input.properties ?? { work_surface: input.workSurface, origin: input.origin ?? "human", is_first_loop: revision === 1 },
269
+ });
270
+ await tx
271
+ .update(productAnalyticsWorkCycles)
272
+ .set({ state: "completed", completionRevision: revision, completedAt: new Date(), invalidatedAt: null, updatedAt: new Date() })
273
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
274
+ return { event, revision };
275
+ });
276
+ }
277
+ export async function invalidateProductAnalyticsWorkCycle(db, input) {
278
+ return db.transaction(async (tx) => {
279
+ const [cycle] = await tx.select().from(productAnalyticsWorkCycles)
280
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)))
281
+ .for("update");
282
+ if (!cycle || cycle.completionRevision < input.completionRevision)
283
+ return null;
284
+ const event = await recordProductAnalyticsEvent(tx, {
285
+ orgId: input.orgId,
286
+ eventName: "work_loop_invalidated",
287
+ occurredAt: input.occurredAt ?? new Date(),
288
+ sourceTransition: "work_loop.invalidate",
289
+ confidence: "exact",
290
+ actorType: "system",
291
+ entityType: input.workSurface,
292
+ entityId: input.workId,
293
+ workSurface: input.workSurface,
294
+ workId: input.workId,
295
+ workCycleId: input.workCycleId,
296
+ completionRevision: input.completionRevision,
297
+ dedupeKey: `work_loop_invalidated:${input.workCycleId}:${input.completionRevision}:${input.reasonCode}`,
298
+ properties: { reason_code: input.reasonCode },
299
+ });
300
+ await tx.update(productAnalyticsWorkCycles).set({ state: "invalidated", invalidatedAt: input.occurredAt ?? new Date(), updatedAt: new Date() })
301
+ .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
302
+ return event;
303
+ });
304
+ }
305
+ export async function registerProductAnalyticsInstallation(db, input) {
306
+ assertBoundedText(input.installationId, "installation id", 200);
307
+ if (input.mode && input.mode !== "off") {
308
+ throw unprocessable("Product analytics mode requires an explicit consent ledger decision");
309
+ }
310
+ const secret = input.installationSecret ?? randomBytes(32).toString("hex");
311
+ const secretHash = createHash("sha256").update(secret).digest("hex");
312
+ const [row] = await db.insert(productAnalyticsInstallations).values({
313
+ installationId: input.installationId,
314
+ installationSecretHash: secretHash,
315
+ mode: input.mode ?? "off",
316
+ }).onConflictDoNothing({ target: productAnalyticsInstallations.installationId }).returning();
317
+ return { installation: row ?? null, installationSecret: row ? secret : null };
318
+ }
319
+ export async function assertProductAnalyticsInstallationSecret(db, installationId, installationSecret) {
320
+ assertBoundedText(installationSecret, "installation secret", 256);
321
+ const [installation] = await db.select({ secretHash: productAnalyticsInstallations.installationSecretHash })
322
+ .from(productAnalyticsInstallations)
323
+ .where(eq(productAnalyticsInstallations.installationId, installationId))
324
+ .limit(1);
325
+ if (!installation)
326
+ throw unauthorized("Product analytics installation is not registered");
327
+ const expected = Buffer.from(installation.secretHash, "utf8");
328
+ const actual = Buffer.from(createHash("sha256").update(installationSecret).digest("hex"), "utf8");
329
+ if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
330
+ throw forbidden("Product analytics installation credential is invalid");
331
+ }
332
+ }
333
+ export async function setProductAnalyticsInstallationMode(db, installationId, mode) {
334
+ const [row] = await db.update(productAnalyticsInstallations).set({ mode, updatedAt: new Date() })
335
+ .where(eq(productAnalyticsInstallations.installationId, installationId)).returning();
336
+ return row ?? null;
337
+ }
338
+ export async function reconcileProductAnalyticsInstallationMode(db, installationId) {
339
+ const consent = await db.select({ scope: productAnalyticsConsentLedger.scope, decision: productAnalyticsConsentLedger.decision })
340
+ .from(productAnalyticsConsentLedger)
341
+ .where(eq(productAnalyticsConsentLedger.installationId, installationId))
342
+ .orderBy(desc(productAnalyticsConsentLedger.consentEpoch));
343
+ const latestByScope = new Map();
344
+ for (const row of consent) {
345
+ if (!latestByScope.has(row.scope))
346
+ latestByScope.set(row.scope, row.decision);
347
+ }
348
+ const mode = latestByScope.get("account_linked_user") === "granted"
349
+ ? "account_linked"
350
+ : latestByScope.get("anonymous_installation") === "granted" ? "anonymous" : "off";
351
+ return setProductAnalyticsInstallationMode(db, installationId, mode);
352
+ }
353
+ export async function getProductAnalyticsInstallationState(db, installationId) {
354
+ const [installation] = await db.select().from(productAnalyticsInstallations)
355
+ .where(eq(productAnalyticsInstallations.installationId, installationId)).limit(1);
356
+ if (!installation)
357
+ return null;
358
+ const consent = await db.select().from(productAnalyticsConsentLedger)
359
+ .where(eq(productAnalyticsConsentLedger.installationId, installationId))
360
+ .orderBy(desc(productAnalyticsConsentLedger.decidedAt)).limit(20);
361
+ const [pending] = await db.select({ count: sql `count(*)` }).from(productAnalyticsOutbox)
362
+ .where(and(eq(productAnalyticsOutbox.installationId, installationId), inArray(productAnalyticsOutbox.state, ["pending", "claimed", "retry_wait", "failed_actionable"]), eq(productAnalyticsOutbox.deliveryMode, installation.mode)));
363
+ return { installation, consent, pendingCount: Number(pending?.count ?? 0) };
364
+ }
365
+ export async function recordProductAnalyticsConsent(db, input) {
366
+ return db.transaction(async (tx) => {
367
+ const [installation] = await tx.select({ installationId: productAnalyticsInstallations.installationId })
368
+ .from(productAnalyticsInstallations)
369
+ .where(eq(productAnalyticsInstallations.installationId, input.installationId))
370
+ .limit(1)
371
+ .for("update");
372
+ if (!installation)
373
+ throw unprocessable("Product analytics installation is not registered");
374
+ assertBoundedText(input.policyVersion, "consent policy version", 80);
375
+ if (input.localUserId)
376
+ assertBoundedText(input.localUserId, "consent local user id", 200);
377
+ const keyCondition = and(eq(productAnalyticsConsentLedger.installationId, input.installationId), eq(productAnalyticsConsentLedger.scope, input.scope), input.localUserId ? eq(productAnalyticsConsentLedger.localUserId, input.localUserId) : isNull(productAnalyticsConsentLedger.localUserId));
378
+ const [previous] = await tx.select({ consentEpoch: productAnalyticsConsentLedger.consentEpoch })
379
+ .from(productAnalyticsConsentLedger).where(keyCondition).orderBy(desc(productAnalyticsConsentLedger.consentEpoch)).limit(1).for("update");
380
+ const epoch = (previous?.consentEpoch ?? 0) + 1;
381
+ const [row] = await tx.insert(productAnalyticsConsentLedger).values({
382
+ installationId: input.installationId,
383
+ scope: input.scope,
384
+ localUserId: input.localUserId ?? null,
385
+ decision: input.decision,
386
+ policyVersion: input.policyVersion,
387
+ consentEpoch: epoch,
388
+ decidedByLocalUserId: input.decidedByLocalUserId ?? null,
389
+ uploadFromAt: input.uploadFromAt ?? (input.decision === "granted" ? new Date() : null),
390
+ }).returning();
391
+ if (input.decision === "revoked") {
392
+ await tx.delete(productAnalyticsOutbox).where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.consentScope, input.scope), lt(productAnalyticsOutbox.consentEpoch, epoch), input.localUserId ? eq(productAnalyticsOutbox.consentedLocalUserId, input.localUserId) : isNull(productAnalyticsOutbox.consentedLocalUserId), sql `${productAnalyticsOutbox.state} <> 'delivered'`));
393
+ }
394
+ return row ?? null;
395
+ });
396
+ }
397
+ export async function claimProductAnalyticsOutbox(db, input) {
398
+ if (!input.installationId)
399
+ throw unprocessable("Product analytics installation id is required for claim");
400
+ if (!input.deliveryMode)
401
+ throw unprocessable("Product analytics delivery mode is required for claim");
402
+ const deliveryMode = input.deliveryMode;
403
+ const now = new Date();
404
+ const leaseExpiresAt = new Date(now.getTime() + (input.leaseSeconds ?? 300) * 1000);
405
+ const token = randomUUID();
406
+ const limit = Math.min(Math.max(input.limit ?? 20, 1), 100);
407
+ return db.transaction(async (tx) => {
408
+ const conditions = [
409
+ or(eq(productAnalyticsOutbox.state, "pending"), and(eq(productAnalyticsOutbox.state, "claimed"), lt(productAnalyticsOutbox.leaseExpiresAt, now)), eq(productAnalyticsOutbox.state, "retry_wait")),
410
+ lte(productAnalyticsOutbox.nextAttemptAt, now),
411
+ ];
412
+ conditions.push(eq(productAnalyticsOutbox.deliveryMode, deliveryMode));
413
+ conditions.push(eq(productAnalyticsOutbox.installationId, input.installationId));
414
+ if (input.consentedLocalUserId !== undefined)
415
+ conditions.push(input.consentedLocalUserId ? eq(productAnalyticsOutbox.consentedLocalUserId, input.consentedLocalUserId) : isNull(productAnalyticsOutbox.consentedLocalUserId));
416
+ const [first] = await tx.select({ eventId: productAnalyticsOutbox.eventId, consentEpoch: productAnalyticsOutbox.consentEpoch, consentedLocalUserId: productAnalyticsOutbox.consentedLocalUserId }).from(productAnalyticsOutbox).where(and(...conditions)).orderBy(productAnalyticsOutbox.nextAttemptAt).limit(1).for("update", { skipLocked: true });
417
+ if (!first)
418
+ return [];
419
+ conditions.push(eq(productAnalyticsOutbox.consentEpoch, first.consentEpoch));
420
+ conditions.push(first.consentedLocalUserId ? eq(productAnalyticsOutbox.consentedLocalUserId, first.consentedLocalUserId) : isNull(productAnalyticsOutbox.consentedLocalUserId));
421
+ const rows = await tx.select({ eventId: productAnalyticsOutbox.eventId }).from(productAnalyticsOutbox).where(and(...conditions)).orderBy(productAnalyticsOutbox.nextAttemptAt).limit(limit).for("update", { skipLocked: true });
422
+ if (rows.length === 0)
423
+ return [];
424
+ const scope = deliveryMode === "account_linked" ? "account_linked_user" : "anonymous_installation";
425
+ const consentConditions = [
426
+ eq(productAnalyticsConsentLedger.installationId, input.installationId),
427
+ eq(productAnalyticsConsentLedger.scope, scope),
428
+ eq(productAnalyticsConsentLedger.decision, "granted"),
429
+ eq(productAnalyticsConsentLedger.consentEpoch, first.consentEpoch),
430
+ first.consentedLocalUserId
431
+ ? eq(productAnalyticsConsentLedger.localUserId, first.consentedLocalUserId)
432
+ : isNull(productAnalyticsConsentLedger.localUserId),
433
+ ];
434
+ const [currentConsent] = await tx.select({ id: productAnalyticsConsentLedger.id })
435
+ .from(productAnalyticsConsentLedger)
436
+ .where(and(...consentConditions))
437
+ .limit(1);
438
+ if (!currentConsent) {
439
+ await tx.delete(productAnalyticsOutbox).where(inArray(productAnalyticsOutbox.eventId, rows.map((row) => row.eventId)));
440
+ return [];
441
+ }
442
+ await tx.update(productAnalyticsOutbox).set({ state: "claimed", claimToken: token, claimedAt: now, leaseExpiresAt, updatedAt: now }).where(inArray(productAnalyticsOutbox.eventId, rows.map((row) => row.eventId)));
443
+ return tx.select().from(productAnalyticsOutbox).where(inArray(productAnalyticsOutbox.eventId, rows.map((row) => row.eventId)));
444
+ });
445
+ }
446
+ export async function claimProductAnalyticsOutboxBatch(db, input) {
447
+ await assertProductAnalyticsInstallationSecret(db, input.installationId, input.installationSecret);
448
+ const rows = await claimProductAnalyticsOutbox(db, input);
449
+ if (rows.length === 0)
450
+ return null;
451
+ const eventRows = await db.select().from(productAnalyticsEvents).where(inArray(productAnalyticsEvents.id, rows.map((row) => row.eventId)));
452
+ const eventsById = new Map(eventRows.map((event) => [event.id, event]));
453
+ let payloads;
454
+ try {
455
+ payloads = rows.map((row) => {
456
+ const event = eventsById.get(row.eventId);
457
+ if (!event)
458
+ throw unprocessable("Product analytics outbox event is missing");
459
+ return buildProductAnalyticsExportPayload(input.installationSecret, event);
460
+ });
461
+ }
462
+ catch (error) {
463
+ await acknowledgeProductAnalyticsOutboxClaim(db, {
464
+ installationId: input.installationId,
465
+ installationSecret: input.installationSecret,
466
+ eventIds: rows.map((row) => row.eventId),
467
+ claimToken: rows[0]?.claimToken ?? "",
468
+ delivered: false,
469
+ errorCode: "payload_rejected",
470
+ });
471
+ throw error;
472
+ }
473
+ const first = rows[0];
474
+ if (!first.claimToken)
475
+ throw unprocessable("Product analytics outbox claim token is missing");
476
+ return {
477
+ claimToken: first.claimToken,
478
+ installationId: first.installationId,
479
+ deliveryMode: first.deliveryMode,
480
+ consentScope: first.consentScope,
481
+ consentEpoch: first.consentEpoch,
482
+ events: payloads,
483
+ };
484
+ }
485
+ export async function acknowledgeProductAnalyticsOutbox(db, input) {
486
+ const now = new Date();
487
+ const where = and(inArray(productAnalyticsOutbox.eventId, input.eventIds), eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.claimToken, input.claimToken), eq(productAnalyticsOutbox.deliveryMode, input.deliveryMode), eq(productAnalyticsOutbox.consentEpoch, input.consentEpoch), input.consentedLocalUserId ? eq(productAnalyticsOutbox.consentedLocalUserId, input.consentedLocalUserId) : isNull(productAnalyticsOutbox.consentedLocalUserId));
488
+ const delivered = input.delivered ?? true;
489
+ const rows = await db.select({ eventId: productAnalyticsOutbox.eventId, attemptCount: productAnalyticsOutbox.attemptCount }).from(productAnalyticsOutbox).where(where);
490
+ if (rows.length === 0)
491
+ return { updatedCount: 0, state: delivered ? "delivered" : "retry_wait" };
492
+ const maxAttemptCount = Math.max(...rows.map((row) => row.attemptCount));
493
+ const deadLettered = !delivered && maxAttemptCount + 1 >= 5;
494
+ await db.update(productAnalyticsOutbox).set({
495
+ state: delivered ? "delivered" : deadLettered ? "failed_actionable" : "retry_wait",
496
+ attemptCount: sql `${productAnalyticsOutbox.attemptCount} + 1`,
497
+ deliveredAt: delivered ? now : null,
498
+ deadLetteredAt: deadLettered ? now : null,
499
+ nextAttemptAt: delivered || deadLettered ? now : new Date(now.getTime() + Math.min(60 * 60_000, 2 ** Math.min(maxAttemptCount, 6) * 60_000)),
500
+ lastErrorCode: input.errorCode ?? null,
501
+ claimToken: null,
502
+ leaseExpiresAt: null,
503
+ updatedAt: now,
504
+ }).where(where);
505
+ return { updatedCount: rows.length, state: delivered ? "delivered" : deadLettered ? "failed_actionable" : "retry_wait" };
506
+ }
507
+ export async function acknowledgeProductAnalyticsOutboxClaim(db, input) {
508
+ await assertProductAnalyticsInstallationSecret(db, input.installationId, input.installationSecret);
509
+ const rows = await db.select({
510
+ eventId: productAnalyticsOutbox.eventId,
511
+ deliveryMode: productAnalyticsOutbox.deliveryMode,
512
+ consentEpoch: productAnalyticsOutbox.consentEpoch,
513
+ consentedLocalUserId: productAnalyticsOutbox.consentedLocalUserId,
514
+ }).from(productAnalyticsOutbox).where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.claimToken, input.claimToken), inArray(productAnalyticsOutbox.eventId, input.eventIds)));
515
+ if (rows.length === 0)
516
+ return { updatedCount: 0, state: input.delivered === false ? "retry_wait" : "delivered" };
517
+ const first = rows[0];
518
+ const sameClaim = rows.length === input.eventIds.length && rows.every((row) => (row.deliveryMode === first.deliveryMode
519
+ && row.consentEpoch === first.consentEpoch
520
+ && row.consentedLocalUserId === first.consentedLocalUserId));
521
+ if (!sameClaim)
522
+ throw forbidden("Product analytics ACK batch is mixed or incomplete");
523
+ return acknowledgeProductAnalyticsOutbox(db, {
524
+ ...input,
525
+ deliveryMode: first.deliveryMode,
526
+ consentEpoch: first.consentEpoch,
527
+ consentedLocalUserId: first.consentedLocalUserId,
528
+ });
529
+ }
530
+ export function pseudonymizeProductAnalyticsId(secret, value) {
531
+ return createHmac("sha256", secret).update(value).digest("hex");
532
+ }
533
+ export function buildProductAnalyticsExportPayload(secret, event) {
534
+ const properties = (event.properties ?? {});
535
+ const rawActorKind = String(event.actorType ?? "system");
536
+ const payload = {
537
+ eventId: String(event.id),
538
+ eventName: String(event.eventName),
539
+ schemaVersion: Number(event.schemaVersion ?? 1),
540
+ occurredAt: new Date(String(event.occurredAt)).toISOString(),
541
+ environment: String(event.environment ?? "production"),
542
+ appVersion: String(event.appVersion ?? "unknown"),
543
+ releaseChannel: String(event.releaseChannel ?? "stable"),
544
+ deploymentMode: String(event.deploymentMode ?? "self_hosted"),
545
+ pseudonymousInstallationId: event.installationId ? pseudonymizeProductAnalyticsId(secret, String(event.installationId)) : null,
546
+ actorKind: rawActorKind === "human" ? "user" : rawActorKind,
547
+ origin: String(event.origin ?? "system"),
548
+ pseudonymousOrgId: event.orgId ? pseudonymizeProductAnalyticsId(secret, String(event.orgId)) : null,
549
+ pseudonymousWorkId: event.workId ? pseudonymizeProductAnalyticsId(secret, String(event.workId)) : null,
550
+ pseudonymousWorkCycleId: event.workCycleId ? pseudonymizeProductAnalyticsId(secret, String(event.workCycleId)) : null,
551
+ pseudonymousRootRunId: event.rootRunId ? pseudonymizeProductAnalyticsId(secret, String(event.rootRunId)) : null,
552
+ pseudonymousRunId: event.runId ? pseudonymizeProductAnalyticsId(secret, String(event.runId)) : null,
553
+ completionRevision: event.completionRevision ?? null,
554
+ properties,
555
+ confidence: String(event.confidence ?? "unknown"),
556
+ isBackfill: Boolean(event.isBackfill),
557
+ };
558
+ const serialized = JSON.stringify(payload);
559
+ if (serialized.length > 2048)
560
+ throw unprocessable("Product analytics export payload is too large");
561
+ if (/(prompt|transcript|title|description|body|content|url|email|secret|token)/i.test(serialized))
562
+ throw unprocessable("Product analytics export payload contains sensitive data");
563
+ return payload;
564
+ }
565
+ function parseWindow(input) {
566
+ const to = input.to ?? new Date();
567
+ const windowDays = input.windowDays ?? 7;
568
+ if (!Number.isInteger(windowDays) || windowDays < 1 || windowDays > 366) {
569
+ throw unprocessable("Product analytics windowDays must be between 1 and 366");
570
+ }
571
+ const from = input.from ?? new Date(to.getTime() - windowDays * 24 * 60 * 60 * 1000);
572
+ if (Number.isNaN(from.getTime()) || Number.isNaN(to.getTime()) || from >= to) {
573
+ throw unprocessable("Product analytics time window is invalid");
574
+ }
575
+ return { from, to };
576
+ }
577
+ export function productAnalyticsService(db) {
578
+ return {
579
+ record: (input) => recordProductAnalyticsEvent(db, input),
580
+ summary: async (orgId, input = {}) => {
581
+ const window = parseWindow(input);
582
+ const where = and(eq(productAnalyticsEvents.orgId, orgId), gte(productAnalyticsEvents.occurredAt, window.from), lt(productAnalyticsEvents.occurredAt, window.to));
583
+ const [eventCounts, distinctHumanRows, totalRows, completedLoopRows, invalidatedLoopRows, meaningfulInstallationRows, productiveInstallationRows] = await Promise.all([
584
+ db
585
+ .select({ eventName: productAnalyticsEvents.eventName, count: sql `count(*)` })
586
+ .from(productAnalyticsEvents)
587
+ .where(where)
588
+ .groupBy(productAnalyticsEvents.eventName),
589
+ db
590
+ .select({ count: sql `count(distinct ${productAnalyticsEvents.actorId})` })
591
+ .from(productAnalyticsEvents)
592
+ .where(and(where, eq(productAnalyticsEvents.actorType, "human"), sql `${productAnalyticsEvents.actorId} is not null`)),
593
+ db
594
+ .select({ count: sql `count(*)` })
595
+ .from(productAnalyticsEvents)
596
+ .where(where),
597
+ db
598
+ .select({ count: sql `count(distinct ${productAnalyticsEvents.workCycleId} || ':' || ${productAnalyticsEvents.completionRevision})` })
599
+ .from(productAnalyticsEvents)
600
+ .where(and(where, eq(productAnalyticsEvents.eventName, "work_loop_completed"), eq(productAnalyticsEvents.origin, "human"), eq(productAnalyticsEvents.environment, "production"), sql `not exists (
601
+ select 1 from ${productAnalyticsWorkCycles} invalidated
602
+ where invalidated.org_id = ${productAnalyticsEvents.orgId}
603
+ and invalidated.work_cycle_id = ${productAnalyticsEvents.workCycleId}
604
+ and invalidated.state = 'invalidated'
605
+ )`)),
606
+ db
607
+ .select({ count: sql `count(*)` })
608
+ .from(productAnalyticsEvents)
609
+ .where(and(where, eq(productAnalyticsEvents.eventName, "work_loop_invalidated"))),
610
+ db
611
+ .select({ count: sql `count(distinct ${productAnalyticsEvents.installationId})` })
612
+ .from(productAnalyticsEvents)
613
+ .where(and(where, inArray(productAnalyticsEvents.eventName, ["human_work_started", "review_decision_recorded", "work_loop_completed"]), eq(productAnalyticsEvents.origin, "human"), eq(productAnalyticsEvents.environment, "production"), sql `${productAnalyticsEvents.installationId} is not null`)),
614
+ db
615
+ .select({ count: sql `count(distinct ${productAnalyticsEvents.installationId})` })
616
+ .from(productAnalyticsEvents)
617
+ .where(and(where, inArray(productAnalyticsEvents.eventName, ["output_ready", "work_loop_completed"]), eq(productAnalyticsEvents.origin, "human"), eq(productAnalyticsEvents.environment, "production"), sql `${productAnalyticsEvents.installationId} is not null`)),
618
+ ]);
619
+ const [approvedReviewRows] = await Promise.all([
620
+ db
621
+ .select({ count: sql `count(*)` })
622
+ .from(productAnalyticsEvents)
623
+ .where(and(where, eq(productAnalyticsEvents.eventName, "review_decision_recorded"), sql `${productAnalyticsEvents.properties}->>'decision' = 'approve'`)),
624
+ ]);
625
+ const counts = Object.fromEntries(eventCounts.map((row) => [row.eventName, Number(row.count)]));
626
+ return {
627
+ window: { from: window.from.toISOString(), to: window.to.toISOString() },
628
+ metrics: {
629
+ mutating_user_dau_local: Number(distinctHumanRows[0]?.count ?? 0),
630
+ meaningful_active_installations: Number(meaningfulInstallationRows[0]?.count ?? 0),
631
+ productive_installations: Number(productiveInstallationRows[0]?.count ?? 0),
632
+ successful_runs: counts.run_succeeded ?? 0,
633
+ failed_runs: counts.run_failed ?? 0,
634
+ human_work_started: counts.human_work_started ?? 0,
635
+ output_ready: counts.output_ready ?? 0,
636
+ review_decisions_recorded: counts.review_decision_recorded ?? 0,
637
+ reviewed_issue_completions: Number(approvedReviewRows[0]?.count ?? 0),
638
+ weekly_completed_work_loops: Number(completedLoopRows[0]?.count ?? 0),
639
+ work_loop_invalidations: Number(invalidatedLoopRows[0]?.count ?? 0),
640
+ ledger_event_count: Number(totalRows[0]?.count ?? 0),
641
+ },
642
+ dataQuality: {
643
+ source: "local_product_analytics_events",
644
+ producedExactEventNames: PRODUCT_ANALYTICS_PRODUCED_EVENT_NAMES,
645
+ producedDerivedEventNames: PRODUCT_ANALYTICS_DERIVED_EVENT_NAMES,
646
+ deferredEventNames: PRODUCT_ANALYTICS_DEFERRED_EVENT_NAMES,
647
+ completeDau: false,
648
+ completeNorthStar: false,
649
+ },
650
+ };
651
+ },
652
+ listEvents: async (orgId, input = {}) => {
653
+ const window = parseWindow(input);
654
+ const limit = Math.min(Math.max(Math.floor(input.limit ?? 100), 1), 500);
655
+ const conditions = [
656
+ eq(productAnalyticsEvents.orgId, orgId),
657
+ gte(productAnalyticsEvents.occurredAt, window.from),
658
+ lt(productAnalyticsEvents.occurredAt, window.to),
659
+ ];
660
+ if (input.eventName) {
661
+ if (!EVENT_NAME_SET.has(input.eventName))
662
+ throw unprocessable("Unknown product analytics event");
663
+ conditions.push(eq(productAnalyticsEvents.eventName, input.eventName));
664
+ }
665
+ return db
666
+ .select({
667
+ id: productAnalyticsEvents.id,
668
+ installationId: productAnalyticsEvents.installationId,
669
+ eventName: productAnalyticsEvents.eventName,
670
+ schemaVersion: productAnalyticsEvents.schemaVersion,
671
+ occurredAt: productAnalyticsEvents.occurredAt,
672
+ sourceTransition: productAnalyticsEvents.sourceTransition,
673
+ confidence: productAnalyticsEvents.confidence,
674
+ actorType: productAnalyticsEvents.actorType,
675
+ environment: productAnalyticsEvents.environment,
676
+ appVersion: productAnalyticsEvents.appVersion,
677
+ releaseChannel: productAnalyticsEvents.releaseChannel,
678
+ deploymentMode: productAnalyticsEvents.deploymentMode,
679
+ origin: productAnalyticsEvents.origin,
680
+ workSurface: productAnalyticsEvents.workSurface,
681
+ workId: productAnalyticsEvents.workId,
682
+ workCycleId: productAnalyticsEvents.workCycleId,
683
+ rootRunId: productAnalyticsEvents.rootRunId,
684
+ runId: productAnalyticsEvents.runId,
685
+ completionRevision: productAnalyticsEvents.completionRevision,
686
+ entityType: productAnalyticsEvents.entityType,
687
+ entityId: productAnalyticsEvents.entityId,
688
+ dedupeKey: productAnalyticsEvents.dedupeKey,
689
+ properties: productAnalyticsEvents.properties,
690
+ })
691
+ .from(productAnalyticsEvents)
692
+ .where(and(...conditions))
693
+ .orderBy(desc(productAnalyticsEvents.occurredAt), desc(productAnalyticsEvents.recordedAt))
694
+ .limit(limit);
695
+ },
696
+ };
697
+ }
698
+ //# sourceMappingURL=product-analytics.js.map