@rudderhq/server 0.6.6-canary.9 → 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 (281) hide show
  1. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  2. package/dist/bootstrap/register-api-routes.js +4 -0
  3. package/dist/bootstrap/register-api-routes.js.map +1 -1
  4. package/dist/bundled-plugins/plugin-linear/dist/worker.js +75 -4
  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/browser.d.ts.map +1 -1
  17. package/dist/routes/browser.js +6 -2
  18. package/dist/routes/browser.js.map +1 -1
  19. package/dist/routes/chats.annotation-routes.d.ts.map +1 -1
  20. package/dist/routes/chats.annotation-routes.js +10 -1
  21. package/dist/routes/chats.annotation-routes.js.map +1 -1
  22. package/dist/routes/chats.d.ts.map +1 -1
  23. package/dist/routes/chats.js +1 -0
  24. package/dist/routes/chats.js.map +1 -1
  25. package/dist/routes/chats.runtime-controls.d.ts +1 -0
  26. package/dist/routes/chats.runtime-controls.d.ts.map +1 -1
  27. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  28. package/dist/routes/chats.stream-routes.js +52 -4
  29. package/dist/routes/chats.stream-routes.js.map +1 -1
  30. package/dist/routes/chats.stream-support.d.ts +1 -0
  31. package/dist/routes/chats.stream-support.d.ts.map +1 -1
  32. package/dist/routes/chats.stream-support.js +8 -0
  33. package/dist/routes/chats.stream-support.js.map +1 -1
  34. package/dist/routes/index.d.ts +1 -0
  35. package/dist/routes/index.d.ts.map +1 -1
  36. package/dist/routes/index.js +1 -0
  37. package/dist/routes/index.js.map +1 -1
  38. package/dist/routes/issues.mutations.d.ts +1 -1
  39. package/dist/routes/issues.mutations.d.ts.map +1 -1
  40. package/dist/routes/issues.mutations.js +70 -2
  41. package/dist/routes/issues.mutations.js.map +1 -1
  42. package/dist/routes/onboarding.d.ts.map +1 -1
  43. package/dist/routes/onboarding.js +35 -32
  44. package/dist/routes/onboarding.js.map +1 -1
  45. package/dist/routes/orgs.js +1 -1
  46. package/dist/routes/orgs.js.map +1 -1
  47. package/dist/routes/product-analytics-collector.d.ts +10 -0
  48. package/dist/routes/product-analytics-collector.d.ts.map +1 -0
  49. package/dist/routes/product-analytics-collector.js +28 -0
  50. package/dist/routes/product-analytics-collector.js.map +1 -0
  51. package/dist/routes/product-analytics.d.ts +3 -0
  52. package/dist/routes/product-analytics.d.ts.map +1 -0
  53. package/dist/routes/product-analytics.js +169 -0
  54. package/dist/routes/product-analytics.js.map +1 -0
  55. package/dist/routes/website-metadata.d.ts +2 -2
  56. package/dist/routes/website-metadata.d.ts.map +1 -1
  57. package/dist/routes/website-metadata.js +15 -2
  58. package/dist/routes/website-metadata.js.map +1 -1
  59. package/dist/services/activity.d.ts +3 -1
  60. package/dist/services/activity.d.ts.map +1 -1
  61. package/dist/services/activity.js +55 -6
  62. package/dist/services/activity.js.map +1 -1
  63. package/dist/services/agents.d.ts +71 -0
  64. package/dist/services/agents.d.ts.map +1 -1
  65. package/dist/services/agents.js +35 -1
  66. package/dist/services/agents.js.map +1 -1
  67. package/dist/services/ai-search.d.ts +6 -0
  68. package/dist/services/ai-search.d.ts.map +1 -0
  69. package/dist/services/ai-search.js +238 -0
  70. package/dist/services/ai-search.js.map +1 -0
  71. package/dist/services/ai-search.test.d.ts +2 -0
  72. package/dist/services/ai-search.test.d.ts.map +1 -0
  73. package/dist/services/ai-search.test.js +52 -0
  74. package/dist/services/ai-search.test.js.map +1 -0
  75. package/dist/services/automations.d.ts +1 -0
  76. package/dist/services/automations.d.ts.map +1 -1
  77. package/dist/services/automations.js +33 -1
  78. package/dist/services/automations.js.map +1 -1
  79. package/dist/services/browser-broker.d.ts +1 -0
  80. package/dist/services/browser-broker.d.ts.map +1 -1
  81. package/dist/services/browser-broker.js +34 -2
  82. package/dist/services/browser-broker.js.map +1 -1
  83. package/dist/services/browser-broker.test.js +73 -0
  84. package/dist/services/browser-broker.test.js.map +1 -1
  85. package/dist/services/chat-family-groups.d.ts +1 -1
  86. package/dist/services/chat-family-groups.d.ts.map +1 -1
  87. package/dist/services/chat-family-groups.js +17 -1
  88. package/dist/services/chat-family-groups.js.map +1 -1
  89. package/dist/services/chat-inline-annotation-validation.d.ts.map +1 -1
  90. package/dist/services/chat-inline-annotation-validation.js +226 -1
  91. package/dist/services/chat-inline-annotation-validation.js.map +1 -1
  92. package/dist/services/chat-inline-annotations.test.js +136 -1
  93. package/dist/services/chat-inline-annotations.test.js.map +1 -1
  94. package/dist/services/chat-message-branch.d.ts +5 -0
  95. package/dist/services/chat-message-branch.d.ts.map +1 -0
  96. package/dist/services/chat-message-branch.js +9 -0
  97. package/dist/services/chat-message-branch.js.map +1 -0
  98. package/dist/services/chats.create.d.ts +2 -0
  99. package/dist/services/chats.create.d.ts.map +1 -1
  100. package/dist/services/chats.create.js +76 -4
  101. package/dist/services/chats.create.js.map +1 -1
  102. package/dist/services/chats.d.ts +21 -1
  103. package/dist/services/chats.d.ts.map +1 -1
  104. package/dist/services/chats.js +46 -24
  105. package/dist/services/chats.js.map +1 -1
  106. package/dist/services/heartbeat-run-summary.d.ts.map +1 -1
  107. package/dist/services/heartbeat-run-summary.js +6 -0
  108. package/dist/services/heartbeat-run-summary.js.map +1 -1
  109. package/dist/services/index.d.ts +1 -0
  110. package/dist/services/index.d.ts.map +1 -1
  111. package/dist/services/index.js +1 -0
  112. package/dist/services/index.js.map +1 -1
  113. package/dist/services/issues.d.ts.map +1 -1
  114. package/dist/services/issues.js +69 -2
  115. package/dist/services/issues.js.map +1 -1
  116. package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
  117. package/dist/services/knowledge-portability/organization-portability.import.js +1 -0
  118. package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
  119. package/dist/services/legacy-operator-state.d.ts.map +1 -1
  120. package/dist/services/legacy-operator-state.js +6 -0
  121. package/dist/services/legacy-operator-state.js.map +1 -1
  122. package/dist/services/local-account-auth.d.ts.map +1 -1
  123. package/dist/services/local-account-auth.js +5 -0
  124. package/dist/services/local-account-auth.js.map +1 -1
  125. package/dist/services/messenger-custom-groups.d.ts +15 -0
  126. package/dist/services/messenger-custom-groups.d.ts.map +1 -0
  127. package/dist/services/messenger-custom-groups.js +155 -0
  128. package/dist/services/messenger-custom-groups.js.map +1 -0
  129. package/dist/services/messenger-saved-views.d.ts +32 -8
  130. package/dist/services/messenger-saved-views.d.ts.map +1 -1
  131. package/dist/services/messenger-saved-views.js +67 -2
  132. package/dist/services/messenger-saved-views.js.map +1 -1
  133. package/dist/services/messenger.d.ts.map +1 -1
  134. package/dist/services/messenger.js +110 -166
  135. package/dist/services/messenger.js.map +1 -1
  136. package/dist/services/orgs.d.ts +10 -1
  137. package/dist/services/orgs.d.ts.map +1 -1
  138. package/dist/services/orgs.js +23 -1
  139. package/dist/services/orgs.js.map +1 -1
  140. package/dist/services/product-analytics-collector.d.ts +86 -0
  141. package/dist/services/product-analytics-collector.d.ts.map +1 -0
  142. package/dist/services/product-analytics-collector.js +172 -0
  143. package/dist/services/product-analytics-collector.js.map +1 -0
  144. package/dist/services/product-analytics-collector.test.d.ts +2 -0
  145. package/dist/services/product-analytics-collector.test.d.ts.map +1 -0
  146. package/dist/services/product-analytics-collector.test.js +81 -0
  147. package/dist/services/product-analytics-collector.test.js.map +1 -0
  148. package/dist/services/product-analytics.d.ts +385 -0
  149. package/dist/services/product-analytics.d.ts.map +1 -0
  150. package/dist/services/product-analytics.js +698 -0
  151. package/dist/services/product-analytics.js.map +1 -0
  152. package/dist/services/product-analytics.test.d.ts +2 -0
  153. package/dist/services/product-analytics.test.d.ts.map +1 -0
  154. package/dist/services/product-analytics.test.js +136 -0
  155. package/dist/services/product-analytics.test.js.map +1 -0
  156. package/dist/services/runtime-kernel/heartbeat.d.ts +4 -0
  157. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  158. package/dist/services/runtime-kernel/heartbeat.js +53 -1
  159. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  160. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +3 -0
  161. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
  162. package/dist/services/runtime-kernel/heartbeat.terminal.js +56 -1
  163. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
  164. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  165. package/dist/services/runtime-kernel/heartbeat.wakeup.js +9 -2
  166. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  167. package/dist/services/side-chats.d.ts +3 -0
  168. package/dist/services/side-chats.d.ts.map +1 -1
  169. package/dist/services/side-chats.js +12 -5
  170. package/dist/services/side-chats.js.map +1 -1
  171. package/dist/services/website-metadata.d.ts +3 -0
  172. package/dist/services/website-metadata.d.ts.map +1 -1
  173. package/dist/services/website-metadata.js +331 -88
  174. package/dist/services/website-metadata.js.map +1 -1
  175. package/package.json +16 -14
  176. package/resources/bundled-skills/app-builder/SKILL.md +31 -14
  177. package/resources/bundled-skills/app-builder/agents/openai.yaml +2 -2
  178. package/resources/bundled-skills/app-builder/evals/evals.json +12 -0
  179. package/resources/bundled-skills/app-builder/references/scaffold-contract.md +17 -2
  180. package/skills/app-builder/SKILL.md +31 -14
  181. package/skills/app-builder/agents/openai.yaml +2 -2
  182. package/skills/app-builder/evals/evals.json +12 -0
  183. package/skills/app-builder/references/scaffold-contract.md +17 -2
  184. package/ui-dist/assets/LibraryLiveSurface-DeUIV5N_.js +1 -0
  185. package/ui-dist/assets/{abnfDiagram-VRR7QNED-DmSFNqZA.js → abnfDiagram-VRR7QNED-BaoLM9Pd.js} +1 -1
  186. package/ui-dist/assets/{arc-DCySHdMS.js → arc-Cx0L6o2z.js} +1 -1
  187. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BL3egF1t.js → architectureDiagram-ZJ3FMSHR-zVfZP7v4.js} +1 -1
  188. package/ui-dist/assets/{blockDiagram-677ZJIJ3-Bh2L4BGz.js → blockDiagram-677ZJIJ3-BiWWFP9R.js} +1 -1
  189. package/ui-dist/assets/{c4Diagram-LMCZKHZV-BC0tQbna.js → c4Diagram-LMCZKHZV-CX65GSJ6.js} +1 -1
  190. package/ui-dist/assets/channel-CHc3ibOz.js +1 -0
  191. package/ui-dist/assets/{chunk-2Q5K7J3B-Du3BHUuM.js → chunk-2Q5K7J3B-CH6PlelX.js} +1 -1
  192. package/ui-dist/assets/{chunk-32BRIVSS--pEBkzQV.js → chunk-32BRIVSS-C7ZEXYO8.js} +1 -1
  193. package/ui-dist/assets/{chunk-5VM5RSS4-BPlwcWj7.js → chunk-5VM5RSS4-CyInWuue.js} +1 -1
  194. package/ui-dist/assets/{chunk-EX3LRPZG-CMdUJlKy.js → chunk-EX3LRPZG-D58qFIdM.js} +1 -1
  195. package/ui-dist/assets/{chunk-JWPE2WC7-gNumDpip.js → chunk-JWPE2WC7-CJ3Qx30f.js} +1 -1
  196. package/ui-dist/assets/{chunk-MOJQB5TN-D_J2XAWb.js → chunk-MOJQB5TN-BO4bKVLH.js} +1 -1
  197. package/ui-dist/assets/{chunk-RYQCIY6F-HUwWvMk8.js → chunk-RYQCIY6F-DxBmxcYc.js} +1 -1
  198. package/ui-dist/assets/{chunk-V7JOEXUC-BxIq5YO9.js → chunk-V7JOEXUC-Chdnd6Lq.js} +1 -1
  199. package/ui-dist/assets/{chunk-VR4S4FIN-Bt21PgEy.js → chunk-VR4S4FIN-GkW_x_pD.js} +1 -1
  200. package/ui-dist/assets/{chunk-XXDRQBXY-BMQqNb2F.js → chunk-XXDRQBXY-BJyBGcSF.js} +1 -1
  201. package/ui-dist/assets/classDiagram-OUVF2IWQ-9iLZpz3t.js +1 -0
  202. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-9iLZpz3t.js +1 -0
  203. package/ui-dist/assets/{cose-bilkent-JH36ORCC-IdO8qXrd.js → cose-bilkent-JH36ORCC-BWP2r8TX.js} +1 -1
  204. package/ui-dist/assets/{cynefin-VYW2F7L2-lliB__8b.js → cynefin-VYW2F7L2-C08dfI3w.js} +1 -1
  205. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-D6hYvTa_.js → cynefinDiagram-TSTJHNR4-h6dXjTaO.js} +1 -1
  206. package/ui-dist/assets/{dagre-VKFMJZFB-BvJ54nY-.js → dagre-VKFMJZFB-CWDgoFQD.js} +1 -1
  207. package/ui-dist/assets/{diagram-FQU43EPY-LwGme7tk.js → diagram-FQU43EPY-CK1j48d-.js} +1 -1
  208. package/ui-dist/assets/{diagram-G47NLZAW-DpFi3fY3.js → diagram-G47NLZAW-CoSiG57q.js} +1 -1
  209. package/ui-dist/assets/{diagram-NH7WQ7WH-BT3DHBTZ.js → diagram-NH7WQ7WH-DRG-xXGQ.js} +1 -1
  210. package/ui-dist/assets/{diagram-OA4YK3LP-PVjLB7c8.js → diagram-OA4YK3LP-D04mMwYs.js} +1 -1
  211. package/ui-dist/assets/{diagram-WEI45ONY-BFtM6fa4.js → diagram-WEI45ONY-ZkC1ZQMq.js} +1 -1
  212. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-cOP8mEWH.js → ebnfDiagram-CCIWWBDH-BxMIw9PZ.js} +1 -1
  213. package/ui-dist/assets/{erDiagram-Q63AITRT-D5Jo2rGL.js → erDiagram-Q63AITRT-B4Zwbn84.js} +1 -1
  214. package/ui-dist/assets/{flowDiagram-23GEKE2U-ywmEM2g3.js → flowDiagram-23GEKE2U-D5pYz8Px.js} +1 -1
  215. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-BU_eZKbI.js → ganttDiagram-NO4QXBWP-RSramodc.js} +1 -1
  216. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-BpjiL_Kc.js → gitGraphDiagram-IHSO6WYX-BFQfCkMd.js} +1 -1
  217. package/ui-dist/assets/{graph-Dm9rhfLh.js → graph-DuSS8poS.js} +1 -1
  218. package/ui-dist/assets/{index-Dd2bkmgp.js → index-5tzBDABj.js} +1 -1
  219. package/ui-dist/assets/index-9U78M9Np.js +1 -0
  220. package/ui-dist/assets/index-B-NP8fOI.js +1 -0
  221. package/ui-dist/assets/{index-hi6RkKKV.js → index-BADa8mxG.js} +1 -1
  222. package/ui-dist/assets/index-BGcgy63z.js +1 -0
  223. package/ui-dist/assets/index-BKXUkeeU.js +1 -0
  224. package/ui-dist/assets/{index-CcolbA6z.js → index-BU0KLNTt.js} +578 -550
  225. package/ui-dist/assets/index-BpvytaKr.js +1 -0
  226. package/ui-dist/assets/index-CHbXMgL0.js +7 -0
  227. package/ui-dist/assets/index-Cyq94-QT.js +1 -0
  228. package/ui-dist/assets/{index-Bh9XaX7-.js → index-DRbt_Svi.js} +2 -2
  229. package/ui-dist/assets/index-De3vaw_P.js +1 -0
  230. package/ui-dist/assets/index-DzdRgH-y.js +1 -0
  231. package/ui-dist/assets/index-QrRs-r-4.css +1 -0
  232. package/ui-dist/assets/index-olsHIcXs.js +1 -0
  233. package/ui-dist/assets/index-wZlfBtwh.js +1 -0
  234. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-Cy4F5ZoW.js → infoDiagram-FWYZ7A6U-f5i4pO3J.js} +1 -1
  235. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-Dz5yqGu9.js → ishikawaDiagram-FXEZZL3T-CVEdv5i_.js} +1 -1
  236. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-TwWo4pm4.js → journeyDiagram-5HDEW3XC-Cvp7vWcy.js} +1 -1
  237. package/ui-dist/assets/{kanban-definition-HUTT4EX6-DY0NB-vi.js → kanban-definition-HUTT4EX6-C6dURjjK.js} +1 -1
  238. package/ui-dist/assets/{layout-BORuuGEy.js → layout-DxdOu9MY.js} +1 -1
  239. package/ui-dist/assets/{linear-CC_dDONu.js → linear-CRi8RB_7.js} +1 -1
  240. package/ui-dist/assets/{map-CkTs9PaD.js → map-BlpDrw_l.js} +1 -1
  241. package/ui-dist/assets/{mermaid.core-BbvVSKV6.js → mermaid.core-2Gz9YusH.js} +4 -4
  242. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-oEqblcyT.js → mindmap-definition-LN4V7U3C-BmSjJLTI.js} +1 -1
  243. package/ui-dist/assets/{pegDiagram-2B236MQR-Be4dxJHH.js → pegDiagram-2B236MQR-BTUboUw8.js} +1 -1
  244. package/ui-dist/assets/{pieDiagram-ENE6RG2P-DEfT3YEZ.js → pieDiagram-ENE6RG2P-CVPP8SYv.js} +1 -1
  245. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-Cb6JOf0G.js → quadrantDiagram-ABIIQ3AL-BG4FmirT.js} +1 -1
  246. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-Cq69PTte.js → railroadDiagram-RFXS5EU6-DFRNo9hF.js} +1 -1
  247. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-uGctx0yQ.js → requirementDiagram-TGXJPOKE-DpnVF9qP.js} +1 -1
  248. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-D-Jn2GZ3.js → sankeyDiagram-HTMAVEWB-D1ETFdSE.js} +1 -1
  249. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-D0PNt4tF.js → sequenceDiagram-DBY2YBRQ-CNqzB_AL.js} +1 -1
  250. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-C5PkxxqV.js → sizeCapture-X5ZJPWSS-DuoMCjdH.js} +1 -1
  251. package/ui-dist/assets/{stateDiagram-2N3HPSRC-CEU6dEp8.js → stateDiagram-2N3HPSRC-fmHeKL8l.js} +1 -1
  252. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DajAVikh.js +1 -0
  253. package/ui-dist/assets/swimlanes-5IMT3BWC-hjosaIhT.js +2 -0
  254. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DJjG9OJ-.js +8 -0
  255. package/ui-dist/assets/{timeline-definition-FHXFAJF6-BzClran5.js → timeline-definition-FHXFAJF6-xoMEClc-.js} +1 -1
  256. package/ui-dist/assets/{vennDiagram-L72KCM5P-DCz0zpBU.js → vennDiagram-L72KCM5P-sdSXQfX-.js} +1 -1
  257. package/ui-dist/assets/{wardleyDiagram-EHGQE667-BCpjHnUs.js → wardleyDiagram-EHGQE667-BbQBSKlD.js} +1 -1
  258. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-PZly8tD0.js → xychartDiagram-FW5EYKEG-BfAtadyK.js} +1 -1
  259. package/ui-dist/index.html +2 -2
  260. package/ui-dist/assets/LibraryLiveSurface-CpsfHfNv.js +0 -1
  261. package/ui-dist/assets/channel-BkaaZhpy.js +0 -1
  262. package/ui-dist/assets/classDiagram-OUVF2IWQ-wAPxgAxC.js +0 -1
  263. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-wAPxgAxC.js +0 -1
  264. package/ui-dist/assets/index-B4-glB0m.js +0 -1
  265. package/ui-dist/assets/index-B77hSKzF.js +0 -1
  266. package/ui-dist/assets/index-BCtddpC-.js +0 -1
  267. package/ui-dist/assets/index-BJsfmJ3d.js +0 -6
  268. package/ui-dist/assets/index-CZQodrD7.js +0 -1
  269. package/ui-dist/assets/index-C_Zqz9dU.js +0 -1
  270. package/ui-dist/assets/index-CjAPsevc.js +0 -1
  271. package/ui-dist/assets/index-CzdqNgTk.js +0 -7
  272. package/ui-dist/assets/index-DPTcb3v2.js +0 -1
  273. package/ui-dist/assets/index-DRSP0xHL.js +0 -1
  274. package/ui-dist/assets/index-DV-n4C-H.js +0 -1
  275. package/ui-dist/assets/index-DryJkjHs.js +0 -1
  276. package/ui-dist/assets/index-TcdPtss-.js +0 -1
  277. package/ui-dist/assets/index-m9ZlZKZy.css +0 -1
  278. package/ui-dist/assets/index-wNtd8gYX.js +0 -1
  279. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-D13NNkEm.js +0 -1
  280. package/ui-dist/assets/swimlanes-5IMT3BWC-Bh0Yv4dw.js +0 -2
  281. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Ba5kuBbj.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