@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
@@ -11,6 +11,9 @@ import {
11
11
  editableArtifactScope,
12
12
  hasPermission,
13
13
  requireAccessGrant,
14
+ requireAccessGrantAuthorization,
15
+ externalActorContinuationForAuthorization,
16
+ requirePermission,
14
17
  requireSessionAuthorization,
15
18
  SessionAuthorizationDeniedError,
16
19
  SessionAuthorizationUnavailableError,
@@ -25,11 +28,13 @@ import {
25
28
  type EditableArtifactPinnedVersion,
26
29
  type EditableArtifactModality,
27
30
  } from "@opengeni/core";
28
- import { listEditableArtifactIdsForSession } from "@opengeni/db";
31
+ import { hasEditableArtifactSessionLink, listEditableArtifactIdsForSession } from "@opengeni/db";
29
32
  import type { Context, Hono } from "hono";
30
33
  import { z } from "zod";
31
34
 
32
35
  import { ApiHttpError } from "../http/api-error";
36
+ import { USER_CONTENT_SECURITY_HEADERS } from "../http/user-content";
37
+ import { withAccessGrantSessionRlsContext } from "../access-grant-rls";
33
38
 
34
39
  export const EDITABLE_ARTIFACT_HTTP_REQUEST_MAX_BYTES = 4 * 1024;
35
40
  export const EDITABLE_ARTIFACT_EXPORT_REQUEST_MAX_BYTES = 260 * 1024;
@@ -77,10 +82,15 @@ const ListEditableArtifactsQuery = z
77
82
 
78
83
  const MintEditableArtifactLiveTicketRequest = z
79
84
  .object({
85
+ sourceSessionId: SessionId.optional(),
80
86
  replicaId: ReplicaId,
81
- protocolVersion: PositiveProtocolVersion,
87
+ modality: z.enum(["document", "spreadsheet", "presentation"]),
88
+ liveProtocolVersion: PositiveProtocolVersion,
82
89
  kernelVersion: VersionName,
83
90
  modelSchemaVersion: PositiveModelSchemaVersion,
91
+ snapshotVersion: PositiveProtocolVersion,
92
+ commandProtocolVersion: PositiveProtocolVersion,
93
+ committedTransactionProtocolVersion: PositiveProtocolVersion,
84
94
  })
85
95
  .strict();
86
96
 
@@ -467,7 +477,13 @@ export function registerEditableArtifactRoutes(
467
477
 
468
478
  // Workspace authentication precedes path/body parsing. Exact artifact
469
479
  // permission is intentionally enforced inside the injected application.
470
- const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
480
+ const authorization = await requireAccessGrantAuthorization(
481
+ context,
482
+ deps,
483
+ workspaceId,
484
+ "artifacts:read",
485
+ );
486
+ const grant = authorization.grant;
471
487
  const artifactId = editableArtifactId(parseArtifactId(context.req.param("artifactId")));
472
488
  const body = await parseBoundedTicketRequest(context);
473
489
  const actor = editableArtifactActorForGrant(grant, body.replicaId);
@@ -475,6 +491,18 @@ export function registerEditableArtifactRoutes(
475
491
  accountId: grant.accountId,
476
492
  workspaceId,
477
493
  });
494
+ if (body.sourceSessionId) {
495
+ requirePermission(grant, "sessions:read");
496
+ await withAccessGrantSessionRlsContext(deps, grant, async () => {
497
+ await authorizeSourceSession(deps, grant, body.sourceSessionId!);
498
+ if (
499
+ !(await hasEditableArtifactSessionLink(deps.db, scope, body.sourceSessionId!, artifactId))
500
+ ) {
501
+ throw applicationHttpError("not_found");
502
+ }
503
+ });
504
+ }
505
+ const externalContinuation = externalActorContinuationForAuthorization(authorization);
478
506
 
479
507
  let rawTicket: MintEditableArtifactLiveTicketOutput;
480
508
  try {
@@ -482,10 +510,52 @@ export function registerEditableArtifactRoutes(
482
510
  scope,
483
511
  actor,
484
512
  artifactId,
485
- protocolVersion: body.protocolVersion,
513
+ modality: body.modality,
514
+ liveProtocolVersion: body.liveProtocolVersion,
486
515
  kernelVersion: body.kernelVersion,
487
516
  modelSchemaVersion: body.modelSchemaVersion,
517
+ snapshotVersion: body.snapshotVersion,
518
+ commandProtocolVersion: body.commandProtocolVersion,
519
+ committedTransactionProtocolVersion: body.committedTransactionProtocolVersion,
488
520
  allowEdit: hasPermission(grant.permissions, "artifacts:publish"),
521
+ ...(body.sourceSessionId
522
+ ? {
523
+ sourceSessionAuthority: {
524
+ sessionId: body.sourceSessionId,
525
+ grant: {
526
+ accountId: grant.accountId,
527
+ workspaceId: grant.workspaceId,
528
+ subjectId: grant.subjectId,
529
+ ...(grant.principalKind ? { principalKind: grant.principalKind } : {}),
530
+ ...(grant.metadata
531
+ ? {
532
+ metadata: Object.fromEntries(
533
+ Object.entries(grant.metadata).filter(([key]) =>
534
+ [
535
+ "externalActor",
536
+ "delegated",
537
+ "sessionId",
538
+ "turnId",
539
+ "attemptId",
540
+ "executionGeneration",
541
+ ].includes(key),
542
+ ),
543
+ ),
544
+ }
545
+ : {}),
546
+ // Bind only editor capabilities, not a broad delegated wildcard.
547
+ permissions: [
548
+ "sessions:read",
549
+ "artifacts:read",
550
+ ...(hasPermission(grant.permissions, "artifacts:publish")
551
+ ? ["artifacts:publish" as const]
552
+ : []),
553
+ ],
554
+ },
555
+ ...(externalContinuation ? { externalContinuation } : {}),
556
+ },
557
+ }
558
+ : {}),
489
559
  });
490
560
  } catch (error) {
491
561
  throw editableArtifactHttpError(error);
@@ -496,7 +566,7 @@ export function registerEditableArtifactRoutes(
496
566
  !parsedTicket.success ||
497
567
  parsedTicket.data.artifactId !== artifactId ||
498
568
  parsedTicket.data.replicaId !== actor.replicaId ||
499
- parsedTicket.data.protocolVersion !== body.protocolVersion ||
569
+ parsedTicket.data.protocolVersion !== body.liveProtocolVersion ||
500
570
  Date.parse(parsedTicket.data.expiresAt) <= Date.now()
501
571
  ) {
502
572
  throw new Error("Editable artifact application returned an invalid live ticket");
@@ -608,7 +678,7 @@ export function registerEditableArtifactRoutes(
608
678
  "content-length": String(download.byteSize),
609
679
  "content-type": download.mimeType,
610
680
  etag: `"${download.contentHash}"`,
611
- "x-content-type-options": "nosniff",
681
+ ...USER_CONTENT_SECURITY_HEADERS,
612
682
  },
613
683
  });
