@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
@@ -5,6 +5,7 @@ import {
5
5
  resolveWorkspaceVoiceInputEnabled,
6
6
  TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS,
7
7
  TRANSCRIPTION_RECORDING_RECOVERY_RETRY_AFTER_MILLISECONDS,
8
+ WorkspaceVoiceInputSettings,
8
9
  type TranscriptionRecordingErrorCode,
9
10
  type TranscriptionRecordingResponse,
10
11
  type UploadTranscriptionRecordingChunkResponse,
@@ -17,6 +18,7 @@ import {
17
18
  TranscriptionServiceError,
18
19
  type ApiRouteDeps,
19
20
  } from "@opengeni/core";
21
+ import { retryWhileMissing } from "@opengeni/storage";
20
22
  import {
21
23
  claimNextTranscriptionRecordingSegment,
22
24
  claimTranscriptionRecordingAssembly,
@@ -57,6 +59,7 @@ type RecordingAuthority = {
57
59
  accountId: string;
58
60
  workspaceId: string;
59
61
  subjectId: string;
62
+ voiceInput?: WorkspaceVoiceInputSettings | undefined;
60
63
  };
61
64
 
62
65
  class RecordingProcessingError extends Error {
@@ -356,6 +359,8 @@ export function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDe
356
359
  let authority: RecordingAuthority | null = null;
357
360
  let attemptId: string | null = null;
358
361
  let segmentNumber: number | null = null;
362
+ let attemptedProvider: string | null = null;
363
+ let fallbackAllowed = false;
359
364
  try {
360
365
  authority = await requireRecordingAuthority(c, deps, true);
361
366
  const service = deps.transcription;
@@ -371,6 +376,8 @@ export function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDe
371
376
  }
372
377
  const selectedProvider = service.selectProvider
373
378
  ? await service.selectProvider({
379
+ preferredProvider: authority.voiceInput?.preferredProvider,
380
+ fallbackEnabled: authority.voiceInput?.fallbackEnabled,
374
381
  workspaceId: authority.workspaceId,
375
382
  subjectId: authority.subjectId,
376
383
  })
@@ -394,7 +401,12 @@ export function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDe
394
401
  );
395
402
  }
396
403
  segmentNumber = claim.segment.segmentNumber;
397
- const stored = await deps.objectStorage?.getObjectBytes(claim.segment.objectKey);
404
+ attemptedProvider = claim.segment.providerId;
405
+ fallbackAllowed = claim.fallbackAllowed === true;
406
+ const segmentKey = claim.segment.objectKey;
407
+ const stored = deps.objectStorage
408
+ ? await retryWhileMissing(async () => deps.objectStorage!.getObjectBytes(segmentKey))
409
+ : null;
398
410
  if (!stored) {
399
411
  throw new RecordingProcessingError("Provider segment is missing", "invalid_audio", false);
400
412
  }
@@ -448,12 +460,32 @@ export function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDe
448
460
  } catch (error) {
449
461
  if (authority && attemptId && segmentNumber !== null) {
450
462
  const failure = processingFailure(error);
463
+ // Only an explicit rejection may move an untouched recording to another vendor.
464
+ // The database fences this against stale attempts and completed segments.
465
+ const fallbackProviderId =
466
+ fallbackAllowed &&
467
+ error instanceof TranscriptionServiceError &&
468
+ error.fallbackSafe &&
469
+ authority.voiceInput?.fallbackEnabled !== false &&
470
+ attemptedProvider &&
471
+ deps.transcription?.selectProvider
472
+ ? await Promise.resolve(
473
+ deps.transcription.selectProvider({
474
+ workspaceId: authority.workspaceId,
475
+ subjectId: authority.subjectId,
476
+ preferredProvider: authority.voiceInput?.preferredProvider,
477
+ excludedProviders: [attemptedProvider],
478
+ afterProvider: attemptedProvider,
479
+ }),
480
+ ).catch(() => null)
481
+ : null;
451
482
  const persisted = await failTranscriptionRecordingSegment(deps.db, {
452
483
  workspaceId: authority.workspaceId,
453
484
  subjectId: authority.subjectId,
454
485
  recordingId: uuidParam(c, "recordingId"),
455
486
  segmentNumber,
456
487
  attemptId,
488
+ fallbackProviderId,
457
489
  errorCode: failure.code,
458
490
  retryable: failure.retryable,
459
491
  }).catch(() => null);
@@ -501,14 +533,15 @@ async function requireRecordingAuthority(
501
533
  throw new TranscriptionRecordingNotFoundError("Workspace not found");
502
534
  }
503
535
  const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:create");
536
+ const workspace = await getWorkspace(deps.db, workspaceId);
504
537
  if (requirePolicy) {
505
- const workspace = await getWorkspace(deps.db, workspaceId);
506
538
  if (!workspace) throw new TranscriptionRecordingNotFoundError("Workspace not found");
507
539
  if (resolveWorkspaceVoiceInputEnabled(workspace.settings) === false) {
508
540
  throw new RecordingProcessingError("Voice input is disabled", "policy_blocked", false);
509
541
  }
510
542
  }
511
543
  return {
544
+ voiceInput: WorkspaceVoiceInputSettings.safeParse(workspace?.settings.voiceInput).data,
512
545
  accountId: grant.accountId,
513
546
  workspaceId,
514
547
  subjectId: grant.subjectId,
@@ -578,7 +611,9 @@ async function* verifiedChunkBytes(
578
611
  }
579
612
  let stored: Awaited<ReturnType<NonNullable<ApiRouteDeps["objectStorage"]>["getObjectBytes"]>>;
580
613
  try {
581
- stored = await deps.objectStorage!.getObjectBytes(chunk.objectKey);
614
+ stored = await retryWhileMissing(async () =>
615
+ deps.objectStorage!.getObjectBytes(chunk.objectKey),
616
+ );
582
617
  } catch {
583
618
  throw new RecordingProcessingError("Chunk download failed", "network", true);
584
619
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  resolveWorkspaceVoiceInputEnabled,
3
+ WorkspaceVoiceInputSettings,
3
4
  type TranscribeAudioResponse,
4
5
  } from "@opengeni/contracts";
5
6
  import { type ApiRouteDeps, requireAccessGrant, TranscriptionServiceError } from "@opengeni/core";
@@ -17,6 +18,7 @@ export function registerTranscriptionRoutes(app: Hono, deps: ApiRouteDeps): void
17
18
  if (resolveWorkspaceVoiceInputEnabled(workspace.settings) === false) {
18
19
  return c.json({ code: "policy_blocked" }, 403);
19
20
  }
21
+ const preferences = WorkspaceVoiceInputSettings.safeParse(workspace.settings.voiceInput).data;
20
22
  const service = deps.transcription;
21
23
  if (!service || !(await service.available({ workspaceId, subjectId: grant.subjectId }))) {
22
24
  return c.json({ code: "unavailable" }, 503);
@@ -24,6 +26,8 @@ export function registerTranscriptionRoutes(app: Hono, deps: ApiRouteDeps): void
24
26
  try {
25
27
  const body = await audioRequest(c.req.raw, service.limits().maxSizeBytes);
26
28
  const result = await service.transcribe({
29
+ preferredProvider: preferences?.preferredProvider,
30
+ fallbackEnabled: preferences?.fallbackEnabled,
27
31
  workspaceId,
28
32
  accountId: grant.accountId,
29
33
  subjectId: grant.subjectId,
@@ -0,0 +1,378 @@
1
+ import type { Context, Hono } from "hono";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import {
4
+ ClearWorkspaceAllowanceRequest,
5
+ GetMyUsageRequest,
6
+ GetUsageRequest,
7
+ GrantWorkspaceCreditsRequest,
8
+ SetMemberAllowanceRequest,
9
+ SetWorkspaceAllowanceRequest,
10
+ WorkspaceAllowance,
11
+ WorkspaceAllowanceState,
12
+ WorkspaceCreditGrant,
13
+ WorkspaceUsageResponse,
14
+ MemberAllowance,
15
+ ClearWorkspaceAllowanceResponse,
16
+ type AccessContext,
17
+ } from "@opengeni/contracts";
18
+ import { ExternalIdentityReference } from "@opengeni/contracts/external-identities";
19
+ import {
20
+ accountScopedApiKeyWorkspaceAuthority,
21
+ accessGrantAuthorizationFromContext,
22
+ requireAccountAdminAuthorizationStamp,
23
+ requireAccessContext,
24
+ requireAccessGrantAuthorization,
25
+ requireResolvedAccessGrantAuthorization,
26
+ type AccessGrantAuthorization,
27
+ type ApiRouteDeps,
28
+ } from "@opengeni/core";
29
+ import {
30
+ clearWorkspaceAllowance,
31
+ findWorkspaceByExternalIdentity,
32
+ getWorkspaceAllowance,
33
+ getWorkspaceAllowanceState,
34
+ getWorkspace,
35
+ getWorkspaceUsage,
36
+ grantWorkspaceCredits,
37
+ nestedPostgresSqlState,
38
+ setMemberAllowance,
39
+ setWorkspaceAllowance,
40
+ withAccountRls,
41
+ UsageAllowanceVersionConflictError,
42
+ } from "@opengeni/db";
43
+ import { parseRequestBody, parseRequestJson } from "../http/request-body";
44
+
45
+ type Authority = { authorization: AccessGrantAuthorization; context: AccessContext };
46
+ type Operation = "read" | "budget-read" | "workspace-write" | "member-write" | "own";
47
+
48
+ /** No workspace wildcard, service initiator, or actor label can confer budget authority. */
49
+ export function requireAllowanceAuthority(
50
+ { authorization, context }: Authority,
51
+ operation: Operation,
52
+ ): void {
53
+ const grant = requireResolvedAccessGrantAuthorization(
54
+ authorization,
55
+ authorization.grant.workspaceId,
56
+ );
57
+ if (grant.principalKind === "agent_attempt" || grant.metadata?.sessionId !== undefined) {
58
+ throw new HTTPException(403, { message: "Agents cannot access usage allowances" });
59
+ }
60
+ const organizationKey = accountScopedApiKeyWorkspaceAuthority(context);
61
+ const sameOrganizationKey =
62
+ organizationKey?.accountId === grant.accountId ? organizationKey : null;
63
+ const accountAdmin =
64
+ authorization.accountGrant?.permissions.includes("account:admin") === true &&
65
+ requireAccountAdminAuthorizationStamp(authorization).accountId === grant.accountId;
66
+ const workspaceAdmin = grant.permissions.includes("workspace:admin");
67
+ if (
68
+ sameOrganizationKey?.permissionMode === "explicit" &&
69
+ (operation === "workspace-write" || operation === "member-write")
70
+ ) {
71
+ if (authorization.accountGrant?.permissions.includes("usage_allowances:manage")) return;
72
+ throw new HTTPException(403, { message: "missing permission: usage_allowances:manage" });
73
+ }
74
+ if (operation === "own") {
75
+ if (grant.principalKind !== "human_session" || grant.serviceInitiator) {
76
+ throw new HTTPException(403, { message: "Own usage requires an authenticated member" });
77
+ }
78
+ return;
79
+ }
80
+ if (operation === "read" || operation === "budget-read") {
81
+ if (
82
+ workspaceAdmin ||
83
+ accountAdmin ||
84
+ sameOrganizationKey?.permissions.includes("workspace:read")
85
+ )
86
+ return;
87
+ } else if (operation === "workspace-write") {
88
+ // Existing full keys retain key-control budget authority. Setup keys use
89
+ // literal account allowance authority plus the canonical organization-key
90
+ // workspace stamp; asUser carries neither account scope nor key stamp.
91
+ if (sameOrganizationKey) {
92
+ if (
93
+ sameOrganizationKey.permissions.includes("workspace:admin") &&
94
+ (sameOrganizationKey.permissions.includes("api_keys:manage") ||
95
+ authorization.accountGrant?.permissions.includes("usage_allowances:manage"))
96
+ )
97
+ return;
98
+ } else if (
99
+ accountAdmin &&
100
+ grant.principalKind === "human_session" &&
101
+ !grant.serviceInitiator &&
102
+ !context.subjectId.startsWith("api_key:")
103
+ )
104
+ return;
105
+ } else if (operation === "member-write") {
106
+ if (
107
+ sameOrganizationKey?.permissions.includes("workspace:admin") ||
108
+ (workspaceAdmin &&
109
+ grant.principalKind === "human_session" &&
110
+ !grant.serviceInitiator &&
111
+ !context.subjectId.startsWith("api_key:"))
112
+ )
113
+ return;
114
+ }
115
+ throw new HTTPException(403, { message: `Missing usage allowance authority: ${operation}` });
116
+ }
117
+
118
+ async function resolveWorkspace(c: Context, deps: ApiRouteDeps): Promise<string> {
119
+ const workspaceId = c.req.param("workspaceId");
120
+ if (workspaceId) return workspaceId;
121
+ const context = await requireAccessContext(c, deps);
122
+ const organizationKey = accountScopedApiKeyWorkspaceAuthority(context);
123
+ // External tenant paths are host-service lookups, never caller-selected
124
+ // default-account guesses or provisioning.
125
+ const externalAccountId =
126
+ c.req.header("x-opengeni-external-actor") !== undefined ? context.defaultAccountId : null;
127
+ const accountId = organizationKey?.accountId ?? externalAccountId;
128
+ if (!accountId)
129
+ throw new HTTPException(403, { message: "Organization API key or external user required" });
130
+ const reference = parseRequestBody(ExternalIdentityReference, {
131
+ source: c.req.param("workspaceSource"),
132
+ externalId: c.req.param("workspaceExternalId"),
133
+ });
134
+ const workspace = await withAccountRls(deps.db, accountId, (tx) =>
135
+ findWorkspaceByExternalIdentity(tx, {
136
+ accountId,
137
+ externalSource: reference.source,
138
+ externalId: reference.externalId,
139
+ }),
140
+ );
141
+ if (!workspace) throw new HTTPException(404, { message: "Workspace not found" });
142
+ return workspace.id;
143
+ }
144
+
145
+ async function authorize(c: Context, deps: ApiRouteDeps, operation: Operation) {
146
+ const workspaceId = await resolveWorkspace(c, deps);
147
+ const context = await requireAccessContext(c, deps);
148
+ if (
149
+ (operation === "workspace-write" || operation === "budget-read") &&
150
+ !c.req.header("x-opengeni-external-actor") &&
151
+ !context.subjectId.startsWith("api_key:")
152
+ ) {
153
+ // Organization budget management does not require membership in the
154
+ // target shared workspace. Validate the existing authenticated account
155
+ // authority on its own anchor, never manufacture a target workspace grant.
156
+ for (const account of context.accountGrants) {
157
+ if (account.subjectId !== context.subjectId || !account.permissions.includes("account:admin"))
158
+ continue;
159
+ const anchor = context.workspaceGrants.find(
160
+ (grant) =>
161
+ grant.accountId === account.accountId &&
162
+ grant.subjectId === context.subjectId &&
163
+ grant.principalKind === "human_session" &&
164
+ !grant.serviceInitiator &&
165
+ grant.metadata?.sessionId === undefined,
166
+ );
167
+ if (!anchor) continue;
168
+ const stamp = requireAccountAdminAuthorizationStamp(
169
+ accessGrantAuthorizationFromContext(context, anchor),
170
+ );
171
+ const target = await withAccountRls(deps.db, stamp.accountId, (tx) =>
172
+ getWorkspace(tx, workspaceId),
173
+ );
174
+ if (!target || target.accountId !== stamp.accountId) continue;
175
+ if (target.kind !== "shared") {
176
+ throw new HTTPException(403, {
177
+ message: "Organization budgets require a shared workspace",
178
+ });
179
+ }
180
+ return {
181
+ accountId: stamp.accountId,
182
+ workspaceId,
183
+ actorSubjectId: stamp.actorSubjectId,
184
+ actorType: "human_session" as const,
185
+ };
186
+ }
187
+ // An administrator of another organization may still hold an ordinary
188
+ // target grant. A budget lookup miss grants nothing and must not mask it.
189
+ }
190
+ const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
191
+ requireAllowanceAuthority({ authorization, context }, operation);
192
+ return {
193
+ accountId: authorization.grant.accountId,
194
+ workspaceId,
195
+ actorSubjectId: authorization.grant.subjectId,
196
+ actorType: authorization.grant.principalKind ?? "subject",
197
+ };
198
+ }
199
+
200
+ function usageQuery(c: Context, own: boolean) {
201
+ const input = c.req.query();
202
+ const parsed = parseRequestBody(own ? GetMyUsageRequest : GetUsageRequest, {
203
+ ...input,
204
+ ...(input.limit !== undefined ? { limit: Number(input.limit) } : {}),
205
+ });
206
+ return {
207
+ ...(parsed.period === undefined ? {} : { period: parsed.period }),
208
+ ...("limit" in parsed && parsed.limit !== undefined ? { limit: parsed.limit as number } : {}),
209
+ ...("cursor" in parsed && parsed.cursor !== undefined
210
+ ? { cursor: parsed.cursor as string }
211
+ : {}),
212
+ };
213
+ }
214
+
215
+ function requireAllowanceProducersEnabled(deps: ApiRouteDeps): void {
216
+ if (!deps.settings.usageAllowancesEnabled) {
217
+ throw new HTTPException(409, {
218
+ message:
219
+ "Usage allowance changes are not enabled. Upgrade every API and worker before enabling usage allowances.",
220
+ });
221
+ }
222
+ }
223
+
224
+ /** Map lifecycle refusals without treating a failed CAS as an applied mutation. */
225
+ async function lifecycle<T>(operation: () => Promise<T>): Promise<T> {
226
+ try {
227
+ return await operation();
228
+ } catch (error) {
229
+ if (error instanceof UsageAllowanceVersionConflictError)
230
+ throw new HTTPException(409, { message: error.message });
231
+ const state = nestedPostgresSqlState(error);
232
+ if (state === "40001" || state === "23505")
233
+ throw new HTTPException(409, { message: "Allowance version or operation conflicts" });
234
+ if (state === "42501")
235
+ throw new HTTPException(403, { message: "Allowance operation is not authorized" });
236
+ if (state === "P0002" || state === "23503")
237
+ throw new HTTPException(404, { message: "Allowance or member not found" });
238
+ if (state === "22023" || state === "23514")
239
+ throw new HTTPException(400, { message: "Invalid allowance request" });
240
+ throw error;
241
+ }
242
+ }
243
+
244
+ export function registerUsageAllowanceRoutes(app: Hono, deps: ApiRouteDeps): void {
245
+ const bases = [
246
+ "/v1/workspaces/:workspaceId",
247
+ "/v1/workspaces/external/:workspaceSource/:workspaceExternalId",
248
+ ];
249
+ for (const base of bases) {
250
+ app.get(`${base}/allowance`, async (c) => {
251
+ const scope = await authorize(c, deps, "budget-read");
252
+ const result = await getWorkspaceAllowance(deps.db, scope);
253
+ return c.json(result === null ? null : WorkspaceAllowance.parse(result));
254
+ });
255
+ app.get(`${base}/allowance/state`, async (c) => {
256
+ const scope = await authorize(c, deps, "budget-read");
257
+ return c.json(
258
+ WorkspaceAllowanceState.parse(await getWorkspaceAllowanceState(deps.db, scope)),
259
+ );
260
+ });
261
+ app.put(`${base}/allowance`, async (c) => {
262
+ const scope = await authorize(c, deps, "workspace-write");
263
+ const request = await parseRequestJson(c, SetWorkspaceAllowanceRequest);
264
+ requireAllowanceProducersEnabled(deps);
265
+ return c.json(
266
+ WorkspaceAllowance.parse(
267
+ await lifecycle(() =>
268
+ setWorkspaceAllowance(deps.db, {
269
+ ...scope,
270
+ expectedVersion: request.expectedVersion,
271
+ includedCredits: request.includedCredits,
272
+ period: request.period,
273
+ ...(request.anchorDay === undefined ? {} : { anchorDay: request.anchorDay }),
274
+ ...(request.memberDefault === undefined
275
+ ? {}
276
+ : { memberDefault: request.memberDefault }),
277
+ ...(request.thresholds === undefined
278
+ ? {}
279
+ : {
280
+ thresholds: {
281
+ ...(request.thresholds.workspace === undefined
282
+ ? {}
283
+ : { workspace: request.thresholds.workspace }),
284
+ ...(request.thresholds.member === undefined
285
+ ? {}
286
+ : { member: request.thresholds.member }),
287
+ },
288
+ }),
289
+ }),
290
+ ),
291
+ ),
292
+ );
293
+ });
294
+ app.delete(`${base}/allowance`, async (c) => {
295
+ const scope = await authorize(c, deps, "workspace-write");
296
+ const request = await parseRequestJson(c, ClearWorkspaceAllowanceRequest);
297
+ return c.json(
298
+ ClearWorkspaceAllowanceResponse.parse(
299
+ await lifecycle(() =>
300
+ clearWorkspaceAllowance(deps.db, {
301
+ ...scope,
302
+ expectedVersion: request.expectedVersion,
303
+ ...(request.operationId === undefined ? {} : { operationId: request.operationId }),
304
+ }),
305
+ ),
306
+ ),
307
+ );
308
+ });
309
+ app.post(`${base}/allowance/grants`, async (c) => {
310
+ const scope = await authorize(c, deps, "workspace-write");
311
+ const request = await parseRequestJson(c, GrantWorkspaceCreditsRequest);
312
+ requireAllowanceProducersEnabled(deps);
313
+ return c.json(
314
+ WorkspaceCreditGrant.parse(
315
+ await lifecycle(() =>
316
+ grantWorkspaceCredits(deps.db, {
317
+ ...scope,
318
+ operationId: request.operationId,
319
+ credits: request.credits,
320
+ ...(request.expiresAt === undefined ? {} : { expiresAt: request.expiresAt }),
321
+ }),
322
+ ),
323
+ ),
324
+ );
325
+ });
326
+ for (const memberPath of [
327
+ "members/:subjectId",
328
+ "members/external/:memberSource/:memberExternalId",
329
+ ]) {
330
+ app.put(`${base}/${memberPath}/allowance`, async (c) => {
331
+ const scope = await authorize(c, deps, "member-write");
332
+ const request = await parseRequestJson(c, SetMemberAllowanceRequest);
333
+ if (request.rule !== null) requireAllowanceProducersEnabled(deps);
334
+ const subjectId = c.req.param("subjectId");
335
+ const target = subjectId
336
+ ? { subjectId }
337
+ : {
338
+ externalIdentity: parseRequestBody(ExternalIdentityReference, {
339
+ source: c.req.param("memberSource"),
340
+ externalId: c.req.param("memberExternalId"),
341
+ }),
342
+ };
343
+ // Resolve the exact external identity and live member inside the same
344
+ // protected mutation; never enumerate a roster or provision an identity.
345
+ return c.json(
346
+ MemberAllowance.parse(
347
+ await lifecycle(() => setMemberAllowance(deps.db, { ...scope, ...target, ...request })),
348
+ ),
349
+ );
350
+ });
351
+ }
352
+ app.get(`${base}/usage`, async (c) => {
353
+ const scope = await authorize(c, deps, "read");
354
+ return c.json(
355
+ WorkspaceUsageResponse.parse(
356
+ await getWorkspaceUsage(deps.db, { ...scope, ...usageQuery(c, false) }),
357
+ ),
358
+ );
359
+ });
360
+ app.get(`${base}/usage/me`, async (c) => {
361
+ const scope = await authorize(c, deps, "own");
362
+ const result = WorkspaceUsageResponse.parse(
363
+ await getWorkspaceUsage(deps.db, {
364
+ ...scope,
365
+ ...usageQuery(c, true),
366
+ subjectId: scope.actorSubjectId,
367
+ }),
368
+ );
369
+ // Defense in depth: the own endpoint never serializes another member or a
370
+ // roster cursor even if the storage adapter returns an overbroad page.
371
+ return c.json({
372
+ ...result,
373
+ members: result.members.filter((member) => member.subjectId === scope.actorSubjectId),
374
+ nextCursor: null,
375
+ });
376
+ });
377
+ }
378
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ IssueUserResourceGrantRequest,
3
+ ListUserResourceAuthoritiesQuery,
4
+ ListUserResourceAuthoritiesResponse,
5
+ RevokeUserResourceGrantQuery,
6
+ UserResourceGrantMutationResponse,
7
+ UserResourceGrantRevocationResponse,
8
+ } from "@opengeni/contracts";
9
+ import {
10
+ issueManagedHumanUserResourceGrant,
11
+ listManagedHumanUserResourceAuthorities,
12
+ requireAccessGrantAuthorization,
13
+ revokeManagedHumanUserResourceGrant,
14
+ SessionAuthorizationDeniedError,
15
+ SessionAuthorizationUnavailableError,
16
+ SessionTenancyManagedHumanRequiredError,
17
+ type ApiRouteDeps,
18
+ } from "@opengeni/core";
19
+ import { nestedPostgresSqlState, SessionTenancyNotActivatedError } from "@opengeni/db";
20
+ import type { Context, Hono } from "hono";
21
+ import { HTTPException } from "hono/http-exception";
22
+ import { z } from "zod";
23
+
24
+ async function body<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
25
+ const parsed = schema.safeParse(await context.req.json().catch(() => null));
26
+ if (!parsed.success) throw new HTTPException(422, { message: "invalid user-resource request" });
27
+ return parsed.data;
28
+ }
29
+
30
+ function lifecycleError(error: unknown): never {
31
+ if (
32
+ error instanceof SessionAuthorizationDeniedError ||
33
+ error instanceof SessionTenancyManagedHumanRequiredError ||
34
+ error instanceof SessionTenancyNotActivatedError
35
+ ) {
36
+ throw new HTTPException(403, { message: "user-resource authority denied" });
37
+ }
38
+ if (error instanceof SessionAuthorizationUnavailableError) {
39
+ throw new HTTPException(503, { message: "session authorization unavailable" });
40
+ }
41
+ if (nestedPostgresSqlState(error) === "42501") {
42
+ throw new HTTPException(403, { message: "user-resource authority denied" });
43
+ }
44
+ if (nestedPostgresSqlState(error) === "22023") {
45
+ throw new HTTPException(422, { message: "invalid user-resource request" });
46
+ }
47
+ throw error;
48
+ }
49
+
50
+ export function registerUserResourceAuthorityRoutes(app: Hono, deps: ApiRouteDeps): void {
51
+ const base = "/v1/workspaces/:workspaceId/user-resource-authorities";
52
+ app.get(base, async (context) => {
53
+ const query = ListUserResourceAuthoritiesQuery.safeParse({
54
+ scope: context.req.query("scope"),
55
+ resourceKind: context.req.query("resourceKind"),
56
+ cursor: context.req.query("cursor"),
57
+ limit: context.req.query("limit"),
58
+ });
59
+ if (!query.success) throw new HTTPException(422, { message: "explicit scope=user required" });
60
+ const workspaceId = context.req.param("workspaceId");
61
+ const access = await requireAccessGrantAuthorization(
62
+ context,
63
+ deps,
64
+ workspaceId,
65
+ "workspace:read",
66
+ );
67
+ try {
68
+ const page = await listManagedHumanUserResourceAuthorities(
69
+ deps,
70
+ access,
71
+ workspaceId,
72
+ query.data,
73
+ );
74
+ return context.json(
75
+ ListUserResourceAuthoritiesResponse.parse({
76
+ scope: "user",
77
+ ...page,
78
+ }),
79
+ );
80
+ } catch (error) {
81
+ return lifecycleError(error);
82
+ }
83
+ });
84
+
85
+ app.post(`${base}/:authorityId/grants`, async (context) => {
86
+ const workspaceId = context.req.param("workspaceId");
87
+ const authorityId = z.string().uuid().safeParse(context.req.param("authorityId"));
88
+ if (!authorityId.success) throw new HTTPException(422, { message: "invalid authority id" });
89
+ const request = await body(context, IssueUserResourceGrantRequest);
90
+ const access = await requireAccessGrantAuthorization(
91
+ context,
92
+ deps,
93
+ workspaceId,
94
+ "workspace:read",
95
+ );
96
+ try {
97
+ return context.json(
98
+ UserResourceGrantMutationResponse.parse({
99
+ scope: "user",
100
+ grant: await issueManagedHumanUserResourceGrant(
101
+ deps,
102
+ access,
103
+ workspaceId,
104
+ authorityId.data,
105
+ request,
106
+ "http",
107
+ ),
108
+ }),
109
+ );
110
+ } catch (error) {
111
+ return lifecycleError(error);
112
+ }
113
+ });
114
+
115
+ app.delete(`${base}/grants/:grantId`, async (context) => {
116
+ const query = RevokeUserResourceGrantQuery.safeParse({ scope: context.req.query("scope") });
117
+ if (!query.success) throw new HTTPException(422, { message: "explicit scope=user required" });
118
+ const grantId = z.string().uuid().safeParse(context.req.param("grantId"));
119
+ if (!grantId.success) throw new HTTPException(422, { message: "invalid grant id" });
120
+ const workspaceId = context.req.param("workspaceId");
121
+ const access = await requireAccessGrantAuthorization(
122
+ context,
123
+ deps,
124
+ workspaceId,
125
+ "workspace:read",
126
+ );
127
+ try {
128
+ return context.json(
129
+ UserResourceGrantRevocationResponse.parse({
130
+ scope: "user",
131
+ grant: await revokeManagedHumanUserResourceGrant(deps, access, workspaceId, grantId.data),
132
+ }),
133
+ );
134
+ } catch (error) {
135
+ return lifecycleError(error);
136
+ }
137
+ });
138
+ }