@opengeni/api-router 0.30.1 → 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 (436) 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-office-import.d.ts +7 -4
  35. package/dist/editable-artifact-source-session.d.ts +3 -0
  36. package/dist/editable-artifact-websocket.d.ts +10 -18
  37. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  38. package/dist/fatal-process-boundary.d.ts +25 -0
  39. package/dist/github-access.d.ts +31 -2
  40. package/dist/http/acting-person.d.ts +24 -0
  41. package/dist/http/api-contract-compat.d.ts +19 -0
  42. package/dist/http/api-error.d.ts +27 -1
  43. package/dist/http/cors.d.ts +17 -0
  44. package/dist/http/deprecation.d.ts +42 -0
  45. package/dist/http/interaction-control-error.d.ts +13 -0
  46. package/dist/http/keyed-admission.d.ts +12 -0
  47. package/dist/http/local-browser-boundary.d.ts +72 -0
  48. package/dist/http/metrics-listener.d.ts +22 -0
  49. package/dist/http/path-identifier.d.ts +17 -0
  50. package/dist/http/registered-route-label.d.ts +16 -0
  51. package/dist/http/request-body.d.ts +27 -0
  52. package/dist/http/request-source.d.ts +52 -0
  53. package/dist/http/sse.d.ts +17 -6
  54. package/dist/http/unmatched-route.d.ts +18 -0
  55. package/dist/http/user-content.d.ts +36 -0
  56. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  57. package/dist/index.d.ts +10 -3
  58. package/dist/index.js +1129 -166
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration-endpoint-test.d.ts +20 -0
  61. package/dist/integrations/api-integrations.d.ts +2 -2
  62. package/dist/integrations/atlassian.d.ts +29 -3
  63. package/dist/integrations/connect-authority.d.ts +1 -0
  64. package/dist/integrations/connect-callback-return.d.ts +6 -0
  65. package/dist/integrations/fiken.d.ts +16 -2
  66. package/dist/integrations/github-app-connect.d.ts +47 -0
  67. package/dist/integrations/github-installation-proof.d.ts +3 -0
  68. package/dist/integrations/github-lens-connect.d.ts +116 -0
  69. package/dist/integrations/github-skill-source.d.ts +1 -5
  70. package/dist/integrations/google-drive.d.ts +25 -0
  71. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  72. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  73. package/dist/integrations/oauth-client.d.ts +178 -18
  74. package/dist/integrations/oauth-profiles.d.ts +222 -0
  75. package/dist/integrations/oauth-return-path.d.ts +1 -0
  76. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  77. package/dist/integrations/personal-github.d.ts +42 -0
  78. package/dist/integrations/pr-review-provider.d.ts +22 -0
  79. package/dist/integrations/provider-oauth.d.ts +19 -1
  80. package/dist/integrations/slack-app-home.d.ts +24 -0
  81. package/dist/integrations/slack-bot.d.ts +367 -12
  82. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  83. package/dist/integrations/slack-install.d.ts +20 -0
  84. package/dist/integrations/slack-interactions.d.ts +113 -10
  85. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  86. package/dist/integrations/slack-routing.d.ts +120 -0
  87. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  88. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  89. package/dist/integrations/social-oauth.d.ts +7 -0
  90. package/dist/interaction-agent-access.d.ts +15 -0
  91. package/dist/interaction-frame-proxy.d.ts +68 -0
  92. package/dist/interaction-metrics.d.ts +2 -0
  93. package/dist/mcp/action-catalog-types.d.ts +9 -0
  94. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  95. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  96. package/dist/mcp/child-read-evidence.d.ts +9 -0
  97. package/dist/mcp/command-read.d.ts +24 -0
  98. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  99. package/dist/mcp/contract-input.d.ts +11 -0
  100. package/dist/mcp/documents.d.ts +7 -5
  101. package/dist/mcp/files.d.ts +2 -2
  102. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  103. package/dist/mcp/knowledge-entries.d.ts +5 -0
  104. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  105. package/dist/mcp/receipts.d.ts +9 -0
  106. package/dist/mcp/request-abort.d.ts +19 -0
  107. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  108. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  109. package/dist/mcp/server.d.ts +255 -3
  110. package/dist/mcp/session-event-view.d.ts +94 -0
  111. package/dist/mcp/session-view.d.ts +66 -2
  112. package/dist/mcp/session-wait.d.ts +174 -0
  113. package/dist/mcp-oauth.d.ts +52 -0
  114. package/dist/model-catalog.d.ts +11 -31
  115. package/dist/observability.d.ts +3 -1
  116. package/dist/organization-mcp.d.ts +52 -0
  117. package/dist/organization-recovery-notifications.d.ts +41 -0
  118. package/dist/routes/analytics-consent.d.ts +26 -0
  119. package/dist/routes/api-integrations.d.ts +25 -0
  120. package/dist/routes/api-keys.d.ts +17 -0
  121. package/dist/routes/artifact-catalog.d.ts +6 -0
  122. package/dist/routes/automations.d.ts +19 -0
  123. package/dist/routes/billing.d.ts +49 -0
  124. package/dist/routes/browser-sessions.d.ts +71 -3
  125. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  126. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  127. package/dist/routes/client-errors.d.ts +146 -0
  128. package/dist/routes/codex.d.ts +76 -2
  129. package/dist/routes/company-brain.d.ts +3 -0
  130. package/dist/routes/company-profile.d.ts +2 -1
  131. package/dist/routes/computer-sessions.d.ts +14 -0
  132. package/dist/routes/connect.d.ts +5 -0
  133. package/dist/routes/editable-artifacts.d.ts +2 -0
  134. package/dist/routes/external-identity-links.d.ts +6 -0
  135. package/dist/routes/feedback.d.ts +3 -0
  136. package/dist/routes/files.d.ts +4 -0
  137. package/dist/routes/github-browser-pages.d.ts +12 -0
  138. package/dist/routes/github.d.ts +2 -0
  139. package/dist/routes/insights-response-cache.d.ts +10 -0
  140. package/dist/routes/insights-usage.d.ts +12 -0
  141. package/dist/routes/insights.d.ts +30 -1
  142. package/dist/routes/knowledge.d.ts +3 -0
  143. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  144. package/dist/routes/managed-onboarding.d.ts +29 -0
  145. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  146. package/dist/routes/model-connection-access.d.ts +3 -0
  147. package/dist/routes/organization-integration-policy.d.ts +3 -0
  148. package/dist/routes/organization-integrations.d.ts +6 -0
  149. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  150. package/dist/routes/organization-model-providers.d.ts +3 -0
  151. package/dist/routes/organization-recovery.d.ts +19 -0
  152. package/dist/routes/organization-service-accounts.d.ts +3 -0
  153. package/dist/routes/organization-sessions.d.ts +26 -0
  154. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  155. package/dist/routes/personal-github.d.ts +3 -0
  156. package/dist/routes/pr-review-github.d.ts +3 -0
  157. package/dist/routes/pr-review.d.ts +3 -0
  158. package/dist/routes/session-artifact-associations.d.ts +4 -0
  159. package/dist/routes/session-history-imports.d.ts +9 -0
  160. package/dist/routes/sessions.d.ts +44 -6
  161. package/dist/routes/skill-content.d.ts +4 -0
  162. package/dist/routes/skill-install-authority.d.ts +6 -0
  163. package/dist/routes/subscription-account-pools.d.ts +34 -0
  164. package/dist/routes/subscription-pool-access.d.ts +16 -0
  165. package/dist/routes/supergrok.d.ts +6 -1
  166. package/dist/routes/usage-allowances.d.ts +12 -0
  167. package/dist/routes/user-resource-authorities.d.ts +3 -0
  168. package/dist/routes/workspace-artifacts.d.ts +2 -1
  169. package/dist/routes/workspace-capture.d.ts +10 -2
  170. package/dist/routes/workspace-integrations.d.ts +35 -0
  171. package/dist/routes/workspace-learning.d.ts +3 -0
  172. package/dist/routes/workspace-model-providers.d.ts +4 -0
  173. package/dist/routes/workspaces.d.ts +29 -0
  174. package/dist/sandbox/auth-callout.d.ts +8 -0
  175. package/dist/sandbox/channel-a.d.ts +19 -1
  176. package/dist/sandbox/connection-authority.d.ts +3 -0
  177. package/dist/sandbox/enrollment.d.ts +9 -1
  178. package/dist/sandbox/machines.d.ts +5 -2
  179. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  180. package/dist/sandbox/rematerialize.d.ts +2 -0
  181. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  182. package/dist/sandbox/viewer.d.ts +26 -1
  183. package/dist/sandbox-file-artifacts.d.ts +50 -0
  184. package/dist/scheduled-task-deletion.d.ts +13 -0
  185. package/dist/session-create-observability.d.ts +5 -0
  186. package/dist/session-event-view-MPS2OZD3.js +292 -0
  187. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  188. package/dist/site-session-origin.d.ts +13 -0
  189. package/dist/site-uploads.d.ts +41 -0
  190. package/dist/slack-reaction-files.d.ts +1 -1
  191. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  192. package/dist/temporal-schedule-sync.d.ts +26 -0
  193. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  194. package/dist/transcription/service.d.ts +2 -1
  195. package/dist/work-discovery-observability.d.ts +33 -0
  196. package/dist/workspace-artifact-content.d.ts +28 -0
  197. package/dist/workspace-artifact-provenance.d.ts +7 -0
  198. package/dist/workspace-delete-observability.d.ts +10 -0
  199. package/dist/workspace-deletion.d.ts +6 -0
  200. package/dist/workspace-state-projection.d.ts +3 -5
  201. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  202. package/dist/workspace-tool-gateway.d.ts +123 -0
  203. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  204. package/package.json +23 -19
  205. package/src/access-grant-rls.ts +75 -0
  206. package/src/admission-trace.ts +14 -0
  207. package/src/api-websocket.ts +23 -0
  208. package/src/app.ts +1974 -256
  209. package/src/artifact-catalog-cursor.ts +60 -0
  210. package/src/auth/managed-auth-attempt-context.ts +70 -0
  211. package/src/auth/managed-auth-rate-limits.ts +252 -0
  212. package/src/auth/managed-auth-session-adapter.ts +222 -0
  213. package/src/auth/managed-auth.ts +536 -60
  214. package/src/auth/managed-email.ts +174 -0
  215. package/src/auth/managed-sign-in-notifications.ts +81 -0
  216. package/src/auth/new-signups-gate.ts +49 -0
  217. package/src/auth/organization-user-setup.ts +307 -0
  218. package/src/auth/signup-funnel-metrics.ts +164 -0
  219. package/src/browser-controller-authority.ts +1 -186
  220. package/src/browser-ephemeral.ts +39 -0
  221. package/src/browser-state-authority.ts +1 -236
  222. package/src/claude-subscription-account-conflicts.ts +12 -0
  223. package/src/claude-subscription-account-usage.ts +48 -0
  224. package/src/claude-subscription-oauth.ts +362 -0
  225. package/src/claude-subscription-profile.ts +73 -0
  226. package/src/claude-subscription-usage.ts +97 -0
  227. package/src/claude-workspace-connection.ts +82 -0
  228. package/src/codemode.ts +194 -30
  229. package/src/codex-realtime.ts +8 -2
  230. package/src/company-brain-okf.ts +340 -0
  231. package/src/connected-machine-computer-access.ts +33 -0
  232. package/src/connection-ownership.ts +199 -0
  233. package/src/controller-data-plane.ts +81 -0
  234. package/src/editable-artifact-native-kernel.ts +26 -15
  235. package/src/editable-artifact-office-import.ts +77 -9
  236. package/src/editable-artifact-production.ts +23 -7
  237. package/src/editable-artifact-source-session.ts +123 -0
  238. package/src/editable-artifact-websocket.ts +27 -21
  239. package/src/editable-artifact-workspace-files.ts +31 -6
  240. package/src/fatal-process-boundary.ts +246 -0
  241. package/src/gateway-realtime.ts +1 -1
  242. package/src/github-access.ts +147 -5
  243. package/src/http/acting-person.ts +71 -0
  244. package/src/http/api-contract-compat.ts +49 -0
  245. package/src/http/api-error.ts +115 -1
  246. package/src/http/auth.ts +48 -0
  247. package/src/http/cors.ts +67 -0
  248. package/src/http/deprecation.ts +118 -0
  249. package/src/http/interaction-control-error.ts +164 -0
  250. package/src/http/keyed-admission.ts +29 -0
  251. package/src/http/local-browser-boundary.ts +343 -0
  252. package/src/http/metrics-listener.ts +57 -0
  253. package/src/http/path-identifier.ts +54 -0
  254. package/src/http/registered-route-label.ts +34 -0
  255. package/src/http/request-body.ts +127 -0
  256. package/src/http/request-source.ts +178 -0
  257. package/src/http/sse.ts +486 -132
  258. package/src/http/unmatched-route.ts +60 -0
  259. package/src/http/user-content.ts +158 -0
  260. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  261. package/src/index.ts +157 -52
  262. package/src/integration-endpoint-test.ts +291 -0
  263. package/src/integrations/api-integrations.ts +98 -11
  264. package/src/integrations/atlassian.ts +474 -136
  265. package/src/integrations/connect-authority.ts +2 -0
  266. package/src/integrations/connect-callback-return.ts +86 -0
  267. package/src/integrations/fiken.ts +354 -64
  268. package/src/integrations/github-app-connect.ts +435 -0
  269. package/src/integrations/github-installation-proof.ts +60 -0
  270. package/src/integrations/github-lens-connect.ts +76 -0
  271. package/src/integrations/github-skill-source.ts +2 -142
  272. package/src/integrations/google-drive.ts +820 -222
  273. package/src/integrations/integration-commit-authority.ts +69 -0
  274. package/src/integrations/marketplace-plugin.ts +48 -0
  275. package/src/integrations/oauth-client.ts +1356 -459
  276. package/src/integrations/oauth-profiles.ts +537 -0
  277. package/src/integrations/oauth-return-path.ts +18 -0
  278. package/src/integrations/personal-github-repositories.ts +674 -0
  279. package/src/integrations/personal-github.ts +825 -0
  280. package/src/integrations/pr-review-provider.ts +246 -0
  281. package/src/integrations/provider-oauth.ts +250 -22
  282. package/src/integrations/slack-app-home.ts +300 -0
  283. package/src/integrations/slack-bot.ts +963 -239
  284. package/src/integrations/slack-file-upload-flow.ts +147 -0
  285. package/src/integrations/slack-install.ts +83 -0
  286. package/src/integrations/slack-interactions.ts +3170 -474
  287. package/src/integrations/slack-mrkdwn.ts +193 -0
  288. package/src/integrations/slack-routing.ts +337 -0
  289. package/src/integrations/slack-session-defaults.ts +224 -0
  290. package/src/integrations/slack-task-file-upload.ts +323 -0
  291. package/src/integrations/social-oauth.ts +213 -15
  292. package/src/interaction-agent-access.ts +48 -0
  293. package/src/interaction-frame-proxy.ts +409 -0
  294. package/src/interaction-metrics.ts +30 -0
  295. package/src/mcp/action-catalog-types.ts +9 -0
  296. package/src/mcp/action-catalog.gen.ts +6696 -0
  297. package/src/mcp/capability-account-readiness.ts +17 -0
  298. package/src/mcp/child-read-evidence.ts +19 -0
  299. package/src/mcp/command-read.ts +109 -0
  300. package/src/mcp/company-profile-agent-admin.ts +198 -0
  301. package/src/mcp/contract-input.ts +141 -0
  302. package/src/mcp/documents.ts +65 -377
  303. package/src/mcp/editable-artifacts.ts +12 -6
  304. package/src/mcp/files.ts +80 -38
  305. package/src/mcp/first-party-tool-permissions.ts +305 -0
  306. package/src/mcp/knowledge-entries.ts +322 -0
  307. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  308. package/src/mcp/receipts.ts +34 -0
  309. package/src/mcp/request-abort.ts +44 -0
  310. package/src/mcp/scheduled-task-input.ts +76 -0
  311. package/src/mcp/scheduled-task-view.ts +44 -3
  312. package/src/mcp/server.ts +3030 -1267
  313. package/src/mcp/session-event-view.ts +383 -0
  314. package/src/mcp/session-view.ts +84 -3
  315. package/src/mcp/session-wait.ts +665 -0
  316. package/src/mcp-oauth.ts +1123 -0
  317. package/src/model-catalog.ts +91 -333
  318. package/src/observability.ts +15 -1
  319. package/src/organization-mcp.ts +397 -0
  320. package/src/organization-recovery-notifications.ts +103 -0
  321. package/src/routes/analytics-consent.ts +123 -0
  322. package/src/routes/api-integrations.ts +364 -74
  323. package/src/routes/api-keys.ts +336 -10
  324. package/src/routes/artifact-catalog.ts +279 -0
  325. package/src/routes/automations.ts +768 -0
  326. package/src/routes/billing.ts +461 -46
  327. package/src/routes/browser-sessions.ts +839 -131
  328. package/src/routes/canonical-human-identities.ts +90 -14
  329. package/src/routes/capabilities.ts +107 -6
  330. package/src/routes/channels.ts +20 -3
  331. package/src/routes/claude-subscription-accounts.ts +334 -0
  332. package/src/routes/claude-subscription-oauth.ts +142 -0
  333. package/src/routes/client-errors.ts +362 -0
  334. package/src/routes/codex.ts +833 -116
  335. package/src/routes/company-brain.ts +322 -0
  336. package/src/routes/company-profile.ts +65 -1
  337. package/src/routes/computer-sessions.ts +557 -106
  338. package/src/routes/connect.ts +1938 -0
  339. package/src/routes/connections.ts +1040 -349
  340. package/src/routes/documents.ts +488 -287
  341. package/src/routes/editable-artifacts.ts +79 -9
  342. package/src/routes/enrollments.ts +135 -39
  343. package/src/routes/environments.ts +154 -58
  344. package/src/routes/external-identity-links.ts +201 -0
  345. package/src/routes/feedback.ts +113 -0
  346. package/src/routes/files.ts +337 -39
  347. package/src/routes/github-browser-pages.ts +162 -0
  348. package/src/routes/github.ts +446 -144
  349. package/src/routes/insights-response-cache.ts +83 -0
  350. package/src/routes/insights-usage.ts +345 -0
  351. package/src/routes/insights.ts +153 -19
  352. package/src/routes/install.ts +133 -38
  353. package/src/routes/integration-facets.ts +107 -33
  354. package/src/routes/interaction-resources.ts +30 -3
  355. package/src/routes/knowledge.ts +488 -0
  356. package/src/routes/machines.ts +313 -15
  357. package/src/routes/managed-auth-session-sets.ts +1218 -0
  358. package/src/routes/managed-onboarding.ts +337 -0
  359. package/src/routes/managed-sign-in-methods.ts +433 -0
  360. package/src/routes/memory-slack-publications.ts +3 -2
  361. package/src/routes/model-connection-access.ts +208 -0
  362. package/src/routes/organization-integration-policy.ts +116 -0
  363. package/src/routes/organization-integrations.ts +326 -0
  364. package/src/routes/organization-mcp-connections.ts +178 -0
  365. package/src/routes/organization-memberships.ts +1019 -27
  366. package/src/routes/organization-model-providers.ts +293 -0
  367. package/src/routes/organization-recovery.ts +325 -0
  368. package/src/routes/organization-service-accounts.ts +128 -0
  369. package/src/routes/organization-sessions.ts +350 -0
  370. package/src/routes/personal-github-git-broker.ts +785 -0
  371. package/src/routes/personal-github.ts +370 -0
  372. package/src/routes/plugins.ts +251 -22
  373. package/src/routes/pr-review-github.ts +881 -0
  374. package/src/routes/pr-review.ts +622 -0
  375. package/src/routes/preference-registry.ts +12 -93
  376. package/src/routes/rigs.ts +167 -50
  377. package/src/routes/scheduled-tasks.ts +346 -41
  378. package/src/routes/session-artifact-associations.ts +87 -0
  379. package/src/routes/session-history-imports.ts +280 -0
  380. package/src/routes/sessions.ts +2306 -264
  381. package/src/routes/skill-content.ts +297 -0
  382. package/src/routes/skill-install-authority.ts +64 -0
  383. package/src/routes/skills.ts +62 -9
  384. package/src/routes/social.ts +46 -18
  385. package/src/routes/subscription-account-pools.ts +436 -0
  386. package/src/routes/subscription-pool-access.ts +126 -0
  387. package/src/routes/supergrok.ts +219 -303
  388. package/src/routes/transcription-recordings.ts +38 -3
  389. package/src/routes/transcriptions.ts +4 -0
  390. package/src/routes/usage-allowances.ts +378 -0
  391. package/src/routes/user-resource-authorities.ts +138 -0
  392. package/src/routes/video-generation.ts +14 -10
  393. package/src/routes/workspace-artifacts.ts +275 -69
  394. package/src/routes/workspace-capture.ts +21 -0
  395. package/src/routes/workspace-integrations.ts +576 -0
  396. package/src/routes/workspace-learning.ts +159 -0
  397. package/src/routes/workspace-model-providers.ts +148 -0
  398. package/src/routes/workspace-state.ts +38 -25
  399. package/src/routes/workspaces.ts +1004 -127
  400. package/src/sandbox/auth-callout.ts +96 -19
  401. package/src/sandbox/channel-a.ts +323 -24
  402. package/src/sandbox/connection-authority.ts +3 -0
  403. package/src/sandbox/enrollment.ts +81 -6
  404. package/src/sandbox/machines.ts +218 -76
  405. package/src/sandbox/metrics-ingestion.ts +411 -62
  406. package/src/sandbox/rematerialize.ts +121 -19
  407. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  408. package/src/sandbox/viewer.ts +280 -25
  409. package/src/sandbox-file-artifacts.ts +413 -0
  410. package/src/scheduled-task-deletion.ts +138 -0
  411. package/src/session-create-observability.ts +46 -0
  412. package/src/session-realtime-context.ts +6 -3
  413. package/src/site-session-origin.ts +48 -0
  414. package/src/site-uploads.ts +224 -0
  415. package/src/slack-reaction-files.ts +16 -5
  416. package/src/temporal-schedule-cleanup.ts +13 -0
  417. package/src/temporal-schedule-sync.ts +74 -0
  418. package/src/transcription/providers/codex-subscription.ts +2 -0
  419. package/src/transcription/providers/openai.ts +1 -0
  420. package/src/transcription/providers/xai-subscription.ts +49 -3
  421. package/src/transcription/segmenter.ts +8 -2
  422. package/src/transcription/service.ts +43 -6
  423. package/src/work-discovery-observability.ts +121 -0
  424. package/src/workspace-artifact-content.ts +144 -0
  425. package/src/workspace-artifact-provenance.ts +104 -0
  426. package/src/workspace-delete-observability.ts +75 -0
  427. package/src/workspace-deletion.ts +73 -0
  428. package/src/workspace-state-projection.ts +18 -148
  429. package/src/workspace-tool-gateway-observability.ts +100 -0
  430. package/src/workspace-tool-gateway.ts +870 -0
  431. package/src/workspace-webhook-dispatch.ts +190 -0
  432. package/src/xai-subscription-auth.ts +9 -2
  433. package/dist/chunk-2PQMSRCB.js +0 -49759
  434. package/dist/chunk-2PQMSRCB.js.map +0 -1
  435. package/dist/routes/packs.d.ts +0 -3
  436. package/src/routes/packs.ts +0 -729