614
684
  });
@@ -901,7 +971,7 @@ function normalizeArtifactIdList(value: readonly string[]): readonly string[] {
901
971
  return ids;
902
972
  }
903
973
 
904
- function isInvisibleListCandidate(error: unknown): boolean {
974
+ export function isInvisibleListCandidate(error: unknown): boolean {
905
975
  return (
906
976
  (error instanceof EditableArtifactDomainError &&
907
977
  (error.code === "forbidden" || error.code === "not_found")) ||
@@ -1042,7 +1112,7 @@ async function readBoundedJson(request: Request, maxBytes: number): Promise<unkn
1042
1112
  }
1043
1113
  }
1044
1114
 
1045
- function editableArtifactHttpError(error: unknown): unknown {
1115
+ export function editableArtifactHttpError(error: unknown): unknown {
1046
1116
  if (error instanceof EditableArtifactDurableExportError) {
1047
1117
  switch (error.code) {
1048
1118
  case "invalid_request":
@@ -1059,7 +1129,7 @@ function editableArtifactHttpError(error: unknown): unknown {
1059
1129
  case "unsupported_format":
1060
1130
  return new ApiHttpError(422, {
1061
1131
  code: "validation_failed",
1062
- message: "Editable artifact format is not available for this artifact.",
1132
+ message: error.message,
1063
1133
  });
1064
1134
  case "not_ready":
1065
1135
  return new ApiHttpError(409, {
@@ -37,6 +37,7 @@ import {
37
37
  MintEnrollTokenRequest,
38
38
  MintEnrollTokenResponse,
39
39
  RemoveEnrollmentRequest,
40
+ RenewEnrollmentRequest,
40
41
  RevokeEnrollmentResponse,
41
42
  type EnrollmentArch,
42
43
  type EnrollmentOs,
@@ -50,7 +51,12 @@ import {
50
51
  } from "@opengeni/db";
51
52
  import type { Context, Hono } from "hono";
52
53
  import { HTTPException } from "hono/http-exception";
53
- import { requireAccessGrant } from "@opengeni/core";
54
+ import { trustedRequestSourceRateLimitKey } from "../http/request-source";
55
+ import {
56
+ requireAccessGrant,
57
+ requireAccessGrantAuthorization,
58
+ requirePermission,
59
+ } from "@opengeni/core";
54
60
  import type { ApiRouteDeps } from "@opengeni/core";
55
61
  import {
56
62
  approveDeviceEnrollment,
@@ -60,6 +66,7 @@ import {
60
66
  mintEnrollToken,
61
67
  pollDeviceEnrollment,
62
68
  startDeviceEnrollment,
69
+ renewEnrollmentCredentials,
63
70
  toLookupResponse,
64
71
  } from "../sandbox/enrollment";
65
72
 
@@ -88,15 +95,33 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
88
95
  const lookupLimiter = new TokenBucket({ capacity: 30, refillPerSecond: 1 });
89
96
  // The headless token exchange (UNAUTHENTICATED — the token is the auth). Bounded
90
97
  // against an enroll-token brute force; the `oget_` HMAC is the real boundary.
91
- const exchangeLimiter = new TokenBucket({ capacity: 20, refillPerSecond: 0.5 });
98
+ const exchangeLimiter = new TokenBucket({
99
+ capacity: 20,
100
+ refillPerSecond: 0.5,
101
+ });
92
102
 
93
103
  function rateLimit(c: Context, limiter: TokenBucket): void {
94
- const ip = clientIp(c);
104
+ const ip = trustedRequestSourceRateLimitKey(c, settings);
95
105
  if (!limiter.take(ip)) {
96
106
  throw new HTTPException(429, { message: "too many requests; slow down" });
97
107
  }
98
108
  }
99
109
 
110
+ // Machine authentication: signed install-key proof + current enrollment grant.
111
+ app.post("/v1/enrollments/renew", async (c) => {
112
+ assertSelfhostedEnabled();
113
+ rateLimit(c, exchangeLimiter);
114
+ const parsed = RenewEnrollmentRequest.safeParse(await c.req.json().catch(() => null));
115
+ if (!parsed.success) throw new HTTPException(400, { message: "invalid renewal request" });
116
+ const credentials = await renewEnrollmentCredentials({ db, settings }, parsed.data);
117
+ if (!credentials)
118
+ throw new HTTPException(401, {
119
+ message: "machine renewal not authorized",
120
+ });
121
+ c.header("Cache-Control", "no-store");
122
+ return c.json(EnrollTokenExchangeResponse.parse({ credentials }), 200);
123
+ });
124
+
100
125
  // ── POST /enrollments/device/start (agent-side, user-unauthenticated) ───────
101
126
  app.post("/v1/enrollments/device/start", async (c) => {
102
127
  assertSelfhostedEnabled();
@@ -123,8 +148,10 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
123
148
  machineName: body.machineName ?? null,
124
149
  canOfferDisplay: body.canOfferDisplay,
125
150
  requestsScreenControl: body.requestsScreenControl,
126
- // The approve page is served at the SAME origin as this request.
127
- verificationOrigin: new URL(c.req.url).origin,
151
+ // Prefer the externally configured deployment origin. Behind a TLS-
152
+ // terminating proxy c.req.url can carry the internal http scheme, which
153
+ // must never leak into the device-flow command shown to the operator.
154
+ verificationOrigin: settings.publicBaseUrl ?? new URL(c.req.url).origin,
128
155
  },
129
156
  );
130
157
  return c.json(result, 201);
@@ -156,7 +183,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
156
183
  rateLimit(c, lookupLimiter);
157
184
  const parsed = DeviceEnrollmentLookupRequest.safeParse(await c.req.json().catch(() => null));
158
185
  if (!parsed.success) {
159
- throw new HTTPException(400, { message: "invalid device-lookup request" });
186
+ throw new HTTPException(400, {
187
+ message: "invalid device-lookup request",
188
+ });
160
189
  }
161
190
  const record = await lookupDeviceEnrollment(
162
191
  { db, settings },
@@ -165,7 +194,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
165
194
  if (!record) {
166
195
  // Unknown / terminal / expired code → 404 (indistinguishable from an
167
196
  // unauthorized one below, by design).
168
- throw new HTTPException(404, { message: "no pending enrollment for that code" });
197
+ throw new HTTPException(404, {
198
+ message: "no pending enrollment for that code",
199
+ });
169
200
  }
170
201
  // Authorize the caller against the RESOLVED workspace. A missing grant throws
171
202
  // 403/404 from requireAccessGrant; we normalize that to 404 so a caller cannot
@@ -173,7 +204,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
173
204
  try {
174
205
  await requireAccessGrant(c, deps, record.workspaceId, "enrollments:read");
175
206
  } catch {
176
- throw new HTTPException(404, { message: "no pending enrollment for that code" });
207
+ throw new HTTPException(404, {
208
+ message: "no pending enrollment for that code",
209
+ });
177
210
  }
178
211
  return c.json(DeviceEnrollmentLookupResponse.parse(toLookupResponse(record)), 200);
179
212
  });
@@ -189,7 +222,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
189
222
  rateLimit(c, exchangeLimiter);
190
223
  const parsed = EnrollTokenExchangeRequest.safeParse(await c.req.json().catch(() => null));
191
224
  if (!parsed.success) {
192
- throw new HTTPException(400, { message: "invalid enroll-token-exchange request" });
225
+ throw new HTTPException(400, {
226
+ message: "invalid enroll-token-exchange request",
227
+ });
193
228
  }
194
229
  const body = parsed.data;
195
230
  const result = await exchangeEnrollToken(
@@ -206,10 +241,14 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
206
241
  if (!result.ok) {
207
242
  if (result.reason === "disabled") {
208
243
  // The credential plane is off for this deployment (no signing secret).
209
- throw new HTTPException(503, { message: "enrollment credential plane is not configured" });
244
+ throw new HTTPException(503, {
245
+ message: "enrollment credential plane is not configured",
246
+ });
210
247
  }
211
248
  // An invalid / expired / wrong-typ token — the token is the auth, so 401.
212
- throw new HTTPException(401, { message: "invalid or expired enroll token" });
249
+ throw new HTTPException(401, {
250
+ message: "invalid or expired enroll token",
251
+ });
213
252
  }
214
253
  return c.json(EnrollTokenExchangeResponse.parse({ credentials: result.credentials }), 201);
215
254
  });
@@ -218,18 +257,48 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
218
257
  // The LOUD CONSENT step. requireAccessGrant BEFORE the Zod parse.
219
258
  app.post("/v1/workspaces/:workspaceId/enrollments/device/approve", async (c) => {
220
259
  const workspaceId = c.req.param("workspaceId");
221
- const grant = await requireAccessGrant(c, deps, workspaceId, "enrollments:manage");
260
+ const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
261
+ const grant = authorization.grant;
262
+ requirePermission(grant, "enrollments:manage");
222
263
  assertSelfhostedEnabled();
223
- const parsed = DeviceEnrollmentApproveRequest.safeParse(await c.req.json().catch(() => null));
264
+ const rawBody = await c.req.json().catch(() => null);
265
+ const parsed = DeviceEnrollmentApproveRequest.safeParse(rawBody);
224
266
  if (!parsed.success) {
225
- throw new HTTPException(400, { message: "invalid device-approve request" });
267
+ throw new HTTPException(400, {
268
+ message: "invalid device-approve request",
269
+ });
226
270
  }
227
271
  const body = parsed.data;
272
+ const scopeWasExplicit =
273
+ typeof rawBody === "object" &&
274
+ rawBody !== null &&
275
+ Object.prototype.hasOwnProperty.call(rawBody, "scope");
276
+ // Managed browser humans get the personal default. Legacy/delegated
277
+ // principals predate organization-user ownership and retain the compatible
278
+ // workspace default when the caller omitted scope; an explicit scope is
279
+ // never silently rewritten.
280
+ const effectiveScope =
281
+ scopeWasExplicit ||
282
+ (grant.principalKind === "human_session" &&
283
+ authorization.contextIntegrity &&
284
+ grant.metadata?.delegated !== true)
285
+ ? body.scope
286
+ : undefined;
287
+ const allowOrganization =
288
+ effectiveScope === "organization" &&
289
+ authorization.accountGrant?.permissions.includes("account:admin") === true;
290
+ if (effectiveScope === "organization" && !allowOrganization) {
291
+ throw new HTTPException(403, {
292
+ message: "missing permission: account:admin",
293
+ });
294
+ }
228
295
  const approved = await approveDeviceEnrollment(
229
296
  { db, settings },
230
297
  {
231
298
  accountId: grant.accountId,
232
299
  workspaceId,
300
+ ...(effectiveScope ? { scope: effectiveScope } : {}),
301
+ allowOrganization,
233
302
  userCode: body.userCode,
234
303
  allowScreenControl: body.allowScreenControl,
235
304
  // The LOUD consent record: WHO consented (the authenticated subject + label).
@@ -239,7 +308,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
239
308
  );
240
309
  if (!approved) {
241
310
  // An unknown / expired / already-terminal user_code in this workspace.
242
- throw new HTTPException(404, { message: "no pending enrollment for that code" });
311
+ throw new HTTPException(404, {
312
+ message: "no pending enrollment for that code",
313
+ });
243
314
  }
244
315
  return c.json(
245
316
  DeviceEnrollmentApproveResponse.parse({
@@ -286,7 +357,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
286
357
  // allowScreenControl=false). Coalesce a missing/empty body to {} before parse.
287
358
  const parsed = MintEnrollTokenRequest.safeParse(await c.req.json().catch(() => ({})));
288
359
  if (!parsed.success) {
289
- throw new HTTPException(400, { message: "invalid mint-enroll-token request" });
360
+ throw new HTTPException(400, {
361
+ message: "invalid mint-enroll-token request",
362
+ });
290
363
  }
291
364
  const minted = await mintEnrollToken(
292
365
  { db, settings },
@@ -298,7 +371,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
298
371
  );
299
372
  if (!minted) {
300
373
  // The credential plane is off (no signing secret) — mirror poll's disabled path.
301
- throw new HTTPException(503, { message: "enrollment credential plane is not configured" });
374
+ throw new HTTPException(503, {
375
+ message: "enrollment credential plane is not configured",
376
+ });
302
377
  }
303
378
  return c.json(MintEnrollTokenResponse.parse(minted), 201);
304
379
  });
@@ -306,13 +381,15 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
306
381
  // ── GET /workspaces/:workspaceId/enrollments (user-authed) ──────────────────
307
382
  app.get("/v1/workspaces/:workspaceId/enrollments", async (c) => {
308
383
  const workspaceId = c.req.param("workspaceId");
309
- await requireAccessGrant(c, deps, workspaceId, "enrollments:read");
384
+ const grant = await requireAccessGrant(c, deps, workspaceId, "enrollments:read");
310
385
  assertSelfhostedEnabled();
311
386
  const statusFilter = c.req.query("status");
312
387
  if (statusFilter !== undefined && statusFilter !== "active" && statusFilter !== "revoked") {
313
- throw new HTTPException(400, { message: "status must be active or revoked" });
388
+ throw new HTTPException(400, {
389
+ message: "status must be active or revoked",
390
+ });
314
391
  }
315
- const rows = await listEnrollments(db, workspaceId, {
392
+ const rows = await listEnrollments(db, grant, {
316
393
  status: statusFilter === "revoked" ? "revoked" : "active",
317
394
  });
318
395
  return c.json(
@@ -320,6 +397,8 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
320
397
  enrollments: rows.map((row) =>
321
398
  EnrollmentSummary.parse({
322
399
  id: row.id,
400
+ scope: row.scope,
401
+ generation: row.generation,
323
402
  pubkey: row.pubkey,
324
403
  exposure: row.exposure,
325
404
  hasDisplay: row.hasDisplay,
@@ -340,19 +419,40 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
340
419
  // ── POST /workspaces/:workspaceId/enrollments/:id/revoke (user-authed) ──────
341
420
  app.post("/v1/workspaces/:workspaceId/enrollments/:enrollmentId/revoke", async (c) => {
342
421
  const workspaceId = c.req.param("workspaceId");
343
- const grant = await requireAccessGrant(c, deps, workspaceId, "enrollments:manage");
422
+ const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
423
+ const grant = authorization.grant;
424
+ requirePermission(grant, "enrollments:manage");
344
425
  assertSelfhostedEnabled();
345
426
  const parsed = RemoveEnrollmentRequest.safeParse(await c.req.json().catch(() => ({})));
346
427
  if (!parsed.success) {
347
- throw new HTTPException(400, { message: "invalid machine removal request" });
428
+ throw new HTTPException(400, {
429
+ message: "invalid machine removal request",
430
+ });
348
431
  }
349
432
  const idempotencyKey =
350
433
  parsed.data.idempotencyKey?.trim() || c.req.header("idempotency-key")?.trim() || randomUUID();
351
434
  try {
435
+ const enrollmentId = c.req.param("enrollmentId");
436
+ const enrollment = (await listEnrollments(db, grant, { status: "active" })).find(
437
+ (candidate) => candidate.id === enrollmentId,
438
+ );
439
+ if (!enrollment) {
440
+ throw new HTTPException(404, {
441
+ message: "machine enrollment not found",
442
+ });
443
+ }
444
+ if (
445
+ enrollment.scope === "organization" &&
446
+ authorization.accountGrant?.permissions.includes("account:admin") !== true
447
+ ) {
448
+ throw new HTTPException(403, {
449
+ message: "missing permission: account:admin",
450
+ });
451
+ }
352
452
  const result = await removeEnrollment(db, {
353
453
  accountId: grant.accountId,
354
- workspaceId,
355
- enrollmentId: c.req.param("enrollmentId"),
454
+ workspaceId: enrollment.workspaceId,
455
+ enrollmentId,
356
456
  operationKey: idempotencyKey,
357
457
  ...(parsed.data.expectedUpdatedAt
358
458
  ? { expectedUpdatedAt: parsed.data.expectedUpdatedAt }
@@ -360,7 +460,9 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
360
460
  subjectId: grant.subjectId,
361
461
  });
362
462
  if (!result) {
363
- throw new HTTPException(404, { message: "machine enrollment not found" });
463
+ throw new HTTPException(404, {
464
+ message: "machine enrollment not found",
465
+ });
364
466
  }
365
467
  return c.json(
366
468
  RevokeEnrollmentResponse.parse({
@@ -376,7 +478,10 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
376
478
  if (error instanceof MachineRemovalRevisionConflictError) {
377
479
  throw new HTTPException(409, {
378
480
  message: error.message,
379
- cause: { code: "stale_revision", currentUpdatedAt: error.currentUpdatedAt },
481
+ cause: {
482
+ code: "stale_revision",
483
+ currentUpdatedAt: error.currentUpdatedAt,
484
+ },
380
485
  });
381
486
  }
382
487
  throw error;
@@ -384,18 +489,6 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
384
489
  });
385
490
  }
386
491
 
387
- // The remote client IP for the per-IP rate-limit bucket. Honors the proxy's
388
- // X-Forwarded-For (the first hop) when present, falling back to a constant key when
389
- // neither is available (the bucket then caps the whole edge — still a useful cap).
390
- function clientIp(c: Context): string {
391
- const xff = c.req.header("x-forwarded-for");
392
- if (xff) {
393
- const first = xff.split(",")[0]?.trim();
394
- if (first) return first;
395
- }
396
- return c.req.header("x-real-ip")?.trim() || "unknown";
397
- }
398
-
399
492
  // A minimal per-key token bucket. capacity = burst; refillPerSecond = sustained
400
493
  // rate. Buckets are created lazily and reset their tokens by elapsed time on each
401
494
  // take, so an idle key fully refills without a background timer.
@@ -410,7 +503,10 @@ class TokenBucket {
410
503
  }
411
504
 
412
505
  take(key: string, now = Date.now()): boolean {
413
- const bucket = this.buckets.get(key) ?? { tokens: this.capacity, updatedAt: now };
506
+ const bucket = this.buckets.get(key) ?? {
507
+ tokens: this.capacity,
508
+ updatedAt: now,
509
+ };
414
510
  const elapsedSeconds = Math.max(0, (now - bucket.updatedAt) / 1000);
415
511
  bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsedSeconds * this.refillPerSecond);
416
512
  bucket.updatedAt = now;