@opengeni/api-router 0.30.3 → 1.0.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 (434) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-source-session.d.ts +3 -0
  35. package/dist/editable-artifact-websocket.d.ts +10 -18
  36. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  37. package/dist/fatal-process-boundary.d.ts +25 -0
  38. package/dist/github-access.d.ts +31 -2
  39. package/dist/http/acting-person.d.ts +24 -0
  40. package/dist/http/api-contract-compat.d.ts +19 -0
  41. package/dist/http/api-error.d.ts +27 -1
  42. package/dist/http/cors.d.ts +17 -0
  43. package/dist/http/deprecation.d.ts +42 -0
  44. package/dist/http/interaction-control-error.d.ts +13 -0
  45. package/dist/http/keyed-admission.d.ts +12 -0
  46. package/dist/http/local-browser-boundary.d.ts +72 -0
  47. package/dist/http/metrics-listener.d.ts +22 -0
  48. package/dist/http/path-identifier.d.ts +17 -0
  49. package/dist/http/registered-route-label.d.ts +16 -0
  50. package/dist/http/request-body.d.ts +27 -0
  51. package/dist/http/request-source.d.ts +52 -0
  52. package/dist/http/sse.d.ts +17 -6
  53. package/dist/http/unmatched-route.d.ts +18 -0
  54. package/dist/http/user-content.d.ts +36 -0
  55. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  56. package/dist/index.d.ts +10 -3
  57. package/dist/index.js +1091 -162
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-endpoint-test.d.ts +20 -0
  60. package/dist/integrations/api-integrations.d.ts +2 -2
  61. package/dist/integrations/atlassian.d.ts +29 -3
  62. package/dist/integrations/connect-authority.d.ts +1 -0
  63. package/dist/integrations/connect-callback-return.d.ts +6 -0
  64. package/dist/integrations/fiken.d.ts +16 -2
  65. package/dist/integrations/github-app-connect.d.ts +47 -0
  66. package/dist/integrations/github-installation-proof.d.ts +3 -0
  67. package/dist/integrations/github-lens-connect.d.ts +116 -0
  68. package/dist/integrations/github-skill-source.d.ts +1 -5
  69. package/dist/integrations/google-drive.d.ts +24 -0
  70. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  71. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  72. package/dist/integrations/oauth-client.d.ts +178 -18
  73. package/dist/integrations/oauth-profiles.d.ts +222 -0
  74. package/dist/integrations/oauth-return-path.d.ts +1 -0
  75. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  76. package/dist/integrations/personal-github.d.ts +42 -0
  77. package/dist/integrations/pr-review-provider.d.ts +22 -0
  78. package/dist/integrations/provider-oauth.d.ts +19 -1
  79. package/dist/integrations/slack-app-home.d.ts +24 -0
  80. package/dist/integrations/slack-bot.d.ts +367 -12
  81. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  82. package/dist/integrations/slack-install.d.ts +20 -0
  83. package/dist/integrations/slack-interactions.d.ts +107 -9
  84. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  85. package/dist/integrations/slack-routing.d.ts +120 -0
  86. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  87. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  88. package/dist/integrations/social-oauth.d.ts +7 -0
  89. package/dist/interaction-agent-access.d.ts +15 -0
  90. package/dist/interaction-frame-proxy.d.ts +68 -0
  91. package/dist/interaction-metrics.d.ts +2 -0
  92. package/dist/mcp/action-catalog-types.d.ts +9 -0
  93. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  94. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  95. package/dist/mcp/child-read-evidence.d.ts +9 -0
  96. package/dist/mcp/command-read.d.ts +24 -0
  97. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  98. package/dist/mcp/contract-input.d.ts +11 -0
  99. package/dist/mcp/documents.d.ts +7 -5
  100. package/dist/mcp/files.d.ts +2 -2
  101. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  102. package/dist/mcp/knowledge-entries.d.ts +5 -0
  103. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  104. package/dist/mcp/receipts.d.ts +9 -0
  105. package/dist/mcp/request-abort.d.ts +19 -0
  106. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  107. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  108. package/dist/mcp/server.d.ts +255 -3
  109. package/dist/mcp/session-event-view.d.ts +94 -0
  110. package/dist/mcp/session-view.d.ts +66 -2
  111. package/dist/mcp/session-wait.d.ts +174 -0
  112. package/dist/mcp-oauth.d.ts +52 -0
  113. package/dist/model-catalog.d.ts +11 -31
  114. package/dist/observability.d.ts +3 -1
  115. package/dist/organization-mcp.d.ts +52 -0
  116. package/dist/organization-recovery-notifications.d.ts +41 -0
  117. package/dist/routes/analytics-consent.d.ts +26 -0
  118. package/dist/routes/api-integrations.d.ts +25 -0
  119. package/dist/routes/api-keys.d.ts +17 -0
  120. package/dist/routes/artifact-catalog.d.ts +6 -0
  121. package/dist/routes/automations.d.ts +19 -0
  122. package/dist/routes/billing.d.ts +49 -0
  123. package/dist/routes/browser-sessions.d.ts +59 -2
  124. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  125. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  126. package/dist/routes/client-errors.d.ts +146 -0
  127. package/dist/routes/codex.d.ts +76 -2
  128. package/dist/routes/company-brain.d.ts +3 -0
  129. package/dist/routes/company-profile.d.ts +2 -1
  130. package/dist/routes/computer-sessions.d.ts +14 -0
  131. package/dist/routes/connect.d.ts +5 -0
  132. package/dist/routes/editable-artifacts.d.ts +2 -0
  133. package/dist/routes/external-identity-links.d.ts +6 -0
  134. package/dist/routes/feedback.d.ts +3 -0
  135. package/dist/routes/github-browser-pages.d.ts +12 -0
  136. package/dist/routes/github.d.ts +2 -0
  137. package/dist/routes/insights-response-cache.d.ts +10 -0
  138. package/dist/routes/insights-usage.d.ts +12 -0
  139. package/dist/routes/insights.d.ts +30 -1
  140. package/dist/routes/knowledge.d.ts +3 -0
  141. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  142. package/dist/routes/managed-onboarding.d.ts +29 -0
  143. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  144. package/dist/routes/model-connection-access.d.ts +3 -0
  145. package/dist/routes/organization-integration-policy.d.ts +3 -0
  146. package/dist/routes/organization-integrations.d.ts +6 -0
  147. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  148. package/dist/routes/organization-model-providers.d.ts +3 -0
  149. package/dist/routes/organization-recovery.d.ts +19 -0
  150. package/dist/routes/organization-service-accounts.d.ts +3 -0
  151. package/dist/routes/organization-sessions.d.ts +26 -0
  152. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  153. package/dist/routes/personal-github.d.ts +3 -0
  154. package/dist/routes/pr-review-github.d.ts +3 -0
  155. package/dist/routes/pr-review.d.ts +3 -0
  156. package/dist/routes/session-artifact-associations.d.ts +4 -0
  157. package/dist/routes/session-history-imports.d.ts +9 -0
  158. package/dist/routes/sessions.d.ts +44 -6
  159. package/dist/routes/skill-content.d.ts +4 -0
  160. package/dist/routes/skill-install-authority.d.ts +6 -0
  161. package/dist/routes/subscription-account-pools.d.ts +34 -0
  162. package/dist/routes/subscription-pool-access.d.ts +16 -0
  163. package/dist/routes/supergrok.d.ts +6 -1
  164. package/dist/routes/usage-allowances.d.ts +12 -0
  165. package/dist/routes/user-resource-authorities.d.ts +3 -0
  166. package/dist/routes/workspace-artifacts.d.ts +2 -1
  167. package/dist/routes/workspace-capture.d.ts +10 -2
  168. package/dist/routes/workspace-integrations.d.ts +35 -0
  169. package/dist/routes/workspace-learning.d.ts +3 -0
  170. package/dist/routes/workspace-model-providers.d.ts +4 -0
  171. package/dist/routes/workspaces.d.ts +29 -0
  172. package/dist/sandbox/auth-callout.d.ts +8 -0
  173. package/dist/sandbox/channel-a.d.ts +19 -1
  174. package/dist/sandbox/connection-authority.d.ts +3 -0
  175. package/dist/sandbox/enrollment.d.ts +9 -1
  176. package/dist/sandbox/machines.d.ts +5 -2
  177. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  178. package/dist/sandbox/rematerialize.d.ts +2 -0
  179. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  180. package/dist/sandbox/viewer.d.ts +26 -1
  181. package/dist/sandbox-file-artifacts.d.ts +50 -0
  182. package/dist/scheduled-task-deletion.d.ts +13 -0
  183. package/dist/session-create-observability.d.ts +5 -0
  184. package/dist/session-event-view-MPS2OZD3.js +292 -0
  185. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  186. package/dist/site-session-origin.d.ts +13 -0
  187. package/dist/site-uploads.d.ts +41 -0
  188. package/dist/slack-reaction-files.d.ts +1 -1
  189. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  190. package/dist/temporal-schedule-sync.d.ts +26 -0
  191. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  192. package/dist/transcription/service.d.ts +2 -1
  193. package/dist/work-discovery-observability.d.ts +33 -0
  194. package/dist/workspace-artifact-content.d.ts +28 -0
  195. package/dist/workspace-artifact-provenance.d.ts +7 -0
  196. package/dist/workspace-delete-observability.d.ts +10 -0
  197. package/dist/workspace-deletion.d.ts +6 -0
  198. package/dist/workspace-state-projection.d.ts +3 -5
  199. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  200. package/dist/workspace-tool-gateway.d.ts +123 -0
  201. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  202. package/package.json +23 -19
  203. package/src/access-grant-rls.ts +75 -0
  204. package/src/admission-trace.ts +14 -0
  205. package/src/api-websocket.ts +23 -0
  206. package/src/app.ts +1967 -252
  207. package/src/artifact-catalog-cursor.ts +60 -0
  208. package/src/auth/managed-auth-attempt-context.ts +70 -0
  209. package/src/auth/managed-auth-rate-limits.ts +252 -0
  210. package/src/auth/managed-auth-session-adapter.ts +222 -0
  211. package/src/auth/managed-auth.ts +536 -60
  212. package/src/auth/managed-email.ts +174 -0
  213. package/src/auth/managed-sign-in-notifications.ts +81 -0
  214. package/src/auth/new-signups-gate.ts +49 -0
  215. package/src/auth/organization-user-setup.ts +307 -0
  216. package/src/auth/signup-funnel-metrics.ts +164 -0
  217. package/src/browser-controller-authority.ts +1 -186
  218. package/src/browser-ephemeral.ts +39 -0
  219. package/src/browser-state-authority.ts +1 -236
  220. package/src/claude-subscription-account-conflicts.ts +12 -0
  221. package/src/claude-subscription-account-usage.ts +48 -0
  222. package/src/claude-subscription-oauth.ts +362 -0
  223. package/src/claude-subscription-profile.ts +73 -0
  224. package/src/claude-subscription-usage.ts +97 -0
  225. package/src/claude-workspace-connection.ts +82 -0
  226. package/src/codemode.ts +194 -30
  227. package/src/codex-realtime.ts +8 -2
  228. package/src/company-brain-okf.ts +340 -0
  229. package/src/connected-machine-computer-access.ts +33 -0
  230. package/src/connection-ownership.ts +199 -0
  231. package/src/controller-data-plane.ts +81 -0
  232. package/src/editable-artifact-native-kernel.ts +26 -15
  233. package/src/editable-artifact-office-import.ts +28 -3
  234. package/src/editable-artifact-production.ts +23 -7
  235. package/src/editable-artifact-source-session.ts +123 -0
  236. package/src/editable-artifact-websocket.ts +27 -21
  237. package/src/editable-artifact-workspace-files.ts +31 -6
  238. package/src/fatal-process-boundary.ts +246 -0
  239. package/src/gateway-realtime.ts +1 -1
  240. package/src/github-access.ts +147 -5
  241. package/src/http/acting-person.ts +71 -0
  242. package/src/http/api-contract-compat.ts +49 -0
  243. package/src/http/api-error.ts +115 -1
  244. package/src/http/auth.ts +48 -0
  245. package/src/http/cors.ts +67 -0
  246. package/src/http/deprecation.ts +118 -0
  247. package/src/http/interaction-control-error.ts +164 -0
  248. package/src/http/keyed-admission.ts +29 -0
  249. package/src/http/local-browser-boundary.ts +343 -0
  250. package/src/http/metrics-listener.ts +57 -0
  251. package/src/http/path-identifier.ts +54 -0
  252. package/src/http/registered-route-label.ts +34 -0
  253. package/src/http/request-body.ts +127 -0
  254. package/src/http/request-source.ts +178 -0
  255. package/src/http/sse.ts +486 -132
  256. package/src/http/unmatched-route.ts +60 -0
  257. package/src/http/user-content.ts +158 -0
  258. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  259. package/src/index.ts +157 -52
  260. package/src/integration-endpoint-test.ts +291 -0
  261. package/src/integrations/api-integrations.ts +98 -11
  262. package/src/integrations/atlassian.ts +474 -136
  263. package/src/integrations/connect-authority.ts +2 -0
  264. package/src/integrations/connect-callback-return.ts +86 -0
  265. package/src/integrations/fiken.ts +354 -64
  266. package/src/integrations/github-app-connect.ts +435 -0
  267. package/src/integrations/github-installation-proof.ts +60 -0
  268. package/src/integrations/github-lens-connect.ts +76 -0
  269. package/src/integrations/github-skill-source.ts +2 -142
  270. package/src/integrations/google-drive.ts +717 -225
  271. package/src/integrations/integration-commit-authority.ts +69 -0
  272. package/src/integrations/marketplace-plugin.ts +48 -0
  273. package/src/integrations/oauth-client.ts +1356 -459
  274. package/src/integrations/oauth-profiles.ts +537 -0
  275. package/src/integrations/oauth-return-path.ts +18 -0
  276. package/src/integrations/personal-github-repositories.ts +674 -0
  277. package/src/integrations/personal-github.ts +825 -0
  278. package/src/integrations/pr-review-provider.ts +246 -0
  279. package/src/integrations/provider-oauth.ts +250 -22
  280. package/src/integrations/slack-app-home.ts +300 -0
  281. package/src/integrations/slack-bot.ts +963 -239
  282. package/src/integrations/slack-file-upload-flow.ts +147 -0
  283. package/src/integrations/slack-install.ts +83 -0
  284. package/src/integrations/slack-interactions.ts +3167 -474
  285. package/src/integrations/slack-mrkdwn.ts +193 -0
  286. package/src/integrations/slack-routing.ts +337 -0
  287. package/src/integrations/slack-session-defaults.ts +224 -0
  288. package/src/integrations/slack-task-file-upload.ts +323 -0
  289. package/src/integrations/social-oauth.ts +213 -15
  290. package/src/interaction-agent-access.ts +48 -0
  291. package/src/interaction-frame-proxy.ts +409 -0
  292. package/src/interaction-metrics.ts +30 -0
  293. package/src/mcp/action-catalog-types.ts +9 -0
  294. package/src/mcp/action-catalog.gen.ts +6696 -0
  295. package/src/mcp/capability-account-readiness.ts +17 -0
  296. package/src/mcp/child-read-evidence.ts +19 -0
  297. package/src/mcp/command-read.ts +109 -0
  298. package/src/mcp/company-profile-agent-admin.ts +198 -0
  299. package/src/mcp/contract-input.ts +141 -0
  300. package/src/mcp/documents.ts +65 -377
  301. package/src/mcp/editable-artifacts.ts +12 -6
  302. package/src/mcp/files.ts +80 -43
  303. package/src/mcp/first-party-tool-permissions.ts +305 -0
  304. package/src/mcp/knowledge-entries.ts +322 -0
  305. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  306. package/src/mcp/receipts.ts +34 -0
  307. package/src/mcp/request-abort.ts +44 -0
  308. package/src/mcp/scheduled-task-input.ts +76 -0
  309. package/src/mcp/scheduled-task-view.ts +44 -3
  310. package/src/mcp/server.ts +3030 -1267
  311. package/src/mcp/session-event-view.ts +383 -0
  312. package/src/mcp/session-view.ts +84 -3
  313. package/src/mcp/session-wait.ts +665 -0
  314. package/src/mcp-oauth.ts +1123 -0
  315. package/src/model-catalog.ts +91 -333
  316. package/src/observability.ts +15 -1
  317. package/src/organization-mcp.ts +397 -0
  318. package/src/organization-recovery-notifications.ts +103 -0
  319. package/src/routes/analytics-consent.ts +123 -0
  320. package/src/routes/api-integrations.ts +364 -74
  321. package/src/routes/api-keys.ts +336 -10
  322. package/src/routes/artifact-catalog.ts +279 -0
  323. package/src/routes/automations.ts +768 -0
  324. package/src/routes/billing.ts +461 -46
  325. package/src/routes/browser-sessions.ts +780 -112
  326. package/src/routes/canonical-human-identities.ts +90 -14
  327. package/src/routes/capabilities.ts +107 -6
  328. package/src/routes/channels.ts +20 -3
  329. package/src/routes/claude-subscription-accounts.ts +334 -0
  330. package/src/routes/claude-subscription-oauth.ts +142 -0
  331. package/src/routes/client-errors.ts +362 -0
  332. package/src/routes/codex.ts +833 -116
  333. package/src/routes/company-brain.ts +322 -0
  334. package/src/routes/company-profile.ts +65 -1
  335. package/src/routes/computer-sessions.ts +557 -106
  336. package/src/routes/connect.ts +1938 -0
  337. package/src/routes/connections.ts +1039 -349
  338. package/src/routes/documents.ts +488 -287
  339. package/src/routes/editable-artifacts.ts +79 -9
  340. package/src/routes/enrollments.ts +135 -39
  341. package/src/routes/environments.ts +154 -58
  342. package/src/routes/external-identity-links.ts +201 -0
  343. package/src/routes/feedback.ts +113 -0
  344. package/src/routes/files.ts +267 -33
  345. package/src/routes/github-browser-pages.ts +162 -0
  346. package/src/routes/github.ts +446 -144
  347. package/src/routes/insights-response-cache.ts +83 -0
  348. package/src/routes/insights-usage.ts +345 -0
  349. package/src/routes/insights.ts +153 -19
  350. package/src/routes/install.ts +133 -38
  351. package/src/routes/integration-facets.ts +107 -33
  352. package/src/routes/interaction-resources.ts +30 -3
  353. package/src/routes/knowledge.ts +488 -0
  354. package/src/routes/machines.ts +313 -15
  355. package/src/routes/managed-auth-session-sets.ts +1218 -0
  356. package/src/routes/managed-onboarding.ts +337 -0
  357. package/src/routes/managed-sign-in-methods.ts +433 -0
  358. package/src/routes/memory-slack-publications.ts +3 -2
  359. package/src/routes/model-connection-access.ts +208 -0
  360. package/src/routes/organization-integration-policy.ts +116 -0
  361. package/src/routes/organization-integrations.ts +326 -0
  362. package/src/routes/organization-mcp-connections.ts +178 -0
  363. package/src/routes/organization-memberships.ts +1019 -27
  364. package/src/routes/organization-model-providers.ts +293 -0
  365. package/src/routes/organization-recovery.ts +325 -0
  366. package/src/routes/organization-service-accounts.ts +128 -0
  367. package/src/routes/organization-sessions.ts +350 -0
  368. package/src/routes/personal-github-git-broker.ts +785 -0
  369. package/src/routes/personal-github.ts +370 -0
  370. package/src/routes/plugins.ts +251 -22
  371. package/src/routes/pr-review-github.ts +881 -0
  372. package/src/routes/pr-review.ts +622 -0
  373. package/src/routes/preference-registry.ts +12 -93
  374. package/src/routes/rigs.ts +167 -50
  375. package/src/routes/scheduled-tasks.ts +346 -41
  376. package/src/routes/session-artifact-associations.ts +87 -0
  377. package/src/routes/session-history-imports.ts +280 -0
  378. package/src/routes/sessions.ts +2306 -264
  379. package/src/routes/skill-content.ts +297 -0
  380. package/src/routes/skill-install-authority.ts +64 -0
  381. package/src/routes/skills.ts +62 -9
  382. package/src/routes/social.ts +46 -18
  383. package/src/routes/subscription-account-pools.ts +436 -0
  384. package/src/routes/subscription-pool-access.ts +126 -0
  385. package/src/routes/supergrok.ts +219 -303
  386. package/src/routes/transcription-recordings.ts +38 -3
  387. package/src/routes/transcriptions.ts +4 -0
  388. package/src/routes/usage-allowances.ts +378 -0
  389. package/src/routes/user-resource-authorities.ts +138 -0
  390. package/src/routes/video-generation.ts +14 -10
  391. package/src/routes/workspace-artifacts.ts +275 -69
  392. package/src/routes/workspace-capture.ts +21 -0
  393. package/src/routes/workspace-integrations.ts +576 -0
  394. package/src/routes/workspace-learning.ts +159 -0
  395. package/src/routes/workspace-model-providers.ts +148 -0
  396. package/src/routes/workspace-state.ts +38 -25
  397. package/src/routes/workspaces.ts +1004 -127
  398. package/src/sandbox/auth-callout.ts +96 -19
  399. package/src/sandbox/channel-a.ts +323 -24
  400. package/src/sandbox/connection-authority.ts +3 -0
  401. package/src/sandbox/enrollment.ts +81 -6
  402. package/src/sandbox/machines.ts +218 -76
  403. package/src/sandbox/metrics-ingestion.ts +411 -62
  404. package/src/sandbox/rematerialize.ts +121 -19
  405. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  406. package/src/sandbox/viewer.ts +280 -25
  407. package/src/sandbox-file-artifacts.ts +413 -0
  408. package/src/scheduled-task-deletion.ts +138 -0
  409. package/src/session-create-observability.ts +46 -0
  410. package/src/session-realtime-context.ts +6 -3
  411. package/src/site-session-origin.ts +48 -0
  412. package/src/site-uploads.ts +224 -0
  413. package/src/slack-reaction-files.ts +16 -5
  414. package/src/temporal-schedule-cleanup.ts +13 -0
  415. package/src/temporal-schedule-sync.ts +74 -0
  416. package/src/transcription/providers/codex-subscription.ts +2 -0
  417. package/src/transcription/providers/openai.ts +1 -0
  418. package/src/transcription/providers/xai-subscription.ts +49 -3
  419. package/src/transcription/segmenter.ts +8 -2
  420. package/src/transcription/service.ts +43 -6
  421. package/src/work-discovery-observability.ts +121 -0
  422. package/src/workspace-artifact-content.ts +144 -0
  423. package/src/workspace-artifact-provenance.ts +104 -0
  424. package/src/workspace-delete-observability.ts +75 -0
  425. package/src/workspace-deletion.ts +73 -0
  426. package/src/workspace-state-projection.ts +18 -148
  427. package/src/workspace-tool-gateway-observability.ts +100 -0
  428. package/src/workspace-tool-gateway.ts +870 -0
  429. package/src/workspace-webhook-dispatch.ts +190 -0
  430. package/src/xai-subscription-auth.ts +9 -2
  431. package/dist/chunk-WCXHI3FF.js +0 -49920
  432. package/dist/chunk-WCXHI3FF.js.map +0 -1
  433. package/dist/routes/packs.d.ts +0 -3
  434. package/src/routes/packs.ts +0 -729