@@ -1,13 +1,24 @@
1
1
  import {
2
+ GitHubActionPoliciesResponse,
3
+ GitHubActionPolicyActorState,
2
4
  GitHubAppManifestCreate,
5
+ OrganizationIntegrationDeniedError,
6
+ UpdateGitHubActionPolicyRequest,
3
7
  type AccessGrant,
4
8
  type GitHubInstallationBindingCandidate,
5
- type GitHubInstallationBindingProof,
6
9
  } from "@opengeni/contracts";
10
+ import { PersonalGitHubConnectionMetadata } from "@opengeni/contracts/personal-github";
11
+ import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
12
+ import {
13
+ ListGitHubRepositoryBranchesQuery,
14
+ VerifyPublicGitHubRepositoryRefRequest,
15
+ } from "@opengeni/contracts/github-repository-contracts";
16
+ import { parseCanonicalGitHubRepositoryUrl } from "@opengeni/contracts/github-repository";
7
17
  import {
8
18
  bindAuthorizedGitHubInstallationRepositories,
9
19
  deleteGitHubInstallationBinding,
10
20
  GitHubInstallationAuthorityCommitError,
21
+ listGitHubInstallationAccessForWorkspace,
11
22
  } from "@opengeni/db";
12
23
  import {
13
24
  authorizeGitHubInstallationBinding,
@@ -19,19 +30,32 @@ import {
19
30
  GitHubAppApiError,
20
31
  GitHubAppConfigurationError,
21
32
  GitHubInstallationAuthorityError,
33
+ GitHubPublicRepositoryVerificationError,
22
34
  githubAppMissingSettings,
23
35
  githubOAuthAuthorizeUrl,
36
+ inspectSignedState,
24
37
  organizationAppManifestUrl,
25
38
  personalAppManifestUrl,
26
39
  readSignedState,
27
40
  stateMaxAgeSeconds,
41
+ verifyPublicGitHubRepositoryRef,
28
42
  type GitHubSignedStatePayload,
29
43
  verifySignedState,
30
44
  } from "@opengeni/github";
31
- import type { Context, Hono } from "hono";
45
+ import type { Context, Hono, MiddlewareHandler } from "hono";
32
46
  import { deleteCookie, setCookie } from "hono/cookie";
33
47
  import { HTTPException } from "hono/http-exception";
34
- import { hasPermission, requireAccessGrant } from "@opengeni/core";
48
+ import type { ContentfulStatusCode } from "hono/utils/http-status";
49
+ import {
50
+ githubAppActionPolicyActor,
51
+ hasPermission,
52
+ listGitHubActionPolicyActors,
53
+ personalGitHubActionPolicyActor,
54
+ requireAccessGrant,
55
+ requireAccessGrantAuthorization,
56
+ externalActorContinuationForAuthorization,
57
+ updateGitHubActionPolicyGroup,
58
+ } from "@opengeni/core";
35
59
  import type { ApiRouteDeps } from "@opengeni/core";
36
60
  import {
37
61
  continuedGitHubBrowserGrantClaims,
@@ -42,20 +66,103 @@ import {
42
66
  } from "../github-browser-flow";
43
67
  import {
44
68
  githubBindingStatus,
69
+ GitHubRepositoryBranchAuthorityError,
45
70
  listWorkspaceGitHubInstallationBindings,
71
+ listWorkspaceGitHubRepositoryBranches,
46
72
  listWorkspaceGitHubRepositories,
47
73
  } from "../github-access";
74
+ import {
75
+ assertPersonalConnectionOwnerPrincipal,
76
+ requireLegacyOAuthActor,
77
+ } from "../connection-ownership";
78
+ import { workspaceIntegrationsPathForUntrusted } from "../integrations/oauth-client";
79
+ import { listPersonalGitHubConnections } from "../integrations/personal-github";
80
+ import {
81
+ integrationCommitGrant,
82
+ type IntegrationCommitGrant,
83
+ } from "../integrations/integration-commit-authority";
84
+ import {
85
+ isConsistentGitHubBindingCandidates,
86
+ isConsistentGitHubBindingProof,
87
+ } from "../integrations/github-installation-proof";
88
+ import {
89
+ githubConnectFailureHtml,
90
+ githubInstallationChooserHtml,
91
+ githubSetupPendingHtml,
92
+ githubSetupSuccessHtml,
93
+ githubSuccessHtml,
94
+ type GitHubConnectFailure,
95
+ } from "./github-browser-pages";
96
+ import {
97
+ completeGitHubAppConnect,
98
+ isGitHubAppConnectState,
99
+ } from "../integrations/github-app-connect";
100
+ import { parseRequestJson } from "../http/request-body";
48
101
 
49
102
  const githubStateCookie = "opengeni_github_state";
50
103
  const githubBindingStateMaxAgeSeconds = 10 * 60;
51
104
  const legacyInstallationChooserDisabledMessage =
52
105
  "The legacy repository-admin GitHub installation chooser is disabled; use the GitHub owner-consent connect flow";
106
+ /**
107
+ * GitHub routes a browser navigates to (not the JSON API). A page-load link
108
+ * opened after it expired, GitHub's Cancel button, or a non-owner all land on
109
+ * one of these, so their failures render a readable page instead of JSON.
110
+ */
111
+ const GITHUB_BROWSER_ROUTES = [
112
+ "/v1/workspaces/:workspaceId/github/connect",
113
+ "/v1/workspaces/:workspaceId/github/installations/select",
114
+ "/v1/workspaces/:workspaceId/github/installations/:installationId/configure",
115
+ "/v1/github/app-manifest/callback",
116
+ "/v1/github/setup",
117
+ "/v1/github/install/callback",
118
+ "/v1/github/oauth/callback",
119
+ ] as const;
120
+
121
+ /** An HTTP failure that already knows which browser page explains it. */
122
+ class GitHubBrowserFailure extends HTTPException {
123
+ constructor(
124
+ status: 400 | 403,
125
+ message: string,
126
+ readonly failure: GitHubConnectFailure,
127
+ ) {
128
+ super(status, { message });
129
+ this.name = "GitHubBrowserFailure";
130
+ }
131
+ }
132
+
53
133
  export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
54
134
  const { db, settings, githubStateSecret } = deps;
135
+ // Registered before the routes so it wraps them. The status code is kept, so
136
+ // automation still sees the same outcome; only the body becomes a page. Every
137
+ // failure is rendered, not only HTTP ones: an organization policy denial or an
138
+ // unexpected fault must not reach the browser as the JSON error envelope.
139
+ const browserFailurePage: MiddlewareHandler = async (c, next) => {
140
+ let failure: unknown;
141
+ let handledStatus: number | undefined;
142
+ try {
143
+ await next();
144
+ if (!c.error) return;
145
+ failure = c.error;
146
+ // The app error handler has already answered this failure.
147
+ handledStatus = c.res.status;
148
+ } catch (error) {
149
+ failure = error;
150
+ }
151
+ c.res = c.html(
152
+ githubConnectFailureHtml(
153
+ githubBrowserFailureKind(failure),
154
+ githubFailureReturnUrl(deps, c),
155
+ githubBrowserFailureDetail(failure),
156
+ ),
157
+ githubBrowserFailureStatus(failure, handledStatus) as ContentfulStatusCode,
158
+ );
159
+ };
160
+ for (const path of GITHUB_BROWSER_ROUTES) app.use(path, browserFailurePage);
55
161
 
56
162
  app.get("/v1/workspaces/:workspaceId/github/app", async (c) => {
57
163
  const workspaceId = c.req.param("workspaceId");
58
- const grant = await requireAccessGrant(c, deps, workspaceId, "github:use");
164
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:use");
165
+ const grant = access.grant;
59
166
  const missing = githubAppMissingSettings(settings);
60
167
  const slug = settings.githubAppSlug?.trim() || null;
61
168
  const installations =
@@ -64,7 +171,9 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
64
171
  : [];
65
172
  const status = githubBindingStatus(missing.length === 0, installations);
66
173
  const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
67
- const canManage = hasPermission(grant.permissions, "github:manage");
174
+ const canManage =
175
+ !externalActorContinuationForAuthorization(access) &&
176
+ hasPermission(grant.permissions, "github:manage");
68
177
  const returnPath = githubSessionReturnPath(c.req.query("returnPath"), grant.workspaceId);
69
178
  const connectState =
70
179
  missing.length === 0 && slug && canManage
@@ -99,6 +208,95 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
99
208
  });
100
209
  });
