@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,53 +1,1045 @@
1
- import { ListManagedOrganizationMembershipsResponse } from "@opengeni/contracts";
2
- import { getManagedSession, type ApiRouteDeps } from "@opengeni/core";
3
1
  import {
2
+ lookupExternalIdentityForRequest,
3
+ cancelExternalWorkspaceMemberGrantForRequest,
4
+ updateExternalWorkspaceMemberForRequest,
5
+ } from "@opengeni/core";
6
+ import {
7
+ type Permission,
8
+ AcceptOrganizationInvitationRequest,
9
+ CreateAdditionalOrganizationRequest,
10
+ CreateAdditionalOrganizationResponse,
11
+ CreateOrganizationRequest,
12
+ CreateOrganizationResponse,
13
+ CreateOrganizationWorkspaceRequest,
14
+ CreateOrganizationInvitationRequest,
15
+ ListManagedOrganizationMembershipsResponse,
16
+ ListOrganizationInvitationsPageQuery,
17
+ ListOrganizationInvitationsPageResponse,
18
+ ListOrganizationAdministrationMembersResponse,
19
+ OrganizationAdministrationOverview,
20
+ OrganizationInvitation,
21
+ OrganizationUserSetupDelivery,
22
+ OrganizationMember,
23
+ OrganizationPrivateSessionSettings,
24
+ OrganizationRetentionPolicy,
25
+ OrganizationSummary,
26
+ OrganizationWorkspaceAccess,
27
+ OrganizationWorkspaceAccessMember,
28
+ PutOrganizationWorkspaceMemberRequest,
29
+ RevokeOrganizationInvitationRequest,
30
+ RetryOrganizationUserSetupDeliveryRequest,
31
+ RevokeOrganizationWorkspaceMemberRequest,
32
+ RevokeOrganizationWorkspaceMemberResponse,
33
+ UpdateOrganizationMemberRequest,
34
+ UpdateOrganizationNameRequest,
35
+ UpdateOrganizationPrivateSessionSettingsRequest,
36
+ UpdateOrganizationRetentionPolicyRequest,
37
+ UpdateOrganizationWorkspaceRequest,
38
+ UpdateWorkspaceSettingsRequest,
39
+ Workspace,
40
+ } from "@opengeni/contracts";
41
+ import {
42
+ getManagedSession,
43
+ accountScopedApiKeyWorkspaceAuthority,
44
+ organizationWorkspaceInScope,
45
+ hasPermission,
46
+ requireAccessContext,
47
+ organizationMembershipHttpStatus,
48
+ requireCanonicalLocalAccountAdministrator,
49
+ updateExternalIdentityMembershipForRequest,
50
+ type ApiRouteDeps,
51
+ } from "@opengeni/core";
52
+ import {
53
+ acceptOrganizationInvitation,
54
+ bindPendingOrganizationInvitationsForVerifiedEmail,
55
+ claimOrganizationUserSetupDelivery,
56
+ createAdditionalManagedOrganization,
57
+ createManagedOrganization,
58
+ createOrganizationWorkspace,
59
+ createOrganizationInvitation,
4
60
  ensureManagedAccessForUserWithOrganizationMemberships,
61
+ getOrganizationAdministrationOverview,
62
+ getOrganizationInvitationForAdministration,
63
+ getOrganizationPrivateSessionSettings,
64
+ getSelfOrganizationInvitation,
65
+ getOrganizationRetentionPolicy,
66
+ requireWorkspace,
67
+ listOrganizationAdministrationMembers,
68
+ listOrganizationInvitations,
69
+ listSelfOrganizationInvitations,
5
70
  nestedPostgresSqlState,
71
+ prepareOrganizationUserSetupDelivery,
72
+ revokeOrganizationInvitation,
73
+ settleOrganizationUserSetupDelivery,
74
+ putOrganizationWorkspaceMember,
75
+ revokeOrganizationWorkspaceMember,
76
+ updateOrganizationWorkspace,
77
+ updateOrganizationSharedWorkspaceSettings,
78
+ updateOrganizationMember,
79
+ updateOrganizationName,
80
+ updateOrganizationPrivateSessionSettings,
81
+ updateOrganizationRetentionPolicy,
6
82
  } from "@opengeni/db";
7
- import type { Hono } from "hono";
83
+ import type { Context, Hono } from "hono";
8
84
  import { HTTPException } from "hono/http-exception";