@@ -1,4 +1,26 @@
1
+ type AtlassianRequestAuthority = {
2
+ connectionUseContext?: Omit<AcceptedConnectionUseContext, "physicalRequestId" | "usePhase">;
3
+ };
4
+
5
+ import type { AcceptedConnectionUseContext } from "@opengeni/db";
6
+ import {
7
+ withOrganizationIntegrationAcquisition,
8
+ withOrganizationIntegrationPolicyFence,
9
+ } from "@opengeni/db/organization-integration-policy";
10
+ import {
11
+ claimOAuthAcquisition,
12
+ integrationSourceSelectionRequiresAcquisition,
13
+ } from "./oauth-client";
14
+ import {
15
+ scheduledTaskKnowledgeSource,
16
+ requireScheduledTaskKnowledgeSource,
17
+ knowledgeSourceAgentConfig,
18
+ assertOrganizationIntegrationAllowed,
19
+ } from "@opengeni/contracts";
1
20
  import type { Settings } from "@opengeni/config";
21
+ import { createHash } from "node:crypto";
22
+ import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
23
+ import { requireConnectOwnerAuthority } from "./connect-authority";
2
24
  import type { AccessGrant, ScheduledTask, ScheduledTaskScheduleSpec } from "@opengeni/contracts";
3
25
  import {
4
26
  ATLASSIAN_CREDENTIAL_LABEL,
@@ -24,7 +46,6 @@ import {
24
46
  import {
25
47
  captureScheduledTaskRestoreState,
26
48
  createValidatedScheduledTask,
27
- hasPermission,
28
49
  manualScheduledTaskTriggerWorkflowId,
29
50
  requireEnvironmentEncryption,
30
51
  syncCreatedScheduledTask,
@@ -37,6 +58,10 @@ import {
37
58
  ConnectionDisconnectGenerationError,
38
59
  ConnectionDisconnectIdempotencyError,
39
60
  consumeIntegrationOAuthStateNonce,
61
+ finishConnectOperation,
62
+ getConnectAttempt,
63
+ decryptEnvironmentValue,
64
+ type Database,
40
65
  createConnection,
41
66
  deauthorizeKnowledgeSourceRetrieval,
42
67
  disconnectConnectionIdempotently,
@@ -44,7 +69,6 @@ import {
44
69
  getConnectionMetadata,
45
70
  getKnowledgeSourceByExternalIdentityForSyncAuthority,
46
71
  getKnowledgeSourceForSyncAuthority,
47
- getWorkspaceGrant,
48
72
  listKnowledgeSourceSyncTasksForConnection,
49
73
  loadConnectionCredentialForBroker,
50
74
  recordKnowledgeLifecycleEvent,
@@ -53,17 +77,36 @@ import {
53
77
  updateScheduledTask,
54
78
  upsertKnowledgeProvider,
55
79
  upsertKnowledgeSource,
80
+ withWorkspaceSubjectRls,
56
81
  } from "@opengeni/db";
57
82
  import { atlassianKnowledgeSourceIdentity } from "@opengeni/documents/atlassian";
58
83
  import { createSignedState, readSignedState } from "@opengeni/github";
59
84
  import { readResponseJsonBounded, type FetchLike } from "@opengeni/network";
60
85
  import { HTTPException } from "hono/http-exception";
86
+ import {
87
+ ATLASSIAN_NATIVE_RETIRED_MESSAGE,
88
+ ATLASSIAN_NATIVE_RETIRED_REASON,
89
+ } from "@opengeni/contracts/atlassian-native-retirement";
90
+ import {
91
+ personalOnlyConnectionPrincipalMessage,
92
+ personalOwnerStateAccepted,
93
+ personalOwnerVerifiedInState,
94
+ PERSONAL_OWNER_VERIFIED_STATE_CLAIM,
95
+ } from "../connection-ownership";
61
96
  import {
62
97
  integrationBaseUrl,
98
+ oauthStateFailureReturn,
63
99
  oauthStateTtlMs,
64
100
  requireIntegrationsStateSecret,
65
101
  } from "./oauth-client";
66
102
 
103
+ /**
104
+ * Flow discriminator for this connector's signed OAuth state. See the matching
105
+ * constant in `google-drive.ts`: this state carries no `ownership` field and no
106
+ * provider identity, and its return path is byte-identical to one the MCP OAuth
107
+ * start signs from caller input, so the flow kind is what binds a state here.
108
+ */
109
+ const ATLASSIAN_OAUTH_STATE_KIND = "atlassian_oauth";
67
110
  const ATLASSIAN_AUTHORIZATION_URL = "https://auth.atlassian.com/authorize";
68
111
  const ATLASSIAN_TOKEN_URL = "https://auth.atlassian.com/oauth/token";
69
112
  const ATLASSIAN_RESOURCES_URL = "https://api.atlassian.com/oauth/token/accessible-resources";
@@ -73,9 +116,13 @@ const ATLASSIAN_REQUEST_TIMEOUT_MS = 15_000;
73
116
  const ATLASSIAN_RETURN_PATH = (workspaceId: string) => `/workspaces/${workspaceId}/capabilities`;
74
117
 
75
118
  type AtlassianOAuthState = {
119
+ connectAttemptId?: string;
120
+ externalContinuation?: ExternalActorContinuation;
76
121
  accountId: string;
77
122
  workspaceId: string;
78
123
  subjectId: string;
124
+ /** Signed proof that a live managed human started this personal-only flow. */
125
+ personalOwnerVerified: boolean;
79
126
  returnPath: string;
80
127
  connectionId?: string;
81
128
  connectionVersion?: number;
@@ -99,8 +146,12 @@ export async function startAtlassianOAuth(
99
146
  subjectId: string;
100
147
  requestUrl: string;
101
148
  payload: AtlassianOAuthStartRequest;
149
+ connectAttemptId?: string;
150
+ externalContinuation?: ExternalActorContinuation;
102
151
  },
103
152
  ): Promise<AtlassianOAuthStartResponse> {
153
+ assertNativeAtlassianAvailable();
154
+ await withOrganizationIntegrationAcquisition(deps.db, input, ["atlassian"], async () => {});
104
155
  const oauth = requireAtlassianSettings(deps.settings);
105
156
  const existing = input.payload.connectionId
106
157
  ? await getConnectionMetadata(
@@ -121,6 +172,19 @@ export async function startAtlassianOAuth(
121
172
  accountId: input.accountId,
122
173
  workspaceId: input.workspaceId,
123
174
  subjectId: input.subjectId,
175
+ kind: ATLASSIAN_OAUTH_STATE_KIND,
176
+ ...(input.connectAttemptId ? { connectAttemptId: input.connectAttemptId } : {}),
177
+ ...(input.externalContinuation
178
+ ? {
179
+ encryptedExternalContinuation: encryptEnvironmentValue(
180
+ requireEnvironmentEncryption(deps.settings),
181
+ JSON.stringify(ExternalActorContinuation.parse(input.externalContinuation)),
182
+ ),
183
+ }
184
+ : {}),
185
+ // The route admits only a managed human, so reaching here is the proof; the
186
+ // callback has no live principal and enforces exactly this claim.
187
+ [PERSONAL_OWNER_VERIFIED_STATE_CLAIM]: true,
124
188
  returnPath: ATLASSIAN_RETURN_PATH(input.workspaceId),
125
189
  ...(existing ? { connectionId: existing.id, connectionVersion: existing.version } : {}),
126
190
  });
@@ -146,12 +210,39 @@ export async function completeAtlassianOAuthCallback(
146
210
  error?: string | undefined;
147
211
  requestUrl: string;
148
212
  },
149
- ): Promise<{ redirectTo: string }> {
213
+ ): Promise<{ redirectTo: string; exactReturn?: boolean }> {
150
214
  const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
151
215
  const returnBaseUrl = deps.settings.webBaseUrl?.replace(/\/+$/, "") ?? baseUrl;
152
216
  let state: AtlassianOAuthState | null = null;
217
+ let exactReturnUrl: string | undefined;
218
+ let operation: { attemptId: string; operationId: string; inputDigest: string } | undefined;
153
219
  try {
154
220
  state = readAtlassianOAuthState(input.state, deps.settings);
221
+ if (state.connectAttemptId) {
222
+ const stored = await getConnectAttempt(deps.db, state, state.connectAttemptId);
223
+ if (stored.attempt.providerId !== "atlassian" || stored.attempt.ownership !== "personal")
224
+ throw new AtlassianCallbackError("connection_conflict");
225
+ exactReturnUrl = stored.returnUrl;
226
+ operation = {
227
+ attemptId: state.connectAttemptId,
228
+ operationId: `oauth:${state.nonce}`,
229
+ inputDigest: createHash("sha256").update(input.state!).digest("hex"),
230
+ };
231
+ const claim = await claimOAuthAcquisition(
232
+ deps.db,
233
+ state,
234
+ {
235
+ ...operation,
236
+ expectedRevision: stored.attempt.revision,
237
+ authorize: (tx, _attempt, origin) =>
238
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
239
+ },
240
+ "atlassian",
241
+ // Retirement only settles this attempt; it acquires no integration.
242
+ false,
243
+ );
244
+ if (claim.status === "replayed") return { redirectTo: exactReturnUrl, exactReturn: true };
245
+ }
155
246
  await requireCallbackGrant(deps, state);
156
247
  const consumed = await consumeIntegrationOAuthStateNonce(deps.db, {
157
248
  accountId: state.accountId,
@@ -161,9 +252,52 @@ export async function completeAtlassianOAuthCallback(
161
252
  expiresAt: new Date(state.iat * 1000 + oauthStateTtlMs),
162
253
  now: new Date(),
163
254
  });
164
- if (!consumed) throw new AtlassianCallbackError("state_reused");
255
+ if (!consumed) throw new AtlassianCallbackError("state_replayed");
256
+ if (operation && (input.error || !input.code)) {
257
+ await finishConnectOperation(deps.db, state, {
258
+ ...operation,
259
+ authorize: (tx, _attempt, origin) =>
260
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
261
+ commit: async (_tx, current) => ({
262
+ ...current,
263
+ revision: current.revision + 1,
264
+ state: input.error === "access_denied" ? "cancelled" : "failed",
265
+ nextAction: { type: "none" },
266
+ error: {
267
+ code: input.error ? "provider_denied" : "missing_code",
268
+ message: "Authorization was not completed. Start a new connection attempt.",
269
+ retryable: false,
270
+ },
271
+ }),
272
+ });
273
+ return { redirectTo: exactReturnUrl!, exactReturn: true };
274
+ }
165
275
  if (input.error) throw new AtlassianCallbackError("provider_denied");
166
276
  if (!input.code) throw new AtlassianCallbackError("missing_code");
277
+ // An authorization started on an older release must not exchange or save
278
+ // a native grant after retirement. Preserve exact Connect navigation and
279
+ // settle the pending attempt so it does not remain stuck authorizing.
280
+ if (operation) {
281
+ await finishConnectOperation(deps.db, state, {
282
+ ...operation,
283
+ authorize: (tx, _attempt, origin) =>
284
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
285
+ commit: async (_tx, current) => ({
286
+ ...current,
287
+ revision: current.revision + 1,
288
+ state: "failed",
289
+ nextAction: { type: "none" },
290
+ error: {
291
+ code: ATLASSIAN_NATIVE_RETIRED_REASON,
292
+ message: ATLASSIAN_NATIVE_RETIRED_MESSAGE,
293
+ retryable: false,
294
+ },
295
+ }),
296
+ });
297
+ return { redirectTo: exactReturnUrl!, exactReturn: true };
298
+ }
299
+ assertNativeAtlassianAvailable();
300
+ await withOrganizationIntegrationAcquisition(deps.db, state, ["atlassian"], async () => {});
167
301
 
168
302
  const oauth = requireAtlassianSettings(deps.settings);
169
303
  const redirectUri = `${baseUrl}/v1/integrations/atlassian/callback`;
@@ -242,45 +376,64 @@ export async function completeAtlassianOAuthCallback(
242
376
  : {}),
243
377
  selectedSources: previousMetadata?.selectedSources ?? [],
244
378
  });
245
- const connection = existing
246
- ? await updateConnection(deps.db, {
247
- workspaceId: state.workspaceId,
248
- connectionId: existing.id,
249
- visibleToSubjectId: state.subjectId,
250
- expectedVersion: existing.version,
251
- subjectId: state.subjectId,
252
- providerDomain: ATLASSIAN_PROVIDER_DOMAIN,
253
- kind: "oauth2",
254
- status: "active",
255
- credentialEncrypted,
256
- grantedScopes: token.scopes,
257
- expiresAt: token.expiresAt,
258
- metadata,
259
- updatedBySubjectId: state.subjectId,
260
- })
261
- : await createConnection(deps.db, {
262
- accountId: state.accountId,
263
- workspaceId: state.workspaceId,
264
- subjectId: state.subjectId,
265
- providerDomain: ATLASSIAN_PROVIDER_DOMAIN,
266
- kind: "oauth2",
267
- credentialEncrypted,
268
- grantedScopes: token.scopes,
269
- expiresAt: token.expiresAt,
270
- metadata,
271
- createdBySubjectId: state.subjectId,
272
- });
379
+ const acceptedState = state;
380
+ const persist = (tx: Database) => {
381
+ // Capture the validated immutable callback state for delayed persistence.
382
+ // eslint-disable-next-line no-shadow
383
+ const state = acceptedState;
384
+ return existing
385
+ ? updateConnection(tx, {
386
+ workspaceId: state.workspaceId,
387
+ connectionId: existing.id,
388
+ visibleToSubjectId: state.subjectId,
389
+ expectedVersion: existing.version,
390
+ subjectId: state.subjectId,
391
+ providerDomain: ATLASSIAN_PROVIDER_DOMAIN,
392
+ kind: "oauth2",
393
+ status: "active",
394
+ credentialEncrypted,
395
+ grantedScopes: token.scopes,
396
+ expiresAt: token.expiresAt,
397
+ metadata,
398
+ updatedBySubjectId: state.subjectId,
399
+ })
400
+ : createConnection(tx, {
401
+ accountId: state.accountId,
402
+ workspaceId: state.workspaceId,
403
+ subjectId: state.subjectId,
404
+ providerDomain: ATLASSIAN_PROVIDER_DOMAIN,
405
+ kind: "oauth2",
406
+ credentialEncrypted,
407
+ grantedScopes: token.scopes,
408
+ expiresAt: token.expiresAt,
409
+ metadata,
410
+ createdBySubjectId: state.subjectId,
411
+ });
412
+ };
413
+ const connection = await withOrganizationIntegrationAcquisition(
414
+ deps.db,
415
+ acceptedState,
416
+ ["atlassian"],
417
+ async (tx) => {
418
+ await requireCallbackGrant({ ...deps, db: tx }, acceptedState);
419
+ return persist(tx);
420
+ },
421
+ );
273
422
  if (!connection) throw new AtlassianCallbackError("connection_conflict");
274
423
  return {
275
424
  redirectTo: returnUrl(returnBaseUrl, state.returnPath, "connected", connection.id),
276
425
  };
277
426
  } catch (error) {
427
+ if (exactReturnUrl) return { redirectTo: exactReturnUrl, exactReturn: true };
428
+ // Reading the state is the first step, so no state means the link itself
429
+ // was unusable: say whether it expired rather than blame configuration.
430
+ const failure = state ? null : oauthStateFailureReturn(deps.settings, input.state);
278
431
  return {
279
432
  redirectTo: returnUrl(
280
433
  returnBaseUrl,
281
- state?.returnPath ?? "/integrations",
434
+ state?.returnPath ?? failure!.returnPath,
282
435
  "error",
283
- errorReason(error),
436
+ failure?.reason ?? errorReason(error),
284
437
  ),
285
438
  };
286
439
  }
@@ -288,8 +441,13 @@ export async function completeAtlassianOAuthCallback(
288
441
 
289
442
  export async function browseAtlassianSources(
290
443
  deps: ApiRouteDeps,
291
- input: { workspaceId: string; subjectId: string; connectionId: string },
444
+ input: AtlassianRequestAuthority & {
445
+ workspaceId: string;
446
+ subjectId: string;
447
+ connectionId: string;
448
+ },
292
449
  ) {
450
+ assertNativeAtlassianAvailable();
293
451
  const connection = await getConnectionMetadata(
294
452
  deps.db,
295
453
  input.workspaceId,
@@ -329,7 +487,7 @@ export async function browseAtlassianSources(
329
487
 
330
488
  export async function searchAtlassianLive(
331
489
  deps: ApiRouteDeps,
332
- input: {
490
+ input: AtlassianRequestAuthority & {
333
491
  workspaceId: string;
334
492
  subjectId: string;
335
493
  connectionId: string;
@@ -338,6 +496,7 @@ export async function searchAtlassianLive(
338
496
  limit: number;
339
497
  },
340
498
  ) {
499
+ assertNativeAtlassianAvailable();
341
500
  const connection = await getConnectionMetadata(
342
501
  deps.db,
343
502
  input.workspaceId,
@@ -432,7 +591,7 @@ export async function searchAtlassianLive(
432
591
 
433
592
  export async function getAtlassianLiveItem(
434
593
  deps: ApiRouteDeps,
435
- input: {
594
+ input: AtlassianRequestAuthority & {
436
595
  workspaceId: string;
437
596
  subjectId: string;
438
597
  connectionId: string;
@@ -440,6 +599,7 @@ export async function getAtlassianLiveItem(
440
599
  id: string;
441
600
  },
442
601
  ) {
602
+ assertNativeAtlassianAvailable();
443
603
  const connection = await getConnectionMetadata(
444
604
  deps.db,
445
605
  input.workspaceId,
@@ -553,6 +713,7 @@ export async function saveAtlassianSources(
553
713
  canManagePersonalDestination: boolean;
554
714
  },
555
715
  ) {
716
+ assertNativeAtlassianAvailable();
556
717
  const parsed = SaveAtlassianSourcesRequest.safeParse(input.payload);
557
718
  if (!parsed.success) {
558
719
  throw new HTTPException(400, { message: "invalid Atlassian source selection" });
@@ -584,9 +745,28 @@ export async function saveAtlassianSources(
584
745
  initiatingSubjectId: input.subjectId,
585
746
  });
586
747
 
587
- const available = (await browseAtlassianSources(deps, input)).items;
748
+ const requestedSelection = parsed.data.sources.map((source) => ({
749
+ ...source,
750
+ destination,
751
+ syncCadence: parsed.data.syncCadence,
752
+ syncEnabled: parsed.data.syncEnabled,
753
+ readPolicy: parsed.data.readPolicy,
754
+ }));
755
+ const acquiring = integrationSourceSelectionRequiresAcquisition(
756
+ metadata.selectedSources,
757
+ requestedSelection,
758
+ );
759
+ if (acquiring) {
760
+ await withOrganizationIntegrationAcquisition(deps.db, input, ["atlassian"], async () => {});
761
+ }
762
+
763
+ const available =
764
+ acquiring && parsed.data.sources.length
765
+ ? (await browseAtlassianSources(deps, input)).items
766
+ : [];
588
767
  const availableById = new Map(available.map((source) => [source.id, source]));
589
768
  const verified = parsed.data.sources.map((source) => {
769
+ if (!acquiring) return source;
590
770
  const current = availableById.get(source.id);
591
771
  if (
592
772
  !current ||
@@ -603,28 +783,39 @@ export async function saveAtlassianSources(
603
783
  return current;
604
784
  });
605
785
  const previousSources = metadata.selectedSources;
606
- const updated = await transitionConnectionState(deps.db, {
607
- workspaceId: input.workspaceId,
608
- connectionId: existing.id,
609
- visibleToSubjectId: input.subjectId,
610
- expectedVersion: existing.version,
611
- metadata: AtlassianConnectionMetadata.parse({
612
- ...metadata,
613
- documentDestination: destination,
614
- selectedSources: verified.map((source) => ({
615
- ...source,
616
- destination,
617
- syncCadence: parsed.data.syncCadence,
618
- syncEnabled: parsed.data.syncEnabled,
619
- configGeneration:
620
- (previousSources.find((previous) => previous.id === source.id)?.configGeneration ?? 0) +
621
- 1,
622
- readPolicy: parsed.data.readPolicy,
623
- selectedAt: new Date().toISOString(),
624
- })),
625
- }),
626
- updatedBySubjectId: input.subjectId,
627
- });
786
+ const updated = await withOrganizationIntegrationPolicyFence(
787
+ deps.db,
788
+ input,
789
+ async (tx, policy) => {
790
+ // The exact generation checked by transitionConnectionState binds this
791
+ // comparison to the row being changed. Provider verification already ended.
792
+ if (integrationSourceSelectionRequiresAcquisition(previousSources, requestedSelection)) {
793
+ assertOrganizationIntegrationAllowed(policy, "atlassian");
794
+ }
795
+ return transitionConnectionState(tx, {
796
+ workspaceId: input.workspaceId,
797
+ connectionId: existing.id,
798
+ visibleToSubjectId: input.subjectId,
799
+ expectedVersion: existing.version,
800
+ metadata: AtlassianConnectionMetadata.parse({
801
+ ...metadata,
802
+ documentDestination: destination,
803
+ selectedSources: verified.map((source) => ({
804
+ ...source,
805
+ destination,
806
+ syncCadence: parsed.data.syncCadence,
807
+ syncEnabled: parsed.data.syncEnabled,
808
+ configGeneration:
809
+ (previousSources.find((previous) => previous.id === source.id)?.configGeneration ??
810
+ 0) + 1,
811
+ readPolicy: parsed.data.readPolicy,
812
+ selectedAt: new Date().toISOString(),
813
+ })),
814
+ }),
815
+ updatedBySubjectId: input.subjectId,
816
+ });
817
+ },
818
+ );
628
819
  if (!updated) throw new HTTPException(409, { message: "Atlassian connection changed" });
629
820
  await materializeSchedules(deps, {
630
821
  ...input,
@@ -644,6 +835,7 @@ export async function transitionAtlassianLifecycle(
644
835
  payload: AtlassianLifecycleActionRequest;
645
836
  },
646
837
  ) {
838
+ if (input.payload.action === "resume") assertNativeAtlassianAvailable();
647
839
  const connection = await getConnectionMetadata(
648
840
  deps.db,
649
841
  input.workspaceId,
@@ -662,14 +854,27 @@ export async function transitionAtlassianLifecycle(
662
854
  if (target === "paused") {
663
855
  await deauthorizeConnectionSources(deps, connection, input.subjectId, "connection_paused");
664
856
  }
665
- const updated = await transitionConnectionState(deps.db, {
666
- workspaceId: input.workspaceId,
667
- connectionId: input.connectionId,
668
- visibleToSubjectId: input.subjectId,
669
- expectedVersion: connection.version,
670
- metadata: AtlassianConnectionMetadata.parse({ ...metadata, lifecycle: lifecycle(target) }),
671
- updatedBySubjectId: input.subjectId,
672
- });
857
+ const persist = (tx: Database) =>
858
+ transitionConnectionState(tx, {
859
+ workspaceId: input.workspaceId,
860
+ connectionId: input.connectionId,
861
+ visibleToSubjectId: input.subjectId,
862
+ expectedVersion: connection.version,
863
+ metadata: AtlassianConnectionMetadata.parse({ ...metadata, lifecycle: lifecycle(target) }),
864
+ updatedBySubjectId: input.subjectId,
865
+ });
866
+ const updated =
867
+ target === "active"
868
+ ? await withOrganizationIntegrationPolicyFence(
869
+ deps.db,
870
+ { accountId: connection.accountId, workspaceId: input.workspaceId },
871
+ (tx, policy) => {
872
+ if ((metadata.lifecycle?.state ?? "active") !== "active")
873
+ assertOrganizationIntegrationAllowed(policy, "atlassian");
874
+ return persist(tx);
875
+ },
876
+ )
877
+ : await persist(deps.db);
673
878
  if (!updated) throw new HTTPException(409, { message: "Atlassian connection changed" });
674
879
  await setSchedulePause(deps, input.workspaceId, input.connectionId, target === "paused");
675
880
  return updated;
@@ -719,15 +924,15 @@ export async function disconnectAtlassian(
719
924
  }
720
925
  }
721
926
 
722
- export async function revokeAtlassianScheduleAuthorization(
927
+ export async function preflightAtlassianScheduleAuthorization(
723
928
  deps: ApiRouteDeps,
724
929
  input: { task: ScheduledTask; subjectId: string },
725
930
  ): Promise<void> {
726
931
  const { task } = input;
727
- if (task.action.kind !== "knowledge_source_sync") return;
932
+ if (!scheduledTaskKnowledgeSource(task)) return;
728
933
  if (
729
- task.action.initiatingSubjectId !== input.subjectId ||
730
- task.action.connection.ownerSubjectId !== input.subjectId
934
+ requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
935
+ requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
731
936
  ) {
732
937
  throw new HTTPException(403, {
733
938
  message: "knowledge source schedule requires the exact initiating subject",
@@ -737,50 +942,118 @@ export async function revokeAtlassianScheduleAuthorization(
737
942
  if (!externalSourceId) {
738
943
  throw new HTTPException(409, { message: "Atlassian schedule identity is incomplete" });
739
944
  }
740
- const connection = await getConnectionMetadata(
741
- deps.db,
742
- task.workspaceId,
743
- task.action.connection.connectionId,
744
- input.subjectId,
745
- );
746
- if (!connection) throw new HTTPException(409, { message: "Atlassian connection is unavailable" });
747
- const metadata = requireAtlassianConnection(connection, input.subjectId);
748
- const selected = metadata.selectedSources.find((source) => source.id === externalSourceId);
749
- let authorityVersion = connection.version;
750
- if (selected?.syncEnabled) {
751
- const updated = await transitionConnectionState(deps.db, {
752
- workspaceId: task.workspaceId,
753
- connectionId: connection.id,
754
- visibleToSubjectId: input.subjectId,
755
- expectedVersion: connection.version,
756
- metadata: AtlassianConnectionMetadata.parse({
757
- ...metadata,
758
- selectedSources: metadata.selectedSources.map((source) =>
759
- source.id === externalSourceId
760
- ? { ...source, syncEnabled: false, configGeneration: source.configGeneration + 1 }
761
- : source,
762
- ),
763
- }),
764
- updatedBySubjectId: input.subjectId,
945
+ // Current connector attachment is not deletion authority. The frozen task
946
+ // revision and generation-fenced cleanup descriptor are.
947
+ }
948
+
949
+ export async function revokeAtlassianScheduleAuthorization(
950
+ deps: ApiRouteDeps,
951
+ input: { task: ScheduledTask; subjectId: string },
952
+ ): Promise<void> {
953
+ const { task } = input;
954
+ if (!scheduledTaskKnowledgeSource(task)) return;
955
+ if (
956
+ requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
957
+ requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
958
+ ) {
959
+ throw new HTTPException(403, {
960
+ message: "knowledge source schedule requires the exact initiating subject",
765
961
  });
766
- if (!updated) throw new HTTPException(409, { message: "Atlassian connection changed" });
767
- authorityVersion = updated.version;
768
962
  }
769
- const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
963
+ const externalSourceId = optionalString(task.metadata.externalSourceId);
964
+ if (!externalSourceId) {
965
+ throw new HTTPException(409, { message: "Atlassian schedule identity is incomplete" });
966
+ }
967
+ await cleanupAtlassianScheduleAuthorization(deps, {
968
+ taskId: task.id,
770
969
  accountId: task.accountId,
771
970
  workspaceId: task.workspaceId,
772
- sourceId: task.action.sourceId,
971
+ connectionId: requireScheduledTaskKnowledgeSource(task).connection.connectionId,
972
+ connectionVersion: requireScheduledTaskKnowledgeSource(task).connection.connectionVersion,
973
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
974
+ sourceLifecycleGeneration: requireScheduledTaskKnowledgeSource(task).sourceLifecycleGeneration,
975
+ sourceConfigGeneration: requireScheduledTaskKnowledgeSource(task).sourceConfigGeneration,
976
+ externalSourceId,
977
+ subjectId: input.subjectId,
978
+ });
979
+ }
980
+
981
+ export async function cleanupAtlassianScheduleAuthorization(
982
+ deps: ApiRouteDeps,
983
+ input: {
984
+ taskId: string;
985
+ accountId: string;
986
+ workspaceId: string;
987
+ connectionId: string;
988
+ connectionVersion: number;
989
+ sourceId: string;
990
+ sourceLifecycleGeneration: number;
991
+ sourceConfigGeneration: number;
992
+ externalSourceId: string;
993
+ subjectId: string;
994
+ },
995
+ ): Promise<void> {
996
+ const resolvedBeforeLock = await getKnowledgeSourceForSyncAuthority(deps.db, {
997
+ accountId: input.accountId,
998
+ workspaceId: input.workspaceId,
999
+ sourceId: input.sourceId,
773
1000
  initiatingSubjectId: input.subjectId,
774
1001
  });
775
- if (resolved?.source.lifecycleState === "active") {
776
- await recordKnowledgeLifecycleEvent(deps.db, {
777
- accountId: task.accountId,
778
- workspaceId: task.workspaceId,
1002
+ if (
1003
+ resolvedBeforeLock?.source.lifecycleState !== "active" ||
1004
+ resolvedBeforeLock.source.lifecycleGeneration !== input.sourceLifecycleGeneration
1005
+ ) {
1006
+ return;
1007
+ }
1008
+ await withWorkspaceSubjectRls(deps.db, input.workspaceId, input.subjectId, async (tx) => {
1009
+ const connection = await getConnectionMetadata(
1010
+ tx,
1011
+ input.workspaceId,
1012
+ input.connectionId,
1013
+ input.subjectId,
1014
+ );
1015
+ let authorityVersion = input.connectionVersion;
1016
+ if (connection) {
1017
+ const metadata = requireAtlassianConnection(connection, input.subjectId);
1018
+ authorityVersion = connection.version;
1019
+ const updated = await transitionConnectionState(tx, {
1020
+ workspaceId: input.workspaceId,
1021
+ connectionId: connection.id,
1022
+ visibleToSubjectId: input.subjectId,
1023
+ expectedVersion: connection.version,
1024
+ metadata: AtlassianConnectionMetadata.parse({
1025
+ ...metadata,
1026
+ selectedSources: metadata.selectedSources.map((source) =>
1027
+ source.id === input.externalSourceId
1028
+ ? { ...source, syncEnabled: false, configGeneration: source.configGeneration + 1 }
1029
+ : source,
1030
+ ),
1031
+ }),
1032
+ updatedBySubjectId: input.subjectId,
1033
+ });
1034
+ if (!updated) throw new HTTPException(409, { message: "Atlassian connection changed" });
1035
+ authorityVersion = updated.version;
1036
+ }
1037
+ const resolved = await getKnowledgeSourceForSyncAuthority(tx, {
1038
+ accountId: input.accountId,
1039
+ workspaceId: input.workspaceId,
1040
+ sourceId: input.sourceId,
1041
+ initiatingSubjectId: input.subjectId,
1042
+ });
1043
+ if (
1044
+ resolved?.source.lifecycleState !== "active" ||
1045
+ resolved.source.lifecycleGeneration !== input.sourceLifecycleGeneration
1046
+ ) {
1047
+ throw new HTTPException(409, { message: "Atlassian knowledge source changed" });
1048
+ }
1049
+ await recordKnowledgeLifecycleEvent(tx, {
1050
+ accountId: input.accountId,
1051
+ workspaceId: input.workspaceId,
779
1052
  targetKind: "source",
780
1053
  targetId: resolved.source.id,
781
1054
  eventType: "deleted",
782
1055
  expectedGeneration: resolved.source.lifecycleGeneration,
783
- operationId: `atlassian-schedule-delete:${task.id}:${authorityVersion}`,
1056
+ operationId: `atlassian-schedule-delete:${input.taskId}:${authorityVersion}`,
784
1057
  reasonCode: "schedule_deleted",
785
1058
  actor: {
786
1059
  kind: "human",
@@ -788,7 +1061,7 @@ export async function revokeAtlassianScheduleAuthorization(
788
1061
  initiatingHumanSubjectId: input.subjectId,
789
1062
  },
790
1063
  });
791
- }
1064
+ });
792
1065
  }
793
1066
 
794
1067
  async function materializeSchedules(
@@ -830,11 +1103,11 @@ async function materializeSchedules(
830
1103
  let source = null as Awaited<ReturnType<typeof upsertKnowledgeSource>> | null;
831
1104
  let existingTask: (typeof tasks)[number] | null = null;
832
1105
  for (const task of tasks) {
833
- if (task.action.kind !== "knowledge_source_sync") continue;
1106
+ if (!scheduledTaskKnowledgeSource(task)) continue;
834
1107
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
835
1108
  accountId: input.accountId,
836
1109
  workspaceId: input.workspaceId,
837
- sourceId: task.action.sourceId,
1110
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
838
1111
  initiatingSubjectId: input.subjectId,
839
1112
  });
840
1113
  if (resolved?.source.externalSourceId !== identity.externalSourceId) continue;
@@ -941,7 +1214,10 @@ async function materializeSchedules(
941
1214
  name: `Sync ${selectedSource.kind === "jira_project" ? "Jira" : "Confluence"}: ${selectedSource.name}`,
942
1215
  schedule: atlassianSchedule(selectedSource.syncCadence),
943
1216
  overlapPolicy: "buffer_one",
944
- action,
1217
+ action: { kind: "agent_turn" },
1218
+ agentConfig: knowledgeSourceAgentConfig(action, existingTask.agentConfig),
1219
+ refreshPersonalResourceAuthority: true,
1220
+ authorityUpdatedBy: { kind: "subject", subjectId: input.subjectId },
945
1221
  metadata: {
946
1222
  ...existingTask.metadata,
947
1223
  connectorKind: "atlassian",
@@ -969,15 +1245,11 @@ async function materializeSchedules(
969
1245
  status: "active",
970
1246
  schedule: atlassianSchedule(selectedSource.syncCadence),
971
1247
  overlapPolicy: "buffer_one",
972
- action,
1248
+ action: { kind: "agent_turn" },
973
1249
  runMode: "new_session_per_run",
974
1250
  targetSessionId: null,
975
- agentConfig: {
976
- prompt: "Knowledge source synchronization",
977
- resources: [],
978
- tools: [],
979
- metadata: {},
980
- },
1251
+ connectionAccounts: [],
1252
+ agentConfig: knowledgeSourceAgentConfig(action),
981
1253
  variableSetId: null,
982
1254
  environmentId: null,
983
1255
  rigId: null,
@@ -1002,11 +1274,11 @@ async function materializeSchedules(
1002
1274
  }
1003
1275
 
1004
1276
  for (const task of tasks) {
1005
- if (task.action.kind !== "knowledge_source_sync") continue;
1277
+ if (!scheduledTaskKnowledgeSource(task)) continue;
1006
1278
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1007
1279
  accountId: input.accountId,
1008
1280
  workspaceId: input.workspaceId,
1009
- sourceId: task.action.sourceId,
1281
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1010
1282
  initiatingSubjectId: input.subjectId,
1011
1283
  });
1012
1284
  if (!resolved || enabledIds.has(resolved.source.externalSourceId)) continue;
@@ -1041,7 +1313,11 @@ async function materializeSchedules(
1041
1313
 
1042
1314
  async function browseJiraProjects(
1043
1315
  deps: ApiRouteDeps,
1044
- input: { workspaceId: string; subjectId: string; connectionId: string },
1316
+ input: AtlassianRequestAuthority & {
1317
+ workspaceId: string;
1318
+ subjectId: string;
1319
+ connectionId: string;
1320
+ },
1045
1321
  site: AtlassianSite,
1046
1322
  ): Promise<AtlassianBrowseItem[]> {
1047
1323
  const items: AtlassianBrowseItem[] = [];
@@ -1087,7 +1363,11 @@ async function browseJiraProjects(
1087
1363
 
1088
1364
  async function browseConfluenceSpaces(
1089
1365
  deps: ApiRouteDeps,
1090
- input: { workspaceId: string; subjectId: string; connectionId: string },
1366
+ input: AtlassianRequestAuthority & {
1367
+ workspaceId: string;
1368
+ subjectId: string;
1369
+ connectionId: string;
1370
+ },
1091
1371
  site: AtlassianSite,
1092
1372
  ): Promise<AtlassianBrowseItem[]> {
1093
1373
  const items: AtlassianBrowseItem[] = [];
@@ -1145,7 +1425,7 @@ export function confluenceNextUrl(cloudId: string, next: string): URL {
1145
1425
 
1146
1426
  async function atlassianApiRequest(
1147
1427
  deps: ApiRouteDeps,
1148
- input: {
1428
+ input: AtlassianRequestAuthority & {
1149
1429
  workspaceId: string;
1150
1430
  subjectId: string;
1151
1431
  connectionId: string;
@@ -1153,6 +1433,7 @@ async function atlassianApiRequest(
1153
1433
  label: string;
1154
1434
  },
1155
1435
  ): Promise<unknown> {
1436
+ assertNativeAtlassianAvailable();
1156
1437
  const current = await getConnectionMetadata(
1157
1438
  deps.db,
1158
1439
  input.workspaceId,
@@ -1166,9 +1447,20 @@ async function atlassianApiRequest(
1166
1447
  });
1167
1448
  const resolve = async (forceRefresh: boolean) =>
1168
1449
  await resolver({
1169
- workspaceId: input.workspaceId,
1450
+ workspaceId: input.connectionUseContext?.workspaceId ?? input.workspaceId,
1451
+ ...(input.connectionUseContext
1452
+ ? {
1453
+ connectionUseContext: {
1454
+ ...input.connectionUseContext,
1455
+ physicalRequestId: crypto.randomUUID(),
1456
+ usePhase: "credential_resolution" as const,
1457
+ },
1458
+ }
1459
+ : {}),
1170
1460
  subjectId: input.subjectId,
1171
- serverId: "atlassian-source-browser",
1461
+ serverId: input.connectionUseContext
1462
+ ? `atlassian:${input.connectionId}`
1463
+ : "atlassian-source-browser",
1172
1464
  toolName: input.label,
1173
1465
  connectionRef: {
1174
1466
  providerDomain: ATLASSIAN_PROVIDER_DOMAIN,
@@ -1325,20 +1617,20 @@ async function deauthorizeConnectionSources(
1325
1617
  connection.id,
1326
1618
  );
1327
1619
  for (const task of tasks) {
1328
- if (task.action.kind !== "knowledge_source_sync") continue;
1620
+ if (!scheduledTaskKnowledgeSource(task)) continue;
1329
1621
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1330
1622
  accountId: task.accountId,
1331
1623
  workspaceId: task.workspaceId,
1332
- sourceId: task.action.sourceId,
1624
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1333
1625
  initiatingSubjectId: subjectId,
1334
1626
  });
1335
1627
  if (!resolved || resolved.source.lifecycleState !== "active") continue;
1336
1628
  await deauthorizeKnowledgeSourceRetrieval(deps.db, {
1337
1629
  accountId: task.accountId,
1338
1630
  workspaceId: task.workspaceId,
1339
- sourceId: task.action.sourceId,
1340
- audience: task.action.destination,
1341
- operationId: `atlassian-deauthorize:${connection.id}:${connection.version + 1}:${task.action.sourceId}:${reasonCode}`,
1631
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1632
+ audience: requireScheduledTaskKnowledgeSource(task).destination,
1633
+ operationId: `atlassian-deauthorize:${connection.id}:${connection.version + 1}:${requireScheduledTaskKnowledgeSource(task).sourceId}:${reasonCode}`,
1342
1634
  reasonCode,
1343
1635
  actor: { kind: "human", subjectId, initiatingHumanSubjectId: subjectId },
1344
1636
  });
@@ -1409,6 +1701,12 @@ function readAtlassianOAuthState(raw: string | undefined, settings: Settings): A
1409
1701
  if (iat === undefined || now < iat || now - iat > oauthStateTtlMs / 1_000) {
1410
1702
  throw new HTTPException(400, { message: "expired Atlassian OAuth state" });
1411
1703
  }
1704
+ // Reject a state minted by any other flow before reading anything else. A
1705
+ // pre-cutover state carries no kind and is refused; that is the same bounded
1706
+ // `oauthStateTtlMs` window the personal-owner claim already fails closed on.
1707
+ if (payload.kind !== ATLASSIAN_OAUTH_STATE_KIND) {
1708
+ throw new HTTPException(400, { message: "invalid Atlassian OAuth state" });
1709
+ }
1412
1710
  const accountId = requiredString(payload.accountId, "state.accountId");
1413
1711
  const workspaceId = requiredString(payload.workspaceId, "state.workspaceId");
1414
1712
  const subjectId = requiredString(payload.subjectId, "state.subjectId");
@@ -1425,6 +1723,22 @@ function readAtlassianOAuthState(raw: string | undefined, settings: Settings): A
1425
1723
  accountId,
1426
1724
  workspaceId,
1427
1725
  subjectId,
1726
+ personalOwnerVerified: personalOwnerVerifiedInState(payload),
1727
+ ...(typeof payload.connectAttemptId === "string"
1728
+ ? { connectAttemptId: payload.connectAttemptId }
1729
+ : {}),
1730
+ ...(typeof payload.encryptedExternalContinuation === "string"
1731
+ ? {
1732
+ externalContinuation: ExternalActorContinuation.parse(
1733
+ JSON.parse(
1734
+ decryptEnvironmentValue(
1735
+ requireEnvironmentEncryption(settings),
1736
+ payload.encryptedExternalContinuation,
1737
+ ),
1738
+ ),
1739
+ ),
1740
+ }
1741
+ : {}),
1428
1742
  returnPath,
1429
1743
  ...(connectionId ? { connectionId, connectionVersion: connectionVersion! } : {}),
1430
1744
  nonce: requiredString(payload.nonce, "state.nonce"),
@@ -1433,14 +1747,22 @@ function readAtlassianOAuthState(raw: string | undefined, settings: Settings): A
1433
1747
  }
1434
1748
 
1435
1749
  async function requireCallbackGrant(deps: ApiRouteDeps, state: AtlassianOAuthState): Promise<void> {
1436
- const grant = await getWorkspaceGrant(deps.db, state.subjectId, state.workspaceId);
1750
+ // This connector is personal-only by construction: its start request carries
1751
+ // no ownership and the callback always writes `subjectId: state.subjectId`.
1752
+ // A state minted before the start-side principal fence existed carries no
1753
+ // `personalOwnerVerified` claim and must not land a personal Connection.
1437
1754
  if (
1438
- !grant ||
1439
- grant.accountId !== state.accountId ||
1440
- !hasPermission(grant.permissions, "connections:write")
1755
+ !personalOwnerStateAccepted({
1756
+ ownership: "personal",
1757
+ subjectId: state.subjectId,
1758
+ personalOwnerVerified: state.personalOwnerVerified,
1759
+ })
1441
1760
  ) {
1442
- throw new HTTPException(403, { message: "Atlassian OAuth subject no longer has access" });
1761
+ throw new HTTPException(422, {
1762
+ message: personalOnlyConnectionPrincipalMessage("Atlassian"),
1763
+ });
1443
1764
  }
1765
+ await requireConnectOwnerAuthority(deps.db, state, "connections:write");
1444
1766
  }
1445
1767
 
1446
1768
  function requireAtlassianSettings(settings: Settings) {
@@ -1489,11 +1811,17 @@ function returnUrl(base: string, path: string, status: "connected" | "error", va
1489
1811
  }
1490
1812
 
1491
1813
  function errorReason(error: unknown): string {
1814
+ if (error instanceof HTTPException && error.status === 410)
1815
+ return ATLASSIAN_NATIVE_RETIRED_REASON;
1492
1816
  if (error instanceof AtlassianCallbackError) return error.reason;
1493
1817
  if (error instanceof HTTPException) return `http_${error.status}`;
1494
1818
  return "connection_failed";
1495
1819
  }
1496
1820
 
1821
+ function assertNativeAtlassianAvailable(): void {
1822
+ throw new HTTPException(410, { message: ATLASSIAN_NATIVE_RETIRED_MESSAGE });
1823
+ }
1824
+
1497
1825
  class AtlassianCallbackError extends Error {
1498
1826
  constructor(readonly reason: string) {
1499
1827
  super(reason);
@@ -1545,7 +1873,12 @@ function cqlString(value: string): string {
1545
1873
 
1546
1874
  async function readJiraLiveComments(
1547
1875
  deps: ApiRouteDeps,
1548
- input: { workspaceId: string; subjectId: string; connectionId: string; id: string },
1876
+ input: AtlassianRequestAuthority & {
1877
+ workspaceId: string;
1878
+ subjectId: string;
1879
+ connectionId: string;
1880
+ id: string;
1881
+ },
1549
1882
  cloudId: string,
1550
1883
  ) {
1551
1884
  const comments: Array<{ author: string | null; createdAt: string | null; body: string }> = [];
@@ -1578,7 +1911,12 @@ async function readJiraLiveComments(
1578
1911
 
1579
1912
  async function readConfluenceLiveComments(
1580
1913
  deps: ApiRouteDeps,
1581
- input: { workspaceId: string; subjectId: string; connectionId: string; id: string },
1914
+ input: AtlassianRequestAuthority & {
1915
+ workspaceId: string;
1916
+ subjectId: string;
1917
+ connectionId: string;
1918
+ id: string;
1919
+ },
1582
1920
  cloudId: string,
1583
1921
  ) {
1584
1922
  const comments: Array<{ createdAt: string | null; content: string }> = [];