101
210
 
211
+ app.get("/v1/workspaces/:workspaceId/github/action-policies", async (c) => {
212
+ const workspaceId = c.req.param("workspaceId");
213
+ const grant = await requireAccessGrant(c, deps, workspaceId, "github:use");
214
+ const [installations, personalConnections] = await Promise.all([
215
+ listGitHubInstallationAccessForWorkspace(db, grant.workspaceId),
216
+ listPersonalGitHubConnections(deps, {
217
+ workspaceId: grant.workspaceId,
218
+ subjectId: grant.subjectId,
219
+ }),
220
+ ]);
221
+ const personalConnection = canonicalPersonalGitHubPolicyConnection(personalConnections);
222
+ const actors = [
223
+ ...installations.map(githubAppActionPolicyActor),
224
+ ...(personalConnection
225
+ ? [
226
+ personalGitHubActionPolicyActor({
227
+ connectionId: personalConnection.id,
228
+ githubLogin: PersonalGitHubConnectionMetadata.parse(personalConnection.metadata)
229
+ .githubLogin,
230
+ }),
231
+ ]
232
+ : []),
233
+ ];
234
+ return c.json(
235
+ GitHubActionPoliciesResponse.parse({
236
+ enabled: settings.githubRestMcpEnabled,
237
+ actors: await listGitHubActionPolicyActors(db, {
238
+ accountId: grant.accountId,
239
+ workspaceId: grant.workspaceId,
240
+ actors,
241
+ }),
242
+ }),
243
+ );
244
+ });
245
+
246
+ app.patch("/v1/workspaces/:workspaceId/github/action-policies", async (c) => {
247
+ const workspaceId = c.req.param("workspaceId");
248
+ const payload = await parseRequestJson(c, UpdateGitHubActionPolicyRequest);
249
+ const actor = payload.actor;
250
+ if (actor.kind === "workspace_app") {
251
+ const grant = await requireAccessGrant(c, deps, workspaceId, "github:manage");
252
+ const installations = await listGitHubInstallationAccessForWorkspace(db, grant.workspaceId);
253
+ const installation = installations.find(
254
+ (candidate) => candidate.installationId === actor.installationId,
255
+ );
256
+ if (!installation || installation.accountId !== grant.accountId) {
257
+ throw new HTTPException(404, { message: "GitHub installation not found" });
258
+ }
259
+ return c.json(
260
+ GitHubActionPolicyActorState.parse(
261
+ await updateGitHubActionPolicyGroup(db, {
262
+ accountId: grant.accountId,
263
+ workspaceId: grant.workspaceId,
264
+ subjectId: grant.subjectId,
265
+ actor: githubAppActionPolicyActor(installation),
266
+ group: payload.group,
267
+ decision: payload.decision,
268
+ }),
269
+ ),
270
+ );
271
+ }
272
+
273
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
274
+ assertPersonalConnectionOwnerPrincipal(access, "My GitHub action policy");
275
+ const connections = await listPersonalGitHubConnections(deps, {
276
+ workspaceId: access.grant.workspaceId,
277
+ subjectId: access.grant.subjectId,
278
+ });
279
+ const connection = connections.find((candidate) => candidate.id === actor.connectionId);
280
+ if (!connection) {
281
+ throw new HTTPException(404, { message: "personal GitHub connection not found" });
282
+ }
283
+ return c.json(
284
+ GitHubActionPolicyActorState.parse(
285
+ await updateGitHubActionPolicyGroup(db, {
286
+ accountId: access.grant.accountId,
287
+ workspaceId: access.grant.workspaceId,
288
+ subjectId: access.grant.subjectId,
289
+ actor: personalGitHubActionPolicyActor({
290
+ connectionId: connection.id,
291
+ githubLogin: PersonalGitHubConnectionMetadata.parse(connection.metadata).githubLogin,
292
+ }),
293
+ group: payload.group,
294
+ decision: payload.decision,
295
+ }),
296
+ ),
297
+ );
298
+ });
299
+
102
300
  // Start with user authorization, not GitHub's install/configure selector.