85
+ import { z } from "zod";
86
+ import { deleteWorkspaceForRequest } from "../workspace-deletion";
87
+ import { agentActingAsPerson, organizationSettingsPermission } from "../http/acting-person";
88
+
89
+ import {
90
+ assertOrganizationUserSetupDeliveryConfigured,
91
+ resolveOrganizationUserSetupDeliveryEmail,
92
+ } from "../auth/organization-user-setup";
93
+ import { readRequestJson } from "../http/request-body";
94
+
95
+ const OrganizationId = z.string().uuid();
96
+ const WorkspaceId = z.string().uuid();
97
+ const MembershipId = z.string().uuid();
98
+ const InvitationId = z.string().uuid();
99
+
100
+ // Organization keys administer settings, not another person's session contents.
101
+ // The database independently fences current key authority, including on replay.
102
+ async function requirePrivateSessionAdministrator(
103
+ context: Context,
104
+ deps: ApiRouteDeps,
105
+ organizationId: string,
106
+ ): Promise<{ subjectId: string }> {
107
+ const agent = agentActingAsPerson(
108
+ context,
109
+ organizationId,
110
+ organizationSettingsPermission(context),
111
+ );
112
+ if (agent) return agent;
113
+ if (!context.req.header("authorization")) return requireManagedHuman(context, deps);
114
+ const access = await requireAccessContext(context, deps);
115
+ const key = accountScopedApiKeyWorkspaceAuthority(access);
116
+ if (!key || key.accountId !== organizationId || !key.permissions.includes("workspace:admin")) {
117
+ throw new HTTPException(403, {
118
+ message: "organization administration required",
119
+ });
120
+ }
121
+ if (
122
+ key.permissionMode === "explicit" &&
123
+ !access.accountGrants.some(
124
+ (grant) => grant.accountId === organizationId && grant.permissions.includes("account:admin"),
125
+ )
126
+ ) {
127
+ throw new HTTPException(403, { message: "organization administration requires account:admin" });
128
+ }
129
+ return { subjectId: access.subjectId };
130
+ }
131
+
132
+ async function requireOrganizationKeyWorkspaceDeletion(
133
+ context: Context,
134
+ deps: ApiRouteDeps,
135
+ organizationId: string,
136
+ workspaceId: string,
137
+ ): Promise<void> {
138
+ const access = await requireAccessContext(context, deps);
139
+ const key = accountScopedApiKeyWorkspaceAuthority(access);
140
+ if (
141
+ !key ||
142
+ key.accountId !== organizationId ||
143
+ !hasPermission(key.permissions, "workspace:admin")
144
+ ) {
145
+ throw new HTTPException(403, {
146
+ message:
147
+ "deleting an organization workspace requires an organization owner session or an organization API key with workspace:admin",
148
+ });
149
+ }
150
+ const workspace = await requireWorkspace(deps.db, workspaceId).catch(() => null);
151
+ if (!workspace || workspace.accountId !== organizationId) {
152
+ throw new HTTPException(404, { message: "workspace not found" });
153
+ }
154
+ if (workspace.kind !== "shared") {
155
+ throw new HTTPException(403, {
156
+ message: "organization API keys cannot delete a Personal workspace",
157
+ });
158
+ }
159
+ if (!organizationWorkspaceInScope(key.workspaceScope, workspaceId))
160
+ throw new HTTPException(403, { message: "workspace is outside organization key scope" });
161
+ }
162
+
163
+ async function requireManagedHuman(context: Context, deps: ApiRouteDeps) {
164
+ if (
165
+ deps.settings.productAccessMode !== "managed" ||
166
+ !deps.managedAuth ||
167
+ !context.req.header("cookie") ||
168
+ context.req.header("authorization")
169
+ ) {
170
+ throw new HTTPException(401, { message: "managed human session required" });
171
+ }
172
+ const session = await getManagedSession(context, deps.managedAuth, {
173
+ db: deps.db,
174
+ sessionAdapter: deps.managedAuthSessionAdapter,
175
+ sessionSetMode: deps.settings.managedAuthSessionSetMode,
176
+ });
177
+ if (!session?.user) {
178
+ throw new HTTPException(401, { message: "managed human session required" });
179
+ }
180
+ return { session, subjectId: `user:${session.user.id}` };
181
+ }
182
+
183
+ /**
184
+ * A person acting in this organization: in their browser, or through an agent
185
+ * acting as them whose access setting includes `permission`. The database
186
+ * still checks the person's own role on every call.
187
+ */
188
+ async function requireOrganizationPerson(
189
+ context: Context,
190
+ deps: ApiRouteDeps,
191
+ permission: Permission,
192
+ ): Promise<{ subjectId: string }> {
193
+ const agent = agentActingAsPerson(context, context.req.param("organizationId") ?? "", permission);
194
+ if (agent) return agent;
195
+ return await requireManagedHuman(context, deps);
196
+ }
197
+
198
+ async function requireOrganizationAdministrator(
199
+ context: Context,
200
+ deps: ApiRouteDeps,
201
+ organizationId: string,
202
+ ): Promise<{ subjectId: string }> {
203
+ const agent = agentActingAsPerson(
204
+ context,
205
+ organizationId,
206
+ organizationSettingsPermission(context),
207
+ );
208
+ if (agent) return agent;
209
+ if (deps.settings.productAccessMode === "managed") {
210
+ return await requireManagedHuman(context, deps);
211
+ }
212
+ if (deps.settings.productAccessMode === "local") {
213
+ const { subjectId } = await requireCanonicalLocalAccountAdministrator(
214
+ context,
215
+ deps,
216
+ organizationId,
217
+ );
218
+ return { subjectId };
219
+ }
220
+ throw new HTTPException(401, {
221
+ message: "organization administrator session required",
222
+ });
223
+ }
224
+
225
+ async function parseBody<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
226
+ const parsed = schema.safeParse(await context.req.json().catch(() => null));
227
+ if (!parsed.success) {
228
+ throw new HTTPException(422, {
229
+ message: "invalid organization membership request",
230
+ });
231
+ }
232
+ return parsed.data;
233
+ }
234
+
235
+ function parseId(schema: z.ZodString, value: string, label: string): string {
236
+ const parsed = schema.safeParse(value);
237
+ if (!parsed.success) throw new HTTPException(422, { message: `invalid ${label}` });
238
+ return parsed.data;
239
+ }
240
+
241
+ function rethrowMembershipError(error: unknown, resourceLimitMessage?: string): never {
242
+ const sqlState = nestedPostgresSqlState(error);
243
+ if (sqlState === "54000" && resourceLimitMessage) {
244
+ throw new HTTPException(409, {
245
+ message: resourceLimitMessage,
246
+ });
247
+ }
248
+ const status = organizationMembershipHttpStatus(sqlState);
249
+ if (status !== null) {
250
+ throw new HTTPException(status, {
251
+ message:
252
+ status === 403
253
+ ? "organization administration is not authorized"
254
+ : status === 404
255
+ ? "organization resource not found"
256
+ : status === 409
257
+ ? "organization membership state changed; refresh and retry"
258
+ : "invalid organization membership operation",
259
+ });
260
+ }
261
+ throw error;
262
+ }
9
263
 
10
264
  export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDeps): void {
11
- app.get("/v1/organization-memberships", async (context) => {
12
- if (
13
- deps.settings.productAccessMode !== "managed" ||
14
- !deps.managedAuth ||
15
- !context.req.header("cookie") ||
16
- context.req.header("authorization")
17
- ) {
18
- throw new HTTPException(401, {
19
- message: "managed human session required",
20
- });
265
+ // POST carries an opaque identity reference; this lookup never provisions.
266
+ app.post("/v1/organizations/:organizationId/external-identities/lookup", async (context) => {
267
+ const organizationId = parseId(
268
+ OrganizationId,
269
+ context.req.param("organizationId"),
270
+ "organization id",
271
+ );
272
+ return context.json(
273
+ await lookupExternalIdentityForRequest(
274
+ context,
275
+ deps,
276
+ organizationId,
277
+ await context.req.json().catch(() => null),
278
+ ),
279
+ );
280
+ });
281
+ app.patch("/v1/organizations/:organizationId/external-members/:membershipId", async (context) => {
282
+ const organizationId = parseId(
283
+ OrganizationId,
284
+ context.req.param("organizationId"),
285
+ "organization id",
286
+ );
287
+ const membershipId = parseId(MembershipId, context.req.param("membershipId"), "membership id");
288
+ try {
289
+ return context.json(
290
+ await updateExternalIdentityMembershipForRequest(
291
+ context,
292
+ deps,
293
+ organizationId,
294
+ membershipId,
295
+ await context.req.json().catch(() => null),
296
+ ),
297
+ );
298
+ } catch (error) {
299
+ if (error instanceof HTTPException) throw error;
300
+ rethrowMembershipError(error);
301
+ }
302
+ });
303
+ app.post("/v1/organizations", async (context) => {
304
+ const { session, subjectId } = await requireManagedHuman(context, deps);
305
+ const payload = await parseBody(context, CreateOrganizationRequest);
306
+ try {
307
+ return context.json(
308
+ CreateOrganizationResponse.parse(
309
+ await createManagedOrganization(deps.db, {
310
+ subjectId,
311
+ subjectLabel: session.user.email || session.user.name,
312
+ ...payload,
313
+ trialCreditsEnabled:
314
+ deps.settings.productAccessMode === "managed" &&
315
+ deps.settings.verifiedSignupTrialCreditsEnabled,
316
+ }),
317
+ ),
318
+ 201,
319
+ );
320
+ } catch (error) {
321
+ rethrowMembershipError(error);
21
322
  }
323
+ });
22
324
 
23
- const session = await getManagedSession(context, deps.managedAuth, {
24
- db: deps.db,
25
- });
26
- if (!session?.user) {
27
- throw new HTTPException(401, {
28
- message: "managed human session required",
29
- });
325
+ app.post("/v1/organizations/additional", async (context) => {
326
+ const { session, subjectId } = await requireManagedHuman(context, deps);
327
+ const payload = await parseBody(context, CreateAdditionalOrganizationRequest);
328
+ try {
329
+ return context.json(
330
+ CreateAdditionalOrganizationResponse.parse(
331
+ await createAdditionalManagedOrganization(deps.db, {
332
+ subjectId,
333
+ subjectLabel: session.user.email || session.user.name,
334
+ ...payload,
335
+ }),
336
+ ),
337
+ 201,
338
+ );
339
+ } catch (error) {
340
+ rethrowMembershipError(error, "additional organization limit reached");
30
341
  }
342
+ });
31
343
 
32
- let result;
344
+ app.get("/v1/organization-memberships", async (context) => {
345
+ const { session } = await requireManagedHuman(context, deps);
33
346
  try {
34
- result = await ensureManagedAccessForUserWithOrganizationMemberships(deps.db, {
347
+ const result = await ensureManagedAccessForUserWithOrganizationMemberships(deps.db, {
35
348
  userId: session.user.id,
36
349
  email: session.user.email,
37
350
  name: session.user.name,
351
+ emailVerified: session.user.emailVerified,
352
+ provisionFallbackOrganization: false,
38
353
  });
354
+ return context.json(
355
+ ListManagedOrganizationMembershipsResponse.parse({
356
+ memberships: result.organizationMemberships,
357
+ }),
358
+ );
39
359
  } catch (error) {
360
+ // Not dead after 0348: a terminal-only membership is now a bounded EMPTY
361
+ // projection rather than a 42501, but the verified-email binder still
362
+ // raises 42501 when the session claims `emailVerified` and the durable
363
+ // `auth_users` row disagrees (an email change clears it), so this stays
364
+ // the correct deny for a stale-verification session.
40
365
  if (nestedPostgresSqlState(error) === "42501") {
41
366
  throw new HTTPException(403, {
42
367
  message: "organization membership is not active",
43
368
  });
44
369
  }
45
- throw error;
370
+ rethrowMembershipError(error);
46
371
  }
47
- return context.json(
48
- ListManagedOrganizationMembershipsResponse.parse({
49
- memberships: result.organizationMemberships,
50
- }),
372
+ });
373
+
374
+ app.get("/v1/organization-invitations", async (context) => {
375
+ const { session, subjectId } = await requireManagedHuman(context, deps);
376
+ const query = ListOrganizationInvitationsPageQuery.safeParse(context.req.query());
377
+ if (!query.success) {
378
+ throw new HTTPException(422, {
379
+ message: "invalid organization invitation page",
380
+ });
381
+ }
382
+ try {
383
+ if (session.user.emailVerified) {
384
+ await bindPendingOrganizationInvitationsForVerifiedEmail(deps.db, {
385
+ subjectId,
386
+ email: session.user.email,
387
+ });
388
+ }
389
+ return context.json(
390
+ ListOrganizationInvitationsPageResponse.parse(
391
+ await listSelfOrganizationInvitations(deps.db, {
392
+ subjectId,
393
+ ...(query.data.cursor === undefined ? {} : { cursor: query.data.cursor }),
394
+ limit: query.data.limit,
395
+ }),
396
+ ),
397
+ );
398
+ } catch (error) {
399
+ rethrowMembershipError(error);
400
+ }
401
+ });
402
+
403
+ app.get("/v1/organizations/:organizationId/overview", async (context) => {
404
+ const organizationId = parseId(
405
+ OrganizationId,
406
+ context.req.param("organizationId"),
407
+ "organization id",
408
+ );
409
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
410
+ try {
411
+ return context.json(
412
+ OrganizationAdministrationOverview.parse(
413
+ await getOrganizationAdministrationOverview(deps.db, {
414
+ organizationId,
415
+ actorSubjectId: subjectId,
416
+ }),
417
+ ),
418
+ );
419
+ } catch (error) {
420
+ rethrowMembershipError(error);
421
+ }
422
+ });
423
+
424
+ app.patch("/v1/organizations/:organizationId", async (context) => {
425
+ const organizationId = parseId(
426
+ OrganizationId,
427
+ context.req.param("organizationId"),
428
+ "organization id",
429
+ );
430
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
431
+ const payload = await parseBody(context, UpdateOrganizationNameRequest);
432
+ try {
433
+ return context.json(
434
+ OrganizationSummary.parse(
435
+ await updateOrganizationName(deps.db, {
436
+ organizationId,
437
+ actorSubjectId: subjectId,
438
+ ...payload,
439
+ }),
440
+ ),
441
+ );
442
+ } catch (error) {
443
+ rethrowMembershipError(error);
444
+ }
445
+ });
446
+
447
+ app.patch("/v1/organizations/:organizationId/workspaces/:workspaceId", async (context) => {
448
+ const organizationId = parseId(
449
+ OrganizationId,
450
+ context.req.param("organizationId"),
451
+ "organization id",
452
+ );
453
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
454
+ const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
455
+ const payload = await parseBody(context, UpdateOrganizationWorkspaceRequest);
456
+ try {
457
+ return context.json(
458
+ OrganizationWorkspaceAccess.parse(
459
+ await updateOrganizationWorkspace(deps.db, {
460
+ organizationId,
461
+ workspaceId,
462
+ actorSubjectId: subjectId,
463
+ name: payload.name.trim(),
464
+ expectedUpdatedAt: payload.expectedUpdatedAt,
465
+ operationId: payload.operationId,
466
+ }),
467
+ ),
468
+ );
469
+ } catch (error) {
470
+ rethrowMembershipError(error);
471
+ }
472
+ });
473
+
474
+ app.post("/v1/organizations/:organizationId/workspaces", async (context) => {
475
+ const organizationId = parseId(
476
+ OrganizationId,
477
+ context.req.param("organizationId"),
478
+ "organization id",
51
479
  );
480
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
481
+ const payload = await parseBody(context, CreateOrganizationWorkspaceRequest);
482
+ try {
483
+ return context.json(
484
+ OrganizationWorkspaceAccess.parse(
485
+ await createOrganizationWorkspace(deps.db, {
486
+ organizationId,
487
+ actorSubjectId: subjectId,
488
+ name: payload.name.trim(),
489
+ operationId: payload.operationId,
490
+ }),
491
+ ),
492
+ 201,
493
+ );
494
+ } catch (error) {
495
+ rethrowMembershipError(error);
496
+ }
497
+ });
498
+
499
+ app.delete("/v1/organizations/:organizationId/workspaces/:workspaceId", async (context) => {
500
+ const organizationId = parseId(
501
+ OrganizationId,
502
+ context.req.param("organizationId"),
503
+ "organization id",
504
+ );
505
+ if (context.req.header("authorization")) {
506
+ // An integrating backend deletes the organization workspaces it
507
+ // provisions (tenant offboarding, test cleanup) with its organization
508
+ // key. This is exactly the authority `DELETE /v1/workspaces/:id` already
509
+ // grants that key - an organization workspace (never a Personal one) in
510
+ // the key's own organization, with `workspace:admin` - under the same
511
+ // quiescence rules. It confers no organization-administrator identity.
512
+ const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
513
+ await requireOrganizationKeyWorkspaceDeletion(context, deps, organizationId, workspaceId);
514
+ await deleteWorkspaceForRequest(deps, { accountId: organizationId, workspaceId });
515
+ return context.body(null, 204);
516
+ }
517
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
518
+ const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
519
+ await deleteWorkspaceForRequest(deps, {
520
+ accountId: organizationId,
521
+ workspaceId,
522
+ organizationAdministratorSubjectId: subjectId,
523
+ });
524
+ return context.body(null, 204);
525
+ });
526
+
527
+ app.patch(
528
+ "/v1/organizations/:organizationId/workspaces/:workspaceId/settings",
529
+ async (context) => {
530
+ const organizationId = parseId(
531
+ OrganizationId,
532
+ context.req.param("organizationId"),
533
+ "organization id",
534
+ );
535
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
536
+ const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
537
+ const payload = await parseBody(context, UpdateWorkspaceSettingsRequest);
538
+ try {
539
+ return context.json(
540
+ Workspace.parse(
541
+ await updateOrganizationSharedWorkspaceSettings(deps.db, {
542
+ organizationId,
543
+ workspaceId,
544
+ actorSubjectId: subjectId,
545
+ patch: payload,
546
+ }),
547
+ ),
548
+ );
549
+ } catch (error) {
550
+ rethrowMembershipError(error);
551
+ }
552
+ },
553
+ );
554
+
555
+ app.put(
556
+ "/v1/organizations/:organizationId/workspaces/:workspaceId/members/:membershipId",
557
+ async (context) => {
558
+ const { subjectId } = await requireOrganizationPerson(context, deps, "members:manage");
559
+ const organizationId = parseId(
560
+ OrganizationId,
561
+ context.req.param("organizationId"),
562
+ "organization id",
563
+ );
564
+ const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
565
+ const membershipId = parseId(
566
+ MembershipId,
567
+ context.req.param("membershipId"),
568
+ "membership id",
569
+ );
570
+ const payload = await parseBody(context, PutOrganizationWorkspaceMemberRequest);
571
+ try {
572
+ return context.json(
573
+ OrganizationWorkspaceAccessMember.parse(
574
+ await putOrganizationWorkspaceMember(deps.db, {
575
+ organizationId,
576
+ workspaceId,
577
+ actorSubjectId: subjectId,
578
+ targetOrganizationMembershipId: membershipId,
579
+ access: payload,
580
+ }),
581
+ ),
582
+ );
583
+ } catch (error) {
584
+ rethrowMembershipError(error);
585
+ }
586
+ },
587
+ );
588
+
589
+ // Organization service key: replace an existing external member's
590
+ // permissions in one shared workspace (keyed, idempotent, non-destructive).
591
+ app.patch(
592
+ "/v1/organizations/:organizationId/workspaces/:workspaceId/external-members/:membershipId",
593
+ async (context) =>
594
+ context.json(
595
+ await updateExternalWorkspaceMemberForRequest(
596
+ context,
597
+ deps,
598
+ parseId(OrganizationId, context.req.param("organizationId"), "organization id"),
599
+ parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id"),
600
+ parseId(MembershipId, context.req.param("membershipId"), "membership id"),
601
+ await readRequestJson(context),
602
+ ),
603
+ ),
604
+ );
605
+
606
+ app.post(
607
+ "/v1/organizations/:organizationId/workspaces/:workspaceId/members/:membershipId/revoke",
608
+ async (context) => {
609
+ const input: unknown = await context.req.json().catch(() => null);
610
+ if (input && typeof input === "object" && "cancelGrantOperationId" in input) {
611
+ return context.json(
612
+ await cancelExternalWorkspaceMemberGrantForRequest(
613
+ context,
614
+ deps,
615
+ parseId(OrganizationId, context.req.param("organizationId"), "organization id"),
616
+ parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id"),
617
+ parseId(MembershipId, context.req.param("membershipId"), "membership id"),
618
+ input,
619
+ ),
620
+ );
621
+ }
622
+ const { subjectId } = await requireOrganizationPerson(context, deps, "members:manage");
623
+ const organizationId = parseId(
624
+ OrganizationId,
625
+ context.req.param("organizationId"),
626
+ "organization id",
627
+ );
628
+ const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
629
+ const membershipId = parseId(
630
+ MembershipId,
631
+ context.req.param("membershipId"),
632
+ "membership id",
633
+ );
634
+ const payload = await parseBody(context, RevokeOrganizationWorkspaceMemberRequest);
635
+ try {
636
+ return context.json(
637
+ RevokeOrganizationWorkspaceMemberResponse.parse(
638
+ await revokeOrganizationWorkspaceMember(deps.db, {
639
+ organizationId,
640
+ workspaceId,
641
+ actorSubjectId: subjectId,
642
+ targetOrganizationMembershipId: membershipId,
643
+ expectedUpdatedAt: payload.expectedUpdatedAt,
644
+ operationId: payload.operationId,
645
+ }),
646
+ ),
647
+ );
648
+ } catch (error) {
649
+ rethrowMembershipError(error);
650
+ }
651
+ },
652
+ );
653
+
654
+ app.get("/v1/organizations/:organizationId/private-session-settings", async (context) => {
655
+ const organizationId = parseId(
656
+ OrganizationId,
657
+ context.req.param("organizationId"),
658
+ "organization id",
659
+ );
660
+ const { subjectId } = await requirePrivateSessionAdministrator(context, deps, organizationId);
661
+ try {
662
+ return context.json(
663
+ OrganizationPrivateSessionSettings.parse(
664
+ await getOrganizationPrivateSessionSettings(deps.db, {
665
+ organizationId,
666
+ actorSubjectId: subjectId,
667
+ }),
668
+ ),
669
+ );
670
+ } catch (error) {
671
+ rethrowMembershipError(error);
672
+ }
673
+ });
674
+
675
+ app.patch("/v1/organizations/:organizationId/private-session-settings", async (context) => {
676
+ const organizationId = parseId(
677
+ OrganizationId,
678
+ context.req.param("organizationId"),
679
+ "organization id",
680
+ );
681
+ const { subjectId } = await requirePrivateSessionAdministrator(context, deps, organizationId);
682
+ const payload = await parseBody(context, UpdateOrganizationPrivateSessionSettingsRequest);
683
+ try {
684
+ return context.json(
685
+ OrganizationPrivateSessionSettings.parse(
686
+ await updateOrganizationPrivateSessionSettings(deps.db, {
687
+ organizationId,
688
+ actorSubjectId: subjectId,
689
+ ...payload,
690
+ }),
691
+ ),
692
+ );
693
+ } catch (error) {
694
+ if (nestedPostgresSqlState(error) === "55000") {
695
+ throw new HTTPException(409, {
696
+ message: "private-session readiness is not activated for this organization",
697
+ });
698
+ }
699
+ rethrowMembershipError(error);
700
+ }
701
+ });
702
+
703
+ app.post("/v1/organizations/:organizationId/invitations", async (context) => {
704
+ const { subjectId } = await requireOrganizationPerson(context, deps, "members:manage");
705
+ const organizationId = parseId(
706
+ OrganizationId,
707
+ context.req.param("organizationId"),
708
+ "organization id",
709
+ );
710
+ const payload = await parseBody(context, CreateOrganizationInvitationRequest);
711
+ // Bearer construction configuration must fail before the invitation
712
+ // commits. Provider outcomes are journaled durably after this boundary.
713
+ try {
714
+ assertOrganizationUserSetupDeliveryConfigured(deps.settings, deps.managedEmailTransport);
715
+ } catch {
716
+ throw new HTTPException(503, {
717
+ message: "invited-user account setup delivery is not configured on this deployment",
718
+ });
719
+ }
720
+ try {
721
+ const invitation = OrganizationInvitation.parse(
722
+ await createOrganizationInvitation(deps.db, {
723
+ organizationId,
724
+ actorSubjectId: subjectId,
725
+ operationId: payload.operationId,
726
+ targetSubjectId: null,
727
+ targetEmail: payload.email.trim().toLowerCase(),
728
+ ...(payload.name === undefined ? {} : { targetName: payload.name }),
729
+ initialWorkspaceIds: payload.initialWorkspaceIds,
730
+ role: payload.role,
731
+ expiresAt: payload.expiresAt,
732
+ }),
733
+ );
734
+ await deliverOrganizationUserSetup(deps, {
735
+ organizationId,
736
+ actorSubjectId: subjectId,
737
+ invitationId: invitation.id,
738
+ invitationOperationId: payload.operationId,
739
+ operationId: payload.operationId,
740
+ });
741
+ return context.json(
742
+ OrganizationInvitation.parse(
743
+ await getOrganizationInvitationForAdministration(deps.db, {
744
+ organizationId,
745
+ actorSubjectId: subjectId,
746
+ invitationId: invitation.id,
747
+ }),
748
+ ),
749
+ 201,
750
+ );
751
+ } catch (error) {
752
+ if (error instanceof HTTPException) throw error;
753
+ rethrowMembershipError(error);
754
+ }
755
+ });
756
+
757
+ app.get("/v1/organizations/:organizationId/invitations", async (context) => {
758
+ const { subjectId } = await requireOrganizationPerson(context, deps, "account:read");
759
+ const organizationId = parseId(
760
+ OrganizationId,
761
+ context.req.param("organizationId"),
762
+ "organization id",
763
+ );
764
+ const query = ListOrganizationInvitationsPageQuery.safeParse(context.req.query());
765
+ if (!query.success) {
766
+ throw new HTTPException(422, {
767
+ message: "invalid organization invitation page",
768
+ });
769
+ }
770
+ try {
771
+ return context.json(
772
+ ListOrganizationInvitationsPageResponse.parse(
773
+ await listOrganizationInvitations(deps.db, {
774
+ organizationId,
775
+ actorSubjectId: subjectId,
776
+ ...(query.data.cursor === undefined ? {} : { cursor: query.data.cursor }),
777
+ limit: query.data.limit,
778
+ }),
779
+ ),
780
+ );
781
+ } catch (error) {
782
+ rethrowMembershipError(error);
783
+ }
784
+ });
785
+
786
+ app.post(
787
+ "/v1/organizations/:organizationId/invitations/:invitationId/delivery/retry",
788
+ async (context) => {
789
+ const { subjectId } = await requireOrganizationPerson(context, deps, "members:manage");
790
+ const organizationId = parseId(
791
+ OrganizationId,
792
+ context.req.param("organizationId"),
793
+ "organization id",
794
+ );
795
+ const invitationId = parseId(
796
+ InvitationId,
797
+ context.req.param("invitationId"),
798
+ "invitation id",
799
+ );
800
+ const payload = await parseBody(context, RetryOrganizationUserSetupDeliveryRequest);
801
+ try {
802
+ assertOrganizationUserSetupDeliveryConfigured(deps.settings, deps.managedEmailTransport);
803
+ } catch {
804
+ throw new HTTPException(503, {
805
+ message: "invited-user account setup delivery is not configured on this deployment",
806
+ });
807
+ }
808
+ try {
809
+ return context.json(
810
+ OrganizationUserSetupDelivery.parse(
811
+ await deliverOrganizationUserSetup(deps, {
812
+ organizationId,
813
+ actorSubjectId: subjectId,
814
+ invitationId,
815
+ operationId: payload.operationId,
816
+ }),
817
+ ),
818
+ );
819
+ } catch (error) {
820
+ rethrowMembershipError(error);
821
+ }
822
+ },
823
+ );
824
+
825
+ app.post("/v1/organization-invitations/:invitationId/accept", async (context) => {
826
+ const { session, subjectId } = await requireManagedHuman(context, deps);
827
+ const invitationId = parseId(InvitationId, context.req.param("invitationId"), "invitation id");
828
+ const payload = await parseBody(context, AcceptOrganizationInvitationRequest);
829
+ try {
830
+ if (session.user.emailVerified) {
831
+ await bindPendingOrganizationInvitationsForVerifiedEmail(deps.db, {
832
+ subjectId,
833
+ email: session.user.email,
834
+ });
835
+ }
836
+ const invitation = await getSelfOrganizationInvitation(deps.db, {
837
+ subjectId,
838
+ invitationId,
839
+ });
840
+ const result = await acceptOrganizationInvitation(deps.db, {
841
+ organizationId: invitation.organizationId,
842
+ actorSubjectId: subjectId,
843
+ operationId: payload.operationId,
844
+ invitationId,
845
+ expectedRevision: payload.expectedRevision,
846
+ });
847
+ return context.json({
848
+ invitation: OrganizationInvitation.parse(result.invitation),
849
+ membership: OrganizationMember.parse(result.membership),
850
+ });
851
+ } catch (error) {
852
+ if (error instanceof HTTPException) throw error;
853
+ rethrowMembershipError(error);
854
+ }
855
+ });
856
+
857
+ app.post(
858
+ "/v1/organizations/:organizationId/invitations/:invitationId/revoke",
859
+ async (context) => {
860
+ const { subjectId } = await requireOrganizationPerson(context, deps, "members:manage");
861
+ const organizationId = parseId(
862
+ OrganizationId,
863
+ context.req.param("organizationId"),
864
+ "organization id",
865
+ );
866
+ const invitationId = parseId(
867
+ InvitationId,
868
+ context.req.param("invitationId"),
869
+ "invitation id",
870
+ );
871
+ const payload = await parseBody(context, RevokeOrganizationInvitationRequest);
872
+ try {
873
+ return context.json(
874
+ OrganizationInvitation.parse(
875
+ await revokeOrganizationInvitation(deps.db, {
876
+ organizationId,
877
+ actorSubjectId: subjectId,
878
+ operationId: payload.operationId,
879
+ invitationId,
880
+ expectedRevision: payload.expectedRevision,
881
+ }),
882
+ ),
883
+ );
884
+ } catch (error) {
885
+ rethrowMembershipError(error);
886
+ }
887
+ },
888
+ );
889
+
890
+ app.get("/v1/organizations/:organizationId/members", async (context) => {
891
+ const organizationId = parseId(
892
+ OrganizationId,
893
+ context.req.param("organizationId"),
894
+ "organization id",
895
+ );
896
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
897
+ try {
898
+ return context.json(
899
+ ListOrganizationAdministrationMembersResponse.parse({
900
+ members: await listOrganizationAdministrationMembers(deps.db, {
901
+ organizationId,
902
+ actorSubjectId: subjectId,
903
+ }),
904
+ }),
905
+ );
906
+ } catch (error) {
907
+ rethrowMembershipError(error);
908
+ }
909
+ });
910
+
911
+ app.patch("/v1/organizations/:organizationId/members/:membershipId", async (context) => {
912
+ const { subjectId } = await requireOrganizationPerson(context, deps, "members:manage");
913
+ const organizationId = parseId(
914
+ OrganizationId,
915
+ context.req.param("organizationId"),
916
+ "organization id",
917
+ );
918
+ const membershipId = parseId(MembershipId, context.req.param("membershipId"), "membership id");
919
+ const payload = await parseBody(context, UpdateOrganizationMemberRequest);
920
+ try {
921
+ return context.json(
922
+ OrganizationMember.parse(
923
+ await updateOrganizationMember(deps.db, {
924
+ organizationId,
925
+ actorSubjectId: subjectId,
926
+ operationId: payload.operationId,
927
+ membershipId,
928
+ transition: payload,
929
+ }),
930
+ ),
931
+ );
932
+ } catch (error) {
933
+ rethrowMembershipError(error);
934
+ }
935
+ });
936
+
937
+ app.get("/v1/organizations/:organizationId/retention-policy", async (context) => {
938
+ const organizationId = parseId(
939
+ OrganizationId,
940
+ context.req.param("organizationId"),
941
+ "organization id",
942
+ );
943
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
944
+ try {
945
+ return context.json(
946
+ OrganizationRetentionPolicy.parse(
947
+ await getOrganizationRetentionPolicy(deps.db, {
948
+ organizationId,
949
+ actorSubjectId: subjectId,
950
+ }),
951
+ ),
952
+ );
953
+ } catch (error) {
954
+ rethrowMembershipError(error);
955
+ }
956
+ });
957
+
958
+ app.patch("/v1/organizations/:organizationId/retention-policy", async (context) => {
959
+ const organizationId = parseId(
960
+ OrganizationId,
961
+ context.req.param("organizationId"),
962
+ "organization id",
963
+ );
964
+ const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId);
965
+ const payload = await parseBody(context, UpdateOrganizationRetentionPolicyRequest);
966
+ try {
967
+ return context.json(
968
+ OrganizationRetentionPolicy.parse(
969
+ await updateOrganizationRetentionPolicy(deps.db, {
970
+ organizationId,
971
+ actorSubjectId: subjectId,
972
+ operationId: payload.operationId,
973
+ mode: payload.mode,
974
+ retentionDays: payload.retentionDays,
975
+ expectedVersion: payload.expectedVersion,
976
+ }),
977
+ ),
978
+ );
979
+ } catch (error) {
980
+ rethrowMembershipError(error);
981
+ }
982
+ });
983
+ }
984
+
985
+ async function deliverOrganizationUserSetup(
986
+ deps: ApiRouteDeps,
987
+ input: {
988
+ organizationId: string;
989
+ actorSubjectId: string;
990
+ invitationId: string;
991
+ invitationOperationId?: string;
992
+ operationId: string;
993
+ },
994
+ ) {
995
+ const claim = await claimOrganizationUserSetupDelivery(deps.db, input);
996
+ if (!claim.claimed) return claim.delivery;
997
+ const prepared = await resolveOrganizationUserSetupDeliveryEmail(deps.settings, {
998
+ invitationId: claim.invitationId,
999
+ deliveryId: claim.delivery.id,
1000
+ senderEmail: deps.managedEmailTransport.sender,
1001
+ recipientEmail: claim.recipientEmail,
1002
+ recipientName: claim.recipientName,
1003
+ organizationName: claim.organizationName,
1004
+ organizationRole: claim.organizationRole,
1005
+ sharedWorkspaceAccess: claim.sharedWorkspaceAccess,
1006
+ providerIdempotencyScope: deps.managedEmailTransport.idempotency.scope,
1007
+ frozenTransport: claim.setupTokenTransport,
1008
+ frozenPayloadDigest: claim.payloadDigest,
1009
+ });
1010
+ await prepareOrganizationUserSetupDelivery(deps.db, {
1011
+ organizationId: input.organizationId,
1012
+ actorSubjectId: input.actorSubjectId,
1013
+ deliveryId: claim.delivery.id,
1014
+ attemptId: claim.attemptId,
1015
+ claimHolderId: claim.claimHolderId,
1016
+ tokenDigest: prepared.tokenDigest,
1017
+ payloadDigest: prepared.payloadDigest,
1018
+ setupTokenTransport: prepared.transport,
1019
+ providerIdempotencyScope: deps.managedEmailTransport.idempotency.scope,
1020
+ providerIdempotencyRetentionSeconds: deps.managedEmailTransport.idempotency.retentionSeconds,
1021
+ });
1022
+ let outcome:
1023
+ | { status: "sent"; providerMessageId: string | null }
1024
+ | { status: "failed" | "outcome_unknown"; errorClass: string };
1025
+ try {
1026
+ outcome = await deps.managedEmailTransport.send({
1027
+ kind: "organization_user_setup",
1028
+ ...prepared.message,
1029
+ idempotencyKey: claim.providerKey,
1030
+ });
1031
+ } catch {
1032
+ outcome = { status: "outcome_unknown", errorClass: "transport_threw" };
1033
+ }
1034
+ return await settleOrganizationUserSetupDelivery(deps.db, {
1035
+ organizationId: input.organizationId,
1036
+ actorSubjectId: input.actorSubjectId,
1037
+ deliveryId: claim.delivery.id,
1038
+ attemptId: claim.attemptId,
1039
+ claimHolderId: claim.claimHolderId,
1040
+ outcome: outcome.status,
1041
+ ...(outcome.status === "sent"
1042
+ ? { providerMessageId: outcome.providerMessageId }
1043
+ : { errorClass: outcome.errorClass }),
52
1044
  });
53
1045
  }