103
301
  // This lets an owner link an already-installed App without relying on
104
302
  // GitHub's Configure page to preserve or return OpenGeni state.
@@ -177,6 +375,65 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
177
375
  }
178
376
  });
179
377
 
378
+ app.post("/v1/workspaces/:workspaceId/github/public-repositories/verify", async (c) => {
379
+ const workspaceId = c.req.param("workspaceId");
380
+ const grant = await requireAccessGrant(c, deps, workspaceId);
381
+ requirePublicGitHubRepositoryVerificationPermission(grant);
382
+ const request = await parseRequestJson(c, VerifyPublicGitHubRepositoryRefRequest);
383
+ let repository: ReturnType<typeof parseCanonicalGitHubRepositoryUrl>;
384
+ try {
385
+ repository = parseCanonicalGitHubRepositoryUrl(request.url);
386
+ } catch (error) {
387
+ throw new HTTPException(422, {
388
+ message: error instanceof Error ? error.message : "GitHub repository URL is invalid.",
389
+ });
390
+ }
391
+ try {
392
+ return c.json(
393
+ await verifyPublicGitHubRepositoryRef(
394
+ { repository, ref: request.ref },
395
+ deps.githubAnonymousFetch ?? fetch,
396
+ ),
397
+ );
398
+ } catch (error) {
399
+ if (error instanceof GitHubPublicRepositoryVerificationError) {
400
+ throw new HTTPException(error.code === "provider_unavailable" ? 503 : 422, {
401
+ message: error.message,
402
+ });
403
+ }
404
+ throw new HTTPException(503, {
405
+ message: "GitHub public repository verification is temporarily unavailable.",
406
+ });
407
+ }
408
+ });
409
+
410
+ app.get(
411
+ "/v1/workspaces/:workspaceId/github/installations/:installationId/repositories/:repositoryId/branches",
412
+ async (c) => {
413
+ const workspaceId = c.req.param("workspaceId");
414
+ const grant = await requireAccessGrant(c, deps, workspaceId, "github:use");
415
+ const installationId = parsePositiveInteger(c.req.param("installationId"));
416
+ const repositoryId = parsePositiveInteger(c.req.param("repositoryId"));
417
+ if (installationId === null || repositoryId === null) {
418
+ throw new HTTPException(400, { message: "invalid GitHub repository identity" });
419
+ }
420
+ const query = ListGitHubRepositoryBranchesQuery.parse(c.req.query());
421
+ try {
422
+ return c.json(
423
+ await listWorkspaceGitHubRepositoryBranches(deps, {
424
+ accountId: grant.accountId,
425
+ workspaceId: grant.workspaceId,
426
+ installationId,
427
+ repositoryId,
428
+ query,
429
+ }),
430
+ );
431
+ } catch (error) {
432
+ throw githubRepositoryBranchesRouteError(error);
433
+ }
434
+ },
435
+ );
436
+
180
437
  app.delete("/v1/workspaces/:workspaceId/github/installations/:installationId", async (c) => {
181
438
  const workspaceId = c.req.param("workspaceId");
182
439
  const grant = await requireAccessGrant(c, deps, workspaceId, "github:manage");
@@ -184,14 +441,11 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
184
441
  if (installationId === null) {
185
442
  throw new HTTPException(400, { message: "invalid GitHub installation id" });
186
443
  }
187
- const deleted = await deleteGitHubInstallationBinding(db, {
444
+ await deleteGitHubInstallationBinding(db, {
188
445
  accountId: grant.accountId,
189
446
  workspaceId: grant.workspaceId,
190
447
  installationId,
191
448
  });
192
- if (!deleted) {
193
- throw new HTTPException(404, { message: "GitHub installation binding not found" });
194
- }
195
449
  return c.body(null, 204);
196
450
  });
197
451
 
@@ -245,8 +499,10 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
245
499
  app.post("/v1/workspaces/:workspaceId/github/app-manifest", async (c) => {
246
500
  assertOperatorGitHubAppSetup(settings);
247
501
  const workspaceId = c.req.param("workspaceId");
248
- const grant = await requireAccessGrant(c, deps, workspaceId, "github:manage");
249
- const payload = GitHubAppManifestCreate.parse(await c.req.json());
502
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:manage");
503
+ requireLegacyOAuthActor(access);
504
+ const grant = access.grant;
505
+ const payload = await parseRequestJson(c, GitHubAppManifestCreate);
250
506
  const baseUrl = (settings.githubAppManifestBaseUrl ?? new URL(c.req.url).origin).replace(
251
507
  /\/+$/,
252
508
  "",
@@ -296,6 +552,14 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
296
552
  });
297
553
 
298
554
  const handleGitHubInstallCallback = async (c: Context) => {
555
+ if (isGitHubAppConnectState(deps, c.req.query("state")))
556
+ return completeGitHubAppConnect(deps, {
557
+ state: c.req.query("state"),
558
+ installationId: c.req.query("installation_id"),
559
+ setupAction: c.req.query("setup_action"),
560
+ error: c.req.query("error"),
561
+ requestUrl: c.req.url,
562
+ });
299
563
  const state =
300
564
  c.req.query("state") ??
301
565
  allCookieValues(c, githubStateCookie).find((candidate) => {
@@ -373,8 +637,18 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
373
637
  app.get("/v1/github/install/callback", handleGitHubInstallCallback);
374
638
 
375
639
  app.get("/v1/github/oauth/callback", async (c) => {
640
+ if (isGitHubAppConnectState(deps, c.req.query("state")))
641
+ return completeGitHubAppConnect(deps, {
642
+ state: c.req.query("state"),
643
+ code: c.req.query("code"),
644
+ error: c.req.query("error"),
645
+ requestUrl: c.req.url,
646
+ });
376
647
  const code = c.req.query("code");
377
648
  const state = c.req.query("state");
649
+ if (c.req.query("error") === "access_denied") {
650
+ throw new GitHubBrowserFailure(400, "GitHub authorization was cancelled", "cancelled");
651
+ }
378
652
  if (!code) {
379
653
  throw new HTTPException(400, { message: "missing GitHub OAuth code" });
380
654
  }
@@ -398,6 +672,7 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
398
672
  message: "GitHub OAuth state does not match this workspace",
399
673
  });
400
674
  }
675
+ await withOrganizationIntegrationAcquisition(db, grant, ["github-app"], async () => {});
401
676
  let candidates: GitHubInstallationBindingCandidate[] | null;
402
677
  try {
403
678
  candidates = deps.githubAppApi?.discoverInstallationBindingCandidates
@@ -428,14 +703,6 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
428
703
  if (candidates.length === 0) {
429
704
  return redirectToGitHubInstallation(c, deps, selectionState);
430
705
  }
431
- if (candidates.length === 1) {
432
- return redirectToExactGitHubAuthorization(
433
- c,
434
- deps,
435
- selectionState,
436
- candidates[0]!.installation.installationId,
437
- );
438
- }
439
706
  setGitHubStateCookie(c, deps, selectionState);
440
707
  return c.html(
441
708
  githubInstallationChooserHtml(
@@ -460,6 +727,7 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
460
727
  message: "GitHub OAuth state does not match this workspace",
461
728
  });
462
729
  }
730
+ await withOrganizationIntegrationAcquisition(db, grant, ["github-app"], async () => {});
463
731
  let proof;
464
732
  try {
465
733
  proof = deps.githubAppApi?.authorizeInstallationBinding
@@ -489,22 +757,30 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
489
757
  const expiresAt = new Date((statePayload.iat + githubBindingStateMaxAgeSeconds) * 1_000);
490
758
  let bound;
491
759
  try {
492
- bound = await bindAuthorizedGitHubInstallationRepositories(db, {
493
- accountId: grant.accountId,
494
- workspaceId: grant.workspaceId,
495
- installationId,
496
- githubAccountId: proof.installation.accountId,
497
- accountLogin: proof.installation.accountLogin,
498
- accountType: proof.installation.accountType,
499
- linkedBySubjectId: grant.subjectId,
500
- githubActorId: proof.actorId,
501
- githubActorLogin: proof.actorLogin,
502
- authorityKind: proof.authorityKind,
503
- authorityCheckedAt,
504
- authorityExpiresAt: expiresAt,
505
- authorityNonce: statePayload.nonce,
506
- repositoryIds,
507
- });
760
+ bound = await withOrganizationIntegrationAcquisition(
761
+ db,
762
+ grant,
763
+ ["github-app"],
764
+ async (tx) => {
765
+ await grant.authorizeCommit(tx);
766
+ return bindAuthorizedGitHubInstallationRepositories(tx, {
767
+ accountId: grant.accountId,
768
+ workspaceId: grant.workspaceId,
769
+ installationId,
770
+ githubAccountId: proof.installation.accountId,
771
+ accountLogin: proof.installation.accountLogin,
772
+ accountType: proof.installation.accountType,
773
+ linkedBySubjectId: grant.subjectId,
774
+ githubActorId: proof.actorId,
775
+ githubActorLogin: proof.actorLogin,
776
+ authorityKind: proof.authorityKind,
777
+ authorityCheckedAt,
778
+ authorityExpiresAt: expiresAt,
779
+ authorityNonce: statePayload.nonce,
780
+ repositoryIds,
781
+ });
782
+ },
783
+ );
508
784
  } catch (error) {
509
785
  if (error instanceof GitHubInstallationAuthorityCommitError) {
510
786
  throw new HTTPException(409, { message: error.message });
@@ -646,6 +922,25 @@ function githubInstallationSettingsUrl(installation: {
646
922
  return new URL(`https://github.com/settings/installations/${installation.installationId}`);
647
923
  }
648
924
 
925
+ function canonicalPersonalGitHubPolicyConnection<
926
+ T extends { id: string; status: string; updatedAt: string },
927
+ >(connections: T[]): T | null {
928
+ const statusRank: Record<string, number> = {
929
+ active: 0,
930
+ needs_reauth: 1,
931
+ error: 2,
932
+ revoked: 3,
933
+ };
934
+ return (
935
+ [...connections].sort((left, right) => {
936
+ const rank = (statusRank[left.status] ?? 4) - (statusRank[right.status] ?? 4);
937
+ if (rank !== 0) return rank;
938
+ const updated = right.updatedAt.localeCompare(left.updatedAt);
939
+ return updated !== 0 ? updated : right.id.localeCompare(left.id);
940
+ })[0] ?? null
941
+ );
942
+ }
943
+
649
944
  function redirectToExactGitHubAuthorization(
650
945
  c: Context,
651
946
  deps: ApiRouteDeps,
@@ -702,16 +997,32 @@ async function requireGitHubManageGrant(
702
997
  deps: ApiRouteDeps,
703
998
  workspaceId: string,
704
999
  expectedState: GitHubSignedStatePayload,
705
- ): Promise<AccessGrant> {
1000
+ ): Promise<IntegrationCommitGrant> {
706
1001
  try {
707
- return await requireAccessGrant(c, deps, workspaceId, "github:manage");
1002
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:manage");
1003
+ requireLegacyOAuthActor(access);
1004
+ return integrationCommitGrant(access, ["github:manage"], {
1005
+ settings: deps.settings,
1006
+ authorizationHeader: c.req.header("authorization"),
1007
+ });
708
1008
  } catch (error) {
709
1009
  if (!(error instanceof HTTPException) || error.status !== 401) {
710
1010
  throw error;
711
1011
  }
712
1012
  const grant = githubBrowserGrantFromState(deps.settings, expectedState, workspaceId);
713
1013
  if (grant) {
714
- return grant;
1014
+ return {
1015
+ ...grant,
1016
+ authorizeCommit: async () => {
1017
+ const current = githubBrowserGrantFromState(deps.settings, expectedState, workspaceId);
1018
+ if (
1019
+ !current ||
1020
+ current.accountId !== grant.accountId ||
1021
+ current.subjectId !== grant.subjectId
1022
+ )
1023
+ throw new HTTPException(403, { message: "GitHub browser handoff expired or changed" });
1024
+ },
1025
+ };
715
1026
  }
716
1027
  throw error;
717
1028
  }
@@ -739,7 +1050,7 @@ function githubAuthorityHttpError(error: unknown): HTTPException {
739
1050
  }
740
1051
  if (error instanceof GitHubInstallationAuthorityError) {
741
1052
  if (error.reason === "authority_denied") {
742
- return new HTTPException(403, { message: error.message });
1053
+ return new GitHubBrowserFailure(403, error.message, "not_owner");
743
1054
  }
744
1055
  if (error.reason === "installation_missing") {
745
1056
  return new HTTPException(404, { message: error.message });
@@ -757,6 +1068,50 @@ function githubAuthorityHttpError(error: unknown): HTTPException {
757
1068
  return new HTTPException(502, { message: "GitHub authority verification failed" });
758
1069
  }
759
1070
 
1071
+ function githubRepositoryBranchesRouteError(error: unknown): Error {
1072
+ if (error instanceof HTTPException) {
1073
+ return error;
1074
+ }
1075
+ if (error instanceof GitHubRepositoryBranchAuthorityError) {
1076
+ return error.code === "not_authorized"
1077
+ ? new HTTPException(404, {
1078
+ message: "GitHub repository is not authorized for this workspace",
1079
+ })
1080
+ : new HTTPException(409, {
1081
+ message: "GitHub repository authorization changed; refresh and try again",
1082
+ });
1083
+ }
1084
+ if (error instanceof GitHubAppConfigurationError) {
1085
+ return new HTTPException(409, { message: "GitHub App is not configured" });
1086
+ }
1087
+ if (error instanceof GitHubAppApiError) {
1088
+ if (error.status === 429) {
1089
+ return new HTTPException(503, { message: "GitHub is temporarily rate limited" });
1090
+ }
1091
+ if (error.status === 401 || error.status === 403 || error.status === 404) {
1092
+ return new HTTPException(409, {
1093
+ message: "GitHub repository access changed; refresh and try again",
1094
+ });
1095
+ }
1096
+ return new HTTPException(502, { message: "GitHub branch discovery is unavailable" });
1097
+ }
1098
+ return new HTTPException(502, { message: "GitHub branch discovery is unavailable" });
1099
+ }
1100
+
1101
+ export function requirePublicGitHubRepositoryVerificationPermission(
1102
+ grant: Pick<AccessGrant, "permissions">,
1103
+ ): void {
1104
+ if (
1105
+ hasPermission(grant.permissions, "sessions:create") ||
1106
+ hasPermission(grant.permissions, "sessions:control")
1107
+ ) {
1108
+ return;
1109
+ }
1110
+ throw new HTTPException(403, {
1111
+ message: "missing permission: sessions:create or sessions:control",
1112
+ });
1113
+ }
1114
+
760
1115
  function isSecureRequest(c: Context, deps: ApiRouteDeps): boolean {
761
1116
  return (
762
1117
  deps.settings.publicBaseUrl?.startsWith("https://") ||
@@ -765,62 +1120,6 @@ function isSecureRequest(c: Context, deps: ApiRouteDeps): boolean {
765
1120
  );
766
1121
  }
767
1122
 
768
- function githubSuccessHtml(envLines: string[]): string {
769
- const envText = envLines.join("\n");
770
- const escaped = escapeHtml(envText);
771
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Created</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(760px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.env-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 8px}.env-header h2{margin:0;font-size:13px;line-height:1.2;text-transform:uppercase;letter-spacing:.08em;color:#a1a1aa}pre{white-space:pre-wrap;word-break:break-word;max-height:380px;overflow:auto;background:#09090b;border:1px solid #27272a;border-radius:8px;padding:16px;font-size:13px;line-height:1.5}button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.7}</style></head><body><main><h1>GitHub App created</h1><p>Add these values to .env, then restart API and worker.</p><div class="env-header"><h2>Environment variables</h2><button id="copy-env" type="button">Copy env</button></div><pre id="env-lines">${escaped}</pre><script>(()=>{const button=document.getElementById("copy-env");const env=document.getElementById("env-lines");async function copyText(text){if(navigator.clipboard&&window.isSecureContext){await navigator.clipboard.writeText(text);return;}const area=document.createElement("textarea");area.value=text;area.setAttribute("readonly","");area.style.position="fixed";area.style.inset="-9999px";document.body.append(area);area.select();document.execCommand("copy");area.remove();}button?.addEventListener("click",async()=>{try{await copyText(env?.textContent||"");button.textContent="Copied";setTimeout(()=>button.textContent="Copy env",1600);}catch{button.textContent="Copy failed";setTimeout(()=>button.textContent="Copy env",2200);}});})();</script></main></body></html>`;
772
- }
773
-
774
- function githubSetupSuccessHtml(account: string, returnUrl: string): string {
775
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Connected</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;text-decoration:none}</style></head><body><main><h1>GitHub App connected</h1><p>${escapeHtml(account)} is now available to this OpenGeni workspace through an explicit repository allowlist.</p><a class="button" href="${escapeHtml(returnUrl)}">Back to OpenGeni</a></main></body></html>`;
776
- }
777
-
778
- function githubInstallationChooserHtml(
779
- candidates: GitHubInstallationBindingCandidate[],
780
- state: string,
781
- workspaceId: string,
782
- baseUrl: string,
783
- ): string {
784
- const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
785
- const options = candidates
786
- .map(({ installation, authorityKind }) => {
787
- const account = escapeHtml(
788
- installation.accountLogin ?? `installation ${installation.installationId}`,
789
- );
790
- const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
791
- return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required><span><strong>${account}</strong><small>${label}</small></span></label>`;
792
- })
793
- .join("");
794
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose GitHub installation</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:12px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px}p{margin:0 0 18px;color:#d4d4d8}.options{display:grid;gap:8px;margin-bottom:18px}.option{display:flex;align-items:center;gap:12px;border:1px solid #3f3f46;border-radius:8px;padding:12px;cursor:pointer}.option span{display:grid;gap:2px}.option small{color:#a1a1aa}button{min-height:38px;border-radius:7px;border:1px solid #3f3f46;padding:0 14px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}.secondary{margin-left:8px;background:transparent;color:#f4f4f5}</style></head><body><main><h1>Choose a GitHub account</h1><p>Only installations where GitHub proved you are the personal owner or an active organization owner are shown.</p><form method="get" action="${escapeHtml(action)}"><input type="hidden" name="state" value="${escapeHtml(state)}"><div class="options">${options}</div><button type="submit">Connect selected</button><button class="secondary" type="submit" name="installation_id" value="new" formnovalidate>Install on another account</button></form></main></body></html>`;
795
- }
796
-
797
- function githubSetupPendingHtml(): string {
798
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Requested</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0;color:#d4d4d8}</style></head><body><main><h1>GitHub App request sent</h1><p>A GitHub organization owner must approve the installation. OpenGeni has not created a workspace binding.</p></main></body></html>`;
799
- }
800
-
801
- function isConsistentGitHubBindingCandidates(
802
- candidates: GitHubInstallationBindingCandidate[],
803
- ): boolean {
804
- const ids = new Set<number>();
805
- return candidates.every(({ installation, authorityKind }) => {
806
- if (
807
- !Number.isSafeInteger(installation.installationId) ||
808
- installation.installationId <= 0 ||
809
- !Number.isSafeInteger(installation.accountId) ||
810
- installation.accountId <= 0 ||
811
- !installation.accountLogin?.trim() ||
812
- installation.suspended ||
813
- ids.has(installation.installationId)
814
- ) {
815
- return false;
816
- }
817
- ids.add(installation.installationId);
818
- return authorityKind === "personal_owner"
819
- ? installation.accountType === "User"
820
- : installation.accountType === "Organization";
821
- });
822
- }
823
-
824
1123
  function parsePositiveInteger(value: string | undefined | null): number | null {
825
1124
  if (!value || !/^\d+$/.test(value)) {
826
1125
  return null;
@@ -834,55 +1133,6 @@ function isFreshGitHubBindingState(payload: GitHubSignedStatePayload): boolean {
834
1133
  return age >= 0 && age < githubBindingStateMaxAgeSeconds;
835
1134
  }
836
1135
 
837
- function isConsistentGitHubBindingProof(
838
- proof: GitHubInstallationBindingProof,
839
- installationId: number,
840
- ): boolean {
841
- const installation = proof.installation;
842
- if (
843
- installation.installationId !== installationId ||
844
- !Number.isSafeInteger(installation.accountId) ||
845
- installation.accountId <= 0 ||
846
- !installation.accountLogin?.trim() ||
847
- installation.suspended ||
848
- !Number.isSafeInteger(proof.actorId) ||
849
- proof.actorId <= 0 ||
850
- !proof.actorLogin.trim() ||
851
- proof.repositories.length === 0
852
- ) {
853
- return false;
854
- }
855
- if (
856
- proof.authorityKind === "personal_owner"
857
- ? installation.accountType !== "User" || proof.actorId !== installation.accountId
858
- : installation.accountType !== "Organization"
859
- ) {
860
- return false;
861
- }
862
- return proof.repositories.every(
863
- (repository) =>
864
- Number.isSafeInteger(repository.id) &&
865
- repository.id > 0 &&
866
- repository.installationId === installationId &&
867
- repository.accountLogin === installation.accountLogin &&
868
- repository.accountType === installation.accountType,
869
- );
870
- }
871
-
872
- function escapeHtml(value: string): string {
873
- return value.replace(
874
- /[&<>"']/g,
875
- (char) =>
876
- ({
877
- "&": "&amp;",
878
- "<": "&lt;",
879
- ">": "&gt;",
880
- '"': "&quot;",
881
- "'": "&#39;",
882
- })[char] ?? char,
883
- );
884
- }
885
-
886
1136
  function openGeniReturnUrl(
887
1137
  settings: ApiRouteDeps["settings"],
888
1138
  c: Context,
@@ -904,3 +1154,55 @@ function openGeniReturnUrl(
904
1154
  function openGeniBaseUrl(settings: ApiRouteDeps["settings"], c: Context): string {
905
1155
  return githubBrowserBaseUrl(settings, new URL(c.req.url).origin);
906
1156
  }
1157
+
1158
+ /** The status the app error handler gives this failure; the page keeps it. */
1159
+ function githubBrowserFailureStatus(error: unknown, handledStatus: number | undefined): number {
1160
+ if (error instanceof HTTPException) return error.status;
1161
+ if (error instanceof OrganizationIntegrationDeniedError) return 403;
1162
+ // An unexpected fault keeps whatever the error handler answered (500 by default).
1163
+ return handledStatus ?? 500;
1164
+ }
1165
+
1166
+ function githubBrowserFailureKind(error: unknown): GitHubConnectFailure {
1167
+ if (error instanceof OrganizationIntegrationDeniedError) return "policy_denied";
1168
+ if (!(error instanceof HTTPException)) return "failed";
1169
+ if (error instanceof GitHubBrowserFailure) return error.failure;
1170
+ if (error.status === 401) return "signed_out";
1171
+ if (error.status === 403) return "forbidden";
1172
+ // Every signed-state rejection names its state; they all mean "start again".
1173
+ if (error.status === 400 && /\bstate\b/iu.test(error.message)) return "expired";
1174
+ return "failed";
1175
+ }
1176
+
1177
+ /**
1178
+ * Human-authored HTTP messages only; configuration errors carry a JSON
1179
+ * envelope. Anything else (an unexpected fault) shows no detail.
1180
+ */
1181
+ function githubBrowserFailureDetail(error: unknown): string | null {
1182
+ if (!(error instanceof HTTPException)) return null;
1183
+ if (error instanceof GitHubBrowserFailure && error.failure === "cancelled") return null;
1184
+ try {
1185
+ const parsed = JSON.parse(error.message) as { message?: unknown };
1186
+ return typeof parsed?.message === "string" ? parsed.message : null;
1187
+ } catch {
1188
+ return error.message || null;
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * The failure page links back to the workspace integrations page when the
1194
+ * request names a workspace (its path, or a correctly signed state even if it
1195
+ * aged out); otherwise to the OpenGeni home. This is only a link target, so an
1196
+ * expired state is acceptable evidence of where the user came from.
1197
+ */
1198
+ function githubFailureReturnUrl(deps: ApiRouteDeps, c: Context): string {
1199
+ const baseUrl = (
1200
+ deps.settings.webBaseUrl ??
1201
+ (openGeniBaseUrl(deps.settings, c) || new URL(c.req.url).origin)
1202
+ ).replace(/\/+$/u, "");
1203
+ const rawState = c.req.query("state");
1204
+ const candidate =
1205
+ c.req.param("workspaceId") ??
1206
+ (rawState ? inspectSignedState(rawState, deps.githubStateSecret)?.workspaceId : undefined);
1207
+ return `${baseUrl}${workspaceIntegrationsPathForUntrusted(candidate) ?? "/"}`;
1208
+ }