@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,39 @@
1
+ import {
2
+ withOrganizationIntegrationAcquisition,
3
+ withOrganizationIntegrationPolicyFence,
4
+ } from "@opengeni/db/organization-integration-policy";
5
+ import {
6
+ claimOAuthAcquisition,
7
+ finishOAuthAcquisition,
8
+ integrationSourceSelectionRequiresAcquisition,
9
+ } from "./oauth-client";
10
+ import {
11
+ scheduledTaskKnowledgeSource,
12
+ requireScheduledTaskKnowledgeSource,
13
+ knowledgeSourceAgentConfig,
14
+ assertOrganizationIntegrationAllowed,
15
+ } from "@opengeni/contracts";
1
16
  import { createHash, randomBytes } from "node:crypto";
2
- import type { Settings } from "@opengeni/config";
17
+ import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
18
+ import { requireConnectOwnerAuthority } from "./connect-authority";
19
+ import {
20
+ configuredGoogleDriveSyncLimits,
21
+ googleDriveOAuthCallbackUrl,
22
+ type Settings,
23
+ } from "@opengeni/config";
3
24
  import type { AccessGrant, ScheduledTask, ScheduledTaskScheduleSpec } from "@opengeni/contracts";
4
25
  import {
5
26
  bindConnectorDocumentDestination,
27
+ type ConnectorDocumentDestination,
6
28
  type ConnectorDocumentDestinationSelection,
7
29
  } from "@opengeni/contracts/connector-destinations";
8
30
  import {
9
31
  GOOGLE_DRIVE_CREDENTIAL_LABEL,
10
32
  GOOGLE_DRIVE_CREDENTIAL_ROLE,
33
+ GOOGLE_DRIVE_FILE_SCOPE,
34
+ GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
35
+ GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
36
+ GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
11
37
  GOOGLE_DRIVE_PROVIDER_DOMAIN,
12
38
  GOOGLE_DRIVE_READONLY_SCOPE,
13
39
  GoogleDriveKnowledgeSourceConfig,
@@ -16,6 +42,7 @@ import {
16
42
  GoogleDriveConnectionLifecycle,
17
43
  GoogleDriveConnectionMetadata,
18
44
  GoogleDriveOAuthStartResponse,
45
+ GoogleDriveOutputDestination,
19
46
  SaveGoogleDriveIntegrationSourceRequest,
20
47
  SaveGoogleDriveSourceRequest,
21
48
  googleDriveOAuthScopeDecision,
@@ -31,12 +58,12 @@ import {
31
58
  } from "@opengeni/contracts";
32
59
  import {
33
60
  GOOGLE_DRIVE_INTEGRATION_DEFINITION,
61
+ CORE_INTEGRATION_DEFINITIONS,
34
62
  integrationDefinitionProviderDomain,
35
63
  } from "@opengeni/capabilities";
36
64
  import {
37
65
  captureScheduledTaskRestoreState,
38
66
  createValidatedScheduledTask,
39
- hasPermission,
40
67
  manualScheduledTaskTriggerWorkflowId,
41
68
  requireEnvironmentEncryption,
42
69
  syncCreatedScheduledTask,
@@ -46,27 +73,34 @@ import type { ApiRouteDeps } from "@opengeni/core";
46
73
  import {
47
74
  buildConnectionTokenResolver,
48
75
  configureIntegrationFacet,
76
+ integrationFacetConfigureRequestDigest,
49
77
  appendKnowledgeSourceAclVersion,
50
78
  deauthorizeKnowledgeSourceRetrieval,
51
79
  ConnectionDisconnectGenerationError,
52
80
  ConnectionDisconnectIdempotencyError,
53
81
  consumeIntegrationOAuthStateNonce,
82
+ finishConnectOperation,
83
+ getConnectAttempt,
84
+ type Database,
85
+ listInstalledApiIntegrations,
54
86
  createConnection,
55
87
  decryptEnvironmentValue,
56
88
  disconnectConnectionIdempotently,
57
89
  encryptEnvironmentValue,
90
+ ensureConnectorActionPolicyDefault,
58
91
  getConnectionMetadata,
59
92
  getKnowledgeSourceByExternalIdentityForSyncAuthority,
60
93
  getKnowledgeSourceForSyncAuthority,
61
- getWorkspaceGrant,
62
94
  listKnowledgeSourceSyncTasksForConnection,
63
95
  loadConnectionCredentialForBroker,
64
96
  listIntegrationInstanceFacets,
97
+ replayCompletedIntegrationFacetOperation,
65
98
  transitionConnectionState,
66
99
  updateConnection,
67
100
  updateScheduledTask,
68
101
  upsertKnowledgeProvider,
69
102
  upsertKnowledgeSource,
103
+ withWorkspaceSubjectRls,
70
104
  recordKnowledgeLifecycleEvent,
71
105
  type PermanentConnectionRefreshFailure,
72
106
  } from "@opengeni/db";
@@ -74,8 +108,15 @@ import { googleDriveKnowledgeSourceIdentity } from "@opengeni/documents/google-d
74
108
  import { createSignedState, readSignedState } from "@opengeni/github";
75
109
  import { readResponseJsonBounded, type FetchLike } from "@opengeni/network";
76
110
  import { HTTPException } from "hono/http-exception";
111
+ import {
112
+ personalOnlyConnectionPrincipalMessage,
113
+ personalOwnerStateAccepted,
114
+ personalOwnerVerifiedInState,
115
+ PERSONAL_OWNER_VERIFIED_STATE_CLAIM,
116
+ } from "../connection-ownership";
77
117
  import {
78
118
  integrationBaseUrl,
119
+ oauthStateFailureReturn,
79
120
  oauthStateTtlMs,
80
121
  requireIntegrationsStateSecret,
81
122
  } from "./oauth-client";
@@ -88,6 +129,17 @@ const GOOGLE_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
88
129
  const GOOGLE_REQUEST_TIMEOUT_MS = 10_000;
89
130
  const GOOGLE_DRIVE_PAGE_SIZE = 100;
90
131
  const GOOGLE_DRIVE_RETURN_PATH = (workspaceId: string) => `/workspaces/${workspaceId}/capabilities`;
132
+ /**
133
+ * Flow discriminator for this connector's signed OAuth state.
134
+ *
135
+ * This state carries no `ownership` field and no provider identity - it is
136
+ * personal-only by construction - so the `personalOwnerVerified` claim would
137
+ * otherwise be its sole ownership gate. Its return path is also byte-identical
138
+ * to the MCP OAuth start's, which signs that path from caller input, so a
139
+ * caller's own MCP state could be presented here. Requiring an exact flow kind
140
+ * binds a state to the flow that minted it.
141
+ */
142
+ const GOOGLE_DRIVE_OAUTH_STATE_KIND = "google_drive_oauth";
91
143
  const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
92
144
  "appNotAuthorizedToFile",
93
145
  "authError",
@@ -96,11 +148,16 @@ const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
96
148
  ]);
97
149
 
98
150
  type GoogleDriveOAuthState = {
151
+ connectAttemptId?: string;
152
+ externalContinuation?: ExternalActorContinuation;
99
153
  accountId: string;
100
154
  workspaceId: string;
101
155
  subjectId: string;
156
+ /** Signed proof that a live managed human started this personal-only flow. */
157
+ personalOwnerVerified: boolean;
102
158
  returnPath: string;
103
159
  encryptedPkceVerifier: string;
160
+ capability: "source_read" | "publish";
104
161
  connectionId?: string;
105
162
  connectionVersion?: number;
106
163
  nonce: string;
@@ -148,7 +205,7 @@ export async function wakeGoogleDriveSourcesFromWorkspaceEvent(
148
205
  );
149
206
  let triggered = 0;
150
207
  for (const task of tasks) {
151
- if (task.action.kind !== "knowledge_source_sync" || task.status !== "active") continue;
208
+ if (!scheduledTaskKnowledgeSource(task) || task.status !== "active") continue;
152
209
  const externalSourceId =
153
210
  typeof task.metadata.externalSourceId === "string" ? task.metadata.externalSourceId : null;
154
211
  const selectedSource = externalSourceId ? selectedById.get(externalSourceId) : null;
@@ -205,8 +262,11 @@ export async function startGoogleDriveOAuth(
205
262
  subjectId: string;
206
263
  requestUrl: string;
207
264
  payload: GoogleDriveOAuthStartRequest;
265
+ connectAttemptId?: string;
266
+ externalContinuation?: ExternalActorContinuation;
208
267
  },
209
268
  ): Promise<GoogleDriveOAuthStartResponse> {
269
+ await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
210
270
  const google = requireGoogleDriveSettings(deps.settings);
211
271
  const existing = input.payload.connectionId
212
272
  ? await getConnectionMetadata(
@@ -219,30 +279,60 @@ export async function startGoogleDriveOAuth(
219
279
  if (input.payload.connectionId && !existing) {
220
280
  throw new HTTPException(404, { message: "Google Drive connection not found" });
221
281
  }
282
+ if (input.payload.capability === "publish" && !existing) {
283
+ throw new HTTPException(409, {
284
+ message: "Connect Google Drive for source access before enabling publishing",
285
+ });
286
+ }
222
287
  if (existing) {
223
288
  requireGoogleDriveConnection(existing, input.subjectId);
224
289
  }
225
290
 
226
291
  const key = requireEnvironmentEncryption(deps.settings);
227
292
  const verifier = randomBytes(48).toString("base64url");
228
- const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
229
- const redirectUri = `${baseUrl}/v1/integrations/google-drive/callback`;
293
+ const redirectUri = requireGoogleDriveOAuthCallbackUrl(deps.settings);
230
294
  const state = createSignedState(requireIntegrationsStateSecret(deps.settings), {
231
295
  accountId: input.accountId,
232
296
  workspaceId: input.workspaceId,
233
297
  subjectId: input.subjectId,
298
+ kind: GOOGLE_DRIVE_OAUTH_STATE_KIND,
299
+ ...(input.connectAttemptId ? { connectAttemptId: input.connectAttemptId } : {}),
300
+ ...(input.externalContinuation
301
+ ? {
302
+ encryptedExternalContinuation: encryptEnvironmentValue(
303
+ key,
304
+ JSON.stringify(ExternalActorContinuation.parse(input.externalContinuation)),
305
+ ),
306
+ }
307
+ : {}),
308
+ // The route admits only a managed human, so reaching here is the proof; the
309
+ // callback has no live principal and enforces exactly this claim.
310
+ [PERSONAL_OWNER_VERIFIED_STATE_CLAIM]: true,
234
311
  returnPath: GOOGLE_DRIVE_RETURN_PATH(input.workspaceId),
235
312
  encryptedPkceVerifier: encryptEnvironmentValue(key, verifier),
313
+ capability: input.payload.capability,
236
314
  ...(existing ? { connectionId: existing.id, connectionVersion: existing.version } : {}),
237
315
  });
238
316
  const authorizationUrl = new URL(GOOGLE_AUTHORIZATION_URL);
239
317
  authorizationUrl.searchParams.set("client_id", google.clientId);
240
318
  authorizationUrl.searchParams.set("redirect_uri", redirectUri);
241
319
  authorizationUrl.searchParams.set("response_type", "code");
242
- authorizationUrl.searchParams.set("scope", GOOGLE_DRIVE_READONLY_SCOPE);
320
+ authorizationUrl.searchParams.set(
321
+ "scope",
322
+ input.payload.capability === "publish" ? GOOGLE_DRIVE_FILE_SCOPE : GOOGLE_DRIVE_READONLY_SCOPE,
323
+ );
243
324
  authorizationUrl.searchParams.set("access_type", "offline");
244
325
  authorizationUrl.searchParams.set("include_granted_scopes", "true");
245
- authorizationUrl.searchParams.set("prompt", "consent select_account");
326
+ authorizationUrl.searchParams.set(
327
+ "prompt",
328
+ input.payload.capability === "publish" ? "consent" : "consent select_account",
329
+ );
330
+ if (input.payload.capability === "publish") {
331
+ authorizationUrl.searchParams.set("trigger_onepick", "true");
332
+ authorizationUrl.searchParams.set("allow_folder_selection", "true");
333
+ authorizationUrl.searchParams.set("allow_multiple", "false");
334
+ authorizationUrl.searchParams.set("mimetypes", GOOGLE_DRIVE_FOLDER_MIME_TYPE);
335
+ }
246
336
  authorizationUrl.searchParams.set("state", state);
247
337
  authorizationUrl.searchParams.set("code_challenge_method", "S256");
248
338
  authorizationUrl.searchParams.set(
@@ -261,14 +351,46 @@ export async function completeGoogleDriveOAuthCallback(
261
351
  code?: string | undefined;
262
352
  state?: string | undefined;
263
353
  error?: string | undefined;
354
+ pickedFileIds?: string | undefined;
264
355
  requestUrl: string;
265
356
  },
266
- ): Promise<{ redirectTo: string }> {
357
+ ): Promise<{ redirectTo: string; exactReturn?: boolean }> {
358
+ const redirectUri = requireGoogleDriveOAuthCallbackUrl(deps.settings);
267
359
  const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
268
360
  const returnBaseUrl = deps.settings.webBaseUrl?.replace(/\/+$/, "") ?? baseUrl;
269
361
  let state: GoogleDriveOAuthState | null = null;
362
+ let exactReturnUrl: string | undefined;
363
+ let operation: { attemptId: string; operationId: string; inputDigest: string } | undefined;
270
364
  try {
271
365
  state = readGoogleDriveOAuthState(input.state, deps.settings);
366
+ if (state.connectAttemptId) {
367
+ const stored = await getConnectAttempt(deps.db, state, state.connectAttemptId);
368
+ if (
369
+ stored.attempt.providerId !==
370
+ (state.capability === "publish" ? "google-drive-publish" : "google-drive-knowledge") ||
371
+ stored.attempt.ownership !== "personal"
372
+ )
373
+ throw new GoogleDriveCallbackError("connection_conflict");
374
+ exactReturnUrl = stored.returnUrl;
375
+ operation = {
376
+ attemptId: state.connectAttemptId,
377
+ operationId: `oauth:${state.nonce}`,
378
+ inputDigest: createHash("sha256").update(input.state!).digest("hex"),
379
+ };
380
+ const claim = await claimOAuthAcquisition(
381
+ deps.db,
382
+ state,
383
+ {
384
+ ...operation,
385
+ expectedRevision: stored.attempt.revision,
386
+ authorize: (tx, _attempt, origin) =>
387
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
388
+ },
389
+ "google-drive",
390
+ Boolean(input.code && !input.error),
391
+ );
392
+ if (claim.status === "replayed") return { redirectTo: exactReturnUrl, exactReturn: true };
393
+ }
272
394
  await requireGoogleDriveCallbackGrant(deps, state);
273
395
  const consumed = await consumeIntegrationOAuthStateNonce(deps.db, {
274
396
  accountId: state.accountId,
@@ -279,7 +401,26 @@ export async function completeGoogleDriveOAuthCallback(
279
401
  now: new Date(),
280
402
  });
281
403
  if (!consumed) {
282
- throw new HTTPException(400, { message: "Google Drive OAuth state has already been used" });
404
+ throw new GoogleDriveCallbackError("state_replayed");
405
+ }
406
+ if (operation && (input.error || !input.code)) {
407
+ await finishConnectOperation(deps.db, state, {
408
+ ...operation,
409
+ authorize: (tx, _attempt, origin) =>
410
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
411
+ commit: async (_tx, current) => ({
412
+ ...current,
413
+ revision: current.revision + 1,
414
+ state: input.error === "access_denied" ? "cancelled" : "failed",
415
+ nextAction: { type: "none" },
416
+ error: {
417
+ code: input.error ? "provider_denied" : "missing_code",
418
+ message: "Authorization was not completed. Start a new connection attempt.",
419
+ retryable: false,
420
+ },
421
+ }),
422
+ });
423
+ return { redirectTo: exactReturnUrl!, exactReturn: true };
283
424
  }
284
425
  if (input.error) {
285
426
  throw new GoogleDriveCallbackError("provider_denied");
@@ -287,11 +428,11 @@ export async function completeGoogleDriveOAuthCallback(
287
428
  if (!input.code) {
288
429
  throw new GoogleDriveCallbackError("missing_code");
289
430
  }
431
+ await withOrganizationIntegrationAcquisition(deps.db, state, ["google-drive"], async () => {});
290
432
 
291
433
  const google = requireGoogleDriveSettings(deps.settings);
292
434
  const key = requireEnvironmentEncryption(deps.settings);
293
435
  const verifier = decryptEnvironmentValue(key, state.encryptedPkceVerifier);
294
- const redirectUri = `${baseUrl}/v1/integrations/google-drive/callback`;
295
436
  const fetchImpl = deps.googleDriveFetch ?? fetch;
296
437
  const token = await exchangeGoogleAuthorizationCode(
297
438
  {
@@ -303,10 +444,15 @@ export async function completeGoogleDriveOAuthCallback(
303
444
  },
304
445
  fetchImpl,
305
446
  );
306
- const scopeDecision = googleDriveOAuthScopeDecision(token.scopes);
447
+ const grantedScopes = [...new Set(token.scopes)].sort();
448
+ const scopeDecision = googleDriveOAuthScopeDecision(grantedScopes);
449
+ const requiredCapability =
450
+ state.capability === "publish" ? "publish_file" : "recursive_source_sync";
307
451
  if (
308
- scopeDecision.accessMode !== "readonly" ||
309
- !scopeDecision.capabilities.includes("recursive_source_sync")
452
+ !scopeDecision.accessMode ||
453
+ !scopeDecision.capabilities.includes(requiredCapability) ||
454
+ (state.capability === "publish" &&
455
+ !scopeDecision.capabilities.includes("recursive_source_sync"))
310
456
  ) {
311
457
  throw new GoogleDriveCallbackError("scope_not_granted");
312
458
  }
@@ -331,6 +477,17 @@ export async function completeGoogleDriveOAuthCallback(
331
477
  const previousMetadata = existing
332
478
  ? GoogleDriveConnectionMetadata.parse(existing.metadata)
333
479
  : null;
480
+ const outputDestination =
481
+ state.capability === "publish"
482
+ ? await verifyPickedGoogleDriveOutputDestination(
483
+ token.accessToken,
484
+ input.pickedFileIds,
485
+ fetchImpl,
486
+ )
487
+ : previousMetadata?.outputDestination;
488
+ if (state.capability === "publish") {
489
+ if (!existing) throw new GoogleDriveCallbackError("connection_conflict");
490
+ }
334
491
  let refreshToken = token.refreshToken;
335
492
  if (!refreshToken && existing) {
336
493
  const previousCredential = await loadConnectionCredentialForBroker(deps.db, deps.settings, {
@@ -353,7 +510,7 @@ export async function completeGoogleDriveOAuthCallback(
353
510
  refresh_token: refreshToken,
354
511
  token_type: token.tokenType,
355
512
  ...(token.expiresAt ? { expires_at: token.expiresAt.toISOString() } : {}),
356
- scope: token.scopes.join(" "),
513
+ scope: grantedScopes.join(" "),
357
514
  token_endpoint: GOOGLE_TOKEN_URL,
358
515
  client_id: google.clientId,
359
516
  client_secret: google.clientSecret,
@@ -369,6 +526,7 @@ export async function completeGoogleDriveOAuthCallback(
369
526
  verifiedAt: new Date().toISOString(),
370
527
  accessMode: scopeDecision.accessMode,
371
528
  lifecycle: googleDriveLifecycle("active"),
529
+ ...(outputDestination ? { outputDestination } : {}),
372
530
  ...(previousMetadata?.documentDestination
373
531
  ? { documentDestination: previousMetadata.documentDestination }
374
532
  : {}),
@@ -378,34 +536,92 @@ export async function completeGoogleDriveOAuthCallback(
378
536
  ? { selectedSources: [previousMetadata.selectedSource] }
379
537
  : {}),
380
538
  });
381
- const connection = existing
382
- ? await updateConnection(deps.db, {
383
- workspaceId: state.workspaceId,
384
- connectionId: existing.id,
385
- visibleToSubjectId: state.subjectId,
386
- expectedVersion: existing.version,
387
- subjectId: state.subjectId,
388
- providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
389
- kind: "oauth2",
390
- status: "active",
391
- credentialEncrypted,
392
- grantedScopes: token.scopes,
393
- expiresAt: token.expiresAt,
394
- metadata,
395
- updatedBySubjectId: state.subjectId,
396
- })
397
- : await createConnection(deps.db, {
539
+ const acceptedState = state;
540
+ const persist = async (tx: Database) => {
541
+ // Capture the validated immutable callback state for delayed persistence.
542
+ // eslint-disable-next-line no-shadow
543
+ const state = acceptedState;
544
+ if (state.capability === "publish" && existing)
545
+ await ensureConnectorActionPolicyDefault(tx, {
398
546
  accountId: state.accountId,
399
547
  workspaceId: state.workspaceId,
400
548
  subjectId: state.subjectId,
401
- providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
402
- kind: "oauth2",
403
- credentialEncrypted,
404
- grantedScopes: token.scopes,
405
- expiresAt: token.expiresAt,
406
- metadata,
407
- createdBySubjectId: state.subjectId,
549
+ connectionId: existing.id,
550
+ serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
551
+ toolName: GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
552
+ actionName: GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
553
+ policy: "ask",
408
554
  });
555
+ return existing
556
+ ? updateConnection(tx, {
557
+ workspaceId: state.workspaceId,
558
+ connectionId: existing.id,
559
+ visibleToSubjectId: state.subjectId,
560
+ expectedVersion: existing.version,
561
+ subjectId: state.subjectId,
562
+ providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
563
+ kind: "oauth2",
564
+ status: "active",
565
+ credentialEncrypted,
566
+ grantedScopes,
567
+ expiresAt: token.expiresAt,
568
+ metadata,
569
+ updatedBySubjectId: state.subjectId,
570
+ })
571
+ : createConnection(tx, {
572
+ accountId: state.accountId,
573
+ workspaceId: state.workspaceId,
574
+ subjectId: state.subjectId,
575
+ providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
576
+ kind: "oauth2",
577
+ credentialEncrypted,
578
+ grantedScopes,
579
+ expiresAt: token.expiresAt,
580
+ metadata,
581
+ createdBySubjectId: state.subjectId,
582
+ });
583
+ };
584
+ if (operation) {
585
+ await finishOAuthAcquisition(
586
+ deps.db,
587
+ acceptedState,
588
+ {
589
+ ...operation,
590
+ authorize: (tx, _attempt, origin) =>
591
+ requireConnectOwnerAuthority(tx, acceptedState, "connections:write", origin),
592
+ commit: async (tx, current) => {
593
+ const connection = await persist(tx);
594
+ if (!connection) throw new GoogleDriveCallbackError("connection_conflict");
595
+ return {
596
+ ...current,
597
+ revision: current.revision + 1,
598
+ state: "complete",
599
+ credentialsCommitted: true,
600
+ nextAction: { type: "none" },
601
+ account: {
602
+ id: connection.id,
603
+ version: connection.version,
604
+ providerId: current.providerId,
605
+ label: identity.emailAddress ?? identity.displayName,
606
+ ownership: "personal",
607
+ status: "connected",
608
+ },
609
+ };
610
+ },
611
+ },
612
+ "google-drive",
613
+ );
614
+ return { redirectTo: exactReturnUrl!, exactReturn: true };
615
+ }
616
+ const connection = await withOrganizationIntegrationAcquisition(
617
+ deps.db,
618
+ acceptedState,
619
+ ["google-drive"],
620
+ async (tx) => {
621
+ await requireGoogleDriveCallbackGrant({ ...deps, db: tx }, acceptedState);
622
+ return persist(tx);
623
+ },
624
+ );
409
625
  if (!connection) {
410
626
  throw new GoogleDriveCallbackError("connection_conflict");
411
627
  }
@@ -413,12 +629,16 @@ export async function completeGoogleDriveOAuthCallback(
413
629
  redirectTo: googleDriveReturnUrl(returnBaseUrl, state.returnPath, "connected", connection.id),
414
630
  };
415
631
  } catch (error) {
632
+ if (exactReturnUrl) return { redirectTo: exactReturnUrl, exactReturn: true };
633
+ // Reading the state is the first step, so no state means the link itself
634
+ // was unusable: say whether it expired rather than blame configuration.
635
+ const failure = state ? null : oauthStateFailureReturn(deps.settings, input.state);
416
636
  return {
417
637
  redirectTo: googleDriveReturnUrl(
418
638
  returnBaseUrl,
419
- state?.returnPath ?? "/integrations",
639
+ state?.returnPath ?? failure!.returnPath,
420
640
  "error",
421
- googleDriveErrorReason(error),
641
+ failure?.reason ?? googleDriveErrorReason(error),
422
642
  ),
423
643
  };
424
644
  }
@@ -495,19 +715,45 @@ export async function transitionGoogleDriveLifecycle(
495
715
  );
496
716
  }
497
717
 
498
- const updated = await transitionConnectionState(deps.db, {
499
- workspaceId: input.workspaceId,
500
- connectionId: existing.id,
501
- visibleToSubjectId: input.subjectId,
502
- expectedVersion: existing.version,
503
- status: "active",
504
- metadata: GoogleDriveConnectionMetadata.parse({
505
- ...metadata,
506
- lifecycle: googleDriveLifecycle(targetState),
507
- }),
508
- lastError: null,
509
- updatedBySubjectId: input.subjectId,
510
- });
718
+ const persist = (tx: Database) =>
719
+ transitionConnectionState(tx, {
720
+ workspaceId: input.workspaceId,
721
+ connectionId: existing.id,
722
+ visibleToSubjectId: input.subjectId,
723
+ expectedVersion: existing.version,
724
+ status: "active",
725
+ metadata: GoogleDriveConnectionMetadata.parse({
726
+ ...metadata,
727
+ lifecycle: googleDriveLifecycle(targetState),
728
+ }),
729
+ lastError: null,
730
+ updatedBySubjectId: input.subjectId,
731
+ });
732
+ const updated =
733
+ input.payload.action === "resume"
734
+ ? await withOrganizationIntegrationPolicyFence(
735
+ deps.db,
736
+ { accountId: existing.accountId, workspaceId: input.workspaceId },
737
+ async (tx, policy) => {
738
+ const current = await getConnectionMetadata(
739
+ tx,
740
+ input.workspaceId,
741
+ input.connectionId,
742
+ input.subjectId,
743
+ );
744
+ if (
745
+ current?.status === "active" &&
746
+ effectiveGoogleDriveLifecycle(
747
+ current,
748
+ requireGoogleDriveConnection(current, input.subjectId),
749
+ ).state === "active"
750
+ )
751
+ return current;
752
+ assertOrganizationIntegrationAllowed(policy, "google-drive");
753
+ return persist(tx);
754
+ },
755
+ )
756
+ : await persist(deps.db);
511
757
  if (!updated) {
512
758
  const converged = await getConnectionMetadata(
513
759
  deps.db,
@@ -626,20 +872,20 @@ async function deauthorizeGoogleDriveConnectionSources(
626
872
  connection.workspaceId,
627
873
  connection.id,
628
874
  async (task) => {
629
- if (task.action.initiatingSubjectId !== subjectId) return;
875
+ if (requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== subjectId) return;
630
876
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
631
877
  accountId: task.accountId,
632
878
  workspaceId: task.workspaceId,
633
- sourceId: task.action.sourceId,
879
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
634
880
  initiatingSubjectId: subjectId,
635
881
  });
636
882
  if (!resolved || resolved.source.lifecycleState !== "active") return;
637
883
  await deauthorizeKnowledgeSourceRetrieval(deps.db, {
638
884
  accountId: task.accountId,
639
885
  workspaceId: task.workspaceId,
640
- sourceId: task.action.sourceId,
641
- audience: task.action.destination,
642
- operationId: `google-drive-deauthorize:${connection.id}:${authorityVersion}:${task.action.sourceId}:${reasonCode}`,
886
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
887
+ audience: requireScheduledTaskKnowledgeSource(task).destination,
888
+ operationId: `google-drive-deauthorize:${connection.id}:${authorityVersion}:${requireScheduledTaskKnowledgeSource(task).sourceId}:${reasonCode}`,
643
889
  reasonCode,
644
890
  actor: {
645
891
  kind: "human",
@@ -655,7 +901,7 @@ async function forEachGoogleDriveConnectionTask(
655
901
  deps: ApiRouteDeps,
656
902
  workspaceId: string,
657
903
  connectionId: string,
658
- fn: (task: ScheduledTask & { action: { kind: "knowledge_source_sync" } }) => Promise<void>,
904
+ fn: (task: ScheduledTask) => Promise<void>,
659
905
  ): Promise<void> {
660
906
  const tasks = await listKnowledgeSourceSyncTasksForConnection(deps.db, workspaceId, connectionId);
661
907
  for (const task of tasks) {
@@ -776,63 +1022,160 @@ export async function saveGoogleDriveFacetSource(
776
1022
  throw new HTTPException(400, { message: "invalid Google Drive source selection" });
777
1023
  }
778
1024
  const payload = parsedPayload.data;
779
- const context = await requireGoogleDriveIntegrationFacet(deps, input);
780
- const documentDestination = googleDriveDocumentDestination(input, payload);
781
- const verifiedSources = await verifyGoogleDriveSources(deps, {
782
- workspaceId: input.workspaceId,
783
- subjectId: input.subjectId,
784
- connectionId: context.connection.id,
785
- sources: payload.sources,
786
- });
787
- const config = GoogleDriveKnowledgeSourceConfig.parse({
788
- sources: verifiedSources.map((verified) => ({
789
- id: verified.id,
790
- name: verified.name,
791
- mimeType: verified.mimeType,
792
- ...(verified.driveId ? { driveId: verified.driveId } : {}),
793
- sourceKind:
794
- verified.id === "root"
795
- ? "my_drive"
796
- : verified.driveId === verified.id
797
- ? "shared_drive"
798
- : "folder",
799
- includeDescendants: true,
800
- })),
801
- destination: {
802
- authorityKind: documentDestination.authorityKind,
803
- authorityAccountId: documentDestination.authorityAccountId,
804
- ...(documentDestination.authorityWorkspaceId
805
- ? { authorityWorkspaceId: documentDestination.authorityWorkspaceId }
806
- : {}),
807
- ...(documentDestination.authoritySubjectId
808
- ? { authoritySubjectId: documentDestination.authoritySubjectId }
809
- : {}),
810
- ...(documentDestination.collectionId
811
- ? { collectionId: documentDestination.collectionId }
812
- : {}),
1025
+ const requestedSources = payload.sources.map((source) => ({
1026
+ ...source,
1027
+ id: validDriveId(source.id, "source.id"),
1028
+ }));
1029
+ if (new Set(requestedSources.map((source) => source.id)).size !== requestedSources.length) {
1030
+ throw new HTTPException(400, { message: "Google Drive sources must be unique" });
1031
+ }
1032
+ const requestedDestination = bindGoogleDriveDocumentDestination(input, payload);
1033
+ const requestedConfig = googleDriveFacetConfig(requestedSources, requestedDestination, payload);
1034
+ const admitted = await withOrganizationIntegrationPolicyFence(
1035
+ deps.db,
1036
+ input,
1037
+ async (tx, policy) => {
1038
+ const replayed =
1039
+ await replayCompletedIntegrationFacetOperation<IntegrationFacetMutationResult>(tx, {
1040
+ accountId: input.accountId,
1041
+ workspaceId: input.workspaceId,
1042
+ subjectId: input.subjectId,
1043
+ capabilityId: input.capabilityId,
1044
+ instanceKey: input.instanceKey,
1045
+ facetKey: input.facetKey,
1046
+ idempotencyKey: payload.idempotencyKey,
1047
+ kind: "configure",
1048
+ expectedRequestDigest: (result) => {
1049
+ const displayName = integrationFacetReceiptDisplayName(result);
1050
+ if (displayName === null) return "invalid-integration-facet-receipt";
1051
+ return integrationFacetConfigureRequestDigest({
1052
+ capabilityId: input.capabilityId,
1053
+ instanceKey: input.instanceKey,
1054
+ facetKey: input.facetKey,
1055
+ displayName,
1056
+ config: requestedConfig,
1057
+ ...(payload.expectedVersion !== undefined
1058
+ ? { expectedVersion: payload.expectedVersion }
1059
+ : {}),
1060
+ });
1061
+ },
1062
+ });
1063
+ if (replayed) return { replayed: IntegrationFacetMutationResult.parse(replayed) };
1064
+ const context = await requireGoogleDriveIntegrationFacet({ ...deps, db: tx }, input);
1065
+ googleDriveDocumentDestination(input, payload);
1066
+ const acquiring = googleDriveFacetSelectionRequiresAcquisition(context, requestedConfig);
1067
+ if (acquiring) {
1068
+ const installed = (
1069
+ await listInstalledApiIntegrations(tx, input.workspaceId, input.subjectId)
1070
+ ).find(
1071
+ (candidate) =>
1072
+ candidate.capabilityId === input.capabilityId &&
1073
+ candidate.instanceKey === input.instanceKey,
1074
+ );
1075
+ assertOrganizationIntegrationAllowed(
1076
+ policy,
1077
+ installed
1078
+ ? installedGoogleDriveFacetKey({
1079
+ apiProtocol: installed.protocol,
1080
+ definitionId: installed.definitionId,
1081
+ definitionProvenance: installed.definitionProvenance,
1082
+ })
1083
+ : null,
1084
+ );
1085
+ }
1086
+ return { context, acquiring };
813
1087
  },
814
- syncCadence: payload.syncCadence,
815
- readPolicy: payload.readPolicy,
816
- });
817
- return IntegrationFacetMutationResult.parse(
818
- await configureIntegrationFacet(deps.db, {
819
- accountId: input.accountId,
1088
+ );
1089
+ if (admitted.replayed) return admitted.replayed;
1090
+ const context = admitted.context!;
1091
+ if (admitted.acquiring)
1092
+ await verifyGoogleDriveSources(deps, {
820
1093
  workspaceId: input.workspaceId,
821
1094
  subjectId: input.subjectId,
822
- capabilityId: input.capabilityId,
823
- instanceKey: input.instanceKey,
824
- facetKey: input.facetKey,
825
- displayName:
826
- context.facet.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
827
- config,
828
- ...(payload.expectedVersion !== undefined
829
- ? { expectedVersion: payload.expectedVersion }
830
- : {}),
831
- idempotencyKey: payload.idempotencyKey,
832
- }),
1095
+ connectionId: context.connection.id,
1096
+ sources: requestedSources,
1097
+ });
1098
+ return IntegrationFacetMutationResult.parse(
1099
+ await withOrganizationIntegrationPolicyFence(deps.db, input, (tx, policy) =>
1100
+ configureIntegrationFacet(tx, {
1101
+ accountId: input.accountId,
1102
+ workspaceId: input.workspaceId,
1103
+ subjectId: input.subjectId,
1104
+ capabilityId: input.capabilityId,
1105
+ instanceKey: input.instanceKey,
1106
+ facetKey: input.facetKey,
1107
+ displayName:
1108
+ context.facet.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
1109
+ config: requestedConfig,
1110
+ ...(payload.expectedVersion !== undefined
1111
+ ? { expectedVersion: payload.expectedVersion }
1112
+ : {}),
1113
+ idempotencyKey: payload.idempotencyKey,
1114
+ beforeAcquire: async (locked, installed) => {
1115
+ const current = await requireGoogleDriveIntegrationFacet({ ...deps, db: locked }, input);
1116
+ if (googleDriveFacetSelectionRequiresAcquisition(current, requestedConfig)) {
1117
+ assertOrganizationIntegrationAllowed(policy, installedGoogleDriveFacetKey(installed));
1118
+ if (!admitted.acquiring)
1119
+ throw new HTTPException(409, {
1120
+ message: "Google Drive source binding changed; verify the selection again",
1121
+ });
1122
+ }
1123
+ },
1124
+ }),
1125
+ ),
1126
+ );
1127
+ }
1128
+
1129
+ /** This is installed server-authored version provenance, not a facet's mutable
1130
+ * capabilities.provider, URL, domain, or client-authored credential metadata. */
1131
+ function installedGoogleDriveFacetKey(context: {
1132
+ apiProtocol: string | null;
1133
+ definitionId: string | null;
1134
+ definitionProvenance: string | null;
1135
+ }): string | null {
1136
+ if (context.apiProtocol !== "openapi" && context.apiProtocol !== "graphql") return null;
1137
+ if (context.definitionProvenance === "workspace") return `custom:${context.apiProtocol}`;
1138
+ return context.definitionProvenance === "curated" &&
1139
+ CORE_INTEGRATION_DEFINITIONS.some((definition) => definition.id === context.definitionId)
1140
+ ? context.definitionId
1141
+ : null;
1142
+ }
1143
+
1144
+ function googleDriveFacetSelectionRequiresAcquisition(
1145
+ context: Awaited<ReturnType<typeof requireGoogleDriveIntegrationFacet>>,
1146
+ requested: ReturnType<typeof googleDriveFacetConfig>,
1147
+ ): boolean {
1148
+ const binding = context.facet.binding;
1149
+ if (
1150
+ !binding ||
1151
+ binding.status !== "active" ||
1152
+ !binding.directlyOwned ||
1153
+ binding.connectionId !== context.connection.id
1154
+ )
1155
+ return true;
1156
+ const previous = GoogleDriveKnowledgeSourceConfig.safeParse(binding.config);
1157
+ if (!previous.success) return true;
1158
+ const selection = (config: ReturnType<typeof googleDriveFacetConfig>) =>
1159
+ config.sources.map((source) => ({
1160
+ ...source,
1161
+ destination: config.destination,
1162
+ syncCadence: config.syncCadence,
1163
+ syncEnabled: true,
1164
+ readPolicy: config.readPolicy,
1165
+ }));
1166
+ return integrationSourceSelectionRequiresAcquisition(
1167
+ selection(previous.data),
1168
+ selection(requested),
833
1169
  );
834
1170
  }
835
1171
 
1172
+ function integrationFacetReceiptDisplayName(result: Record<string, unknown>): string | null {
1173
+ const binding = result.binding;
1174
+ if (!binding || typeof binding !== "object" || Array.isArray(binding)) return null;
1175
+ const bindingRecord = binding as Record<string, unknown>;
1176
+ return typeof bindingRecord.displayName === "string" ? bindingRecord.displayName : null;
1177
+ }
1178
+
836
1179
  export async function saveGoogleDriveSource(
837
1180
  deps: ApiRouteDeps,
838
1181
  input: {
@@ -864,14 +1207,34 @@ export async function saveGoogleDriveSource(
864
1207
  if (existing.accountId !== input.accountId || existing.workspaceId !== input.workspaceId) {
865
1208
  throw new HTTPException(403, { message: "Google Drive connection authority mismatch" });
866
1209
  }
867
- await requireGoogleDriveSourceConnection(deps, existing, input.subjectId);
1210
+ const existingMetadata = await requireGoogleDriveSourceConnection(
1211
+ deps,
1212
+ existing,
1213
+ input.subjectId,
1214
+ );
868
1215
  const documentDestination = googleDriveDocumentDestination(input, payload);
869
- const verifiedSources = await verifyGoogleDriveSources(deps, {
870
- workspaceId: input.workspaceId,
871
- subjectId: input.subjectId,
872
- connectionId: input.connectionId,
873
- sources: payload.sources,
874
- });
1216
+ const requestedSelection = payload.sources.map((source) => ({
1217
+ ...source,
1218
+ destination: documentDestination,
1219
+ syncCadence: payload.syncCadence,
1220
+ syncEnabled: payload.syncEnabled,
1221
+ readPolicy: payload.readPolicy,
1222
+ }));
1223
+ const acquiring = integrationSourceSelectionRequiresAcquisition(
1224
+ existingMetadata.selectedSources ??
1225
+ (existingMetadata.selectedSource ? [existingMetadata.selectedSource] : []),
1226
+ requestedSelection,
1227
+ );
1228
+ if (acquiring)
1229
+ await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
1230
+ const verifiedSources = acquiring
1231
+ ? await verifyGoogleDriveSources(deps, {
1232
+ workspaceId: input.workspaceId,
1233
+ subjectId: input.subjectId,
1234
+ connectionId: input.connectionId,
1235
+ sources: payload.sources,
1236
+ })
1237
+ : payload.sources;
875
1238
  const latest =
876
1239
  (await getConnectionMetadata(
877
1240
  deps.db,
@@ -883,33 +1246,46 @@ export async function saveGoogleDriveSource(
883
1246
  const previousSources =
884
1247
  latestMetadata.selectedSources ??
885
1248
  (latestMetadata.selectedSource ? [latestMetadata.selectedSource] : []);
886
- const updated = await transitionConnectionState(deps.db, {
887
- workspaceId: input.workspaceId,
888
- connectionId: latest.id,
889
- visibleToSubjectId: input.subjectId,
890
- expectedVersion: latest.version,
891
- metadata: GoogleDriveConnectionMetadata.parse({
892
- ...latestMetadata,
893
- documentDestination,
894
- selectedSource: null,
895
- selectedSources: verifiedSources.map((verified) => {
896
- const previous = previousSources.find((source) => source.id === verified.id);
897
- return {
898
- id: verified.id,
899
- name: verified.name,
900
- mimeType: verified.mimeType,
901
- driveId: verified.driveId,
902
- destination: documentDestination,
903
- syncCadence: payload.syncCadence,
904
- syncEnabled: payload.syncEnabled,
905
- configGeneration: (previous?.configGeneration ?? 0) + 1,
906
- readPolicy: payload.readPolicy,
907
- selectedAt: new Date().toISOString(),
908
- };
909
- }),
910
- }),
911
- updatedBySubjectId: input.subjectId,
912
- });
1249
+ const updated = await withOrganizationIntegrationPolicyFence(
1250
+ deps.db,
1251
+ input,
1252
+ async (tx, policy) => {
1253
+ if (integrationSourceSelectionRequiresAcquisition(previousSources, requestedSelection)) {
1254
+ assertOrganizationIntegrationAllowed(policy, "google-drive");
1255
+ if (!acquiring)
1256
+ throw new HTTPException(409, {
1257
+ message: "Google Drive sources changed; verify the selection again",
1258
+ });
1259
+ }
1260
+ return transitionConnectionState(tx, {
1261
+ workspaceId: input.workspaceId,
1262
+ connectionId: latest.id,
1263
+ visibleToSubjectId: input.subjectId,
1264
+ expectedVersion: latest.version,
1265
+ metadata: GoogleDriveConnectionMetadata.parse({
1266
+ ...latestMetadata,
1267
+ documentDestination,
1268
+ selectedSource: null,
1269
+ selectedSources: verifiedSources.map((verified) => {
1270
+ const previous = previousSources.find((source) => source.id === verified.id);
1271
+ return {
1272
+ id: verified.id,
1273
+ name: verified.name,
1274
+ mimeType: verified.mimeType,
1275
+ driveId: verified.driveId,
1276
+ destination: documentDestination,
1277
+ syncCadence: payload.syncCadence,
1278
+ syncEnabled: payload.syncEnabled,
1279
+ configGeneration: (previous?.configGeneration ?? 0) + 1,
1280
+ readPolicy: payload.readPolicy,
1281
+ selectedAt: new Date().toISOString(),
1282
+ };
1283
+ }),
1284
+ }),
1285
+ updatedBySubjectId: input.subjectId,
1286
+ });
1287
+ },
1288
+ );
913
1289
  if (!updated) {
914
1290
  throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
915
1291
  }
@@ -935,6 +1311,53 @@ export async function saveGoogleDriveSource(
935
1311
  return updated;
936
1312
  }
937
1313
 
1314
+ async function verifyPickedGoogleDriveOutputDestination(
1315
+ accessToken: string,
1316
+ rawPickedFileIds: string | undefined,
1317
+ fetchImpl: FetchLike,
1318
+ ) {
1319
+ const pickedFileIds = uniqueStrings((rawPickedFileIds ?? "").split(","));
1320
+ if (pickedFileIds.length !== 1) {
1321
+ throw new GoogleDriveCallbackError("output_folder_required");
1322
+ }
1323
+ const folderId = validDriveId(pickedFileIds[0]!, "picked_file_ids");
1324
+ const url = new URL(`${GOOGLE_DRIVE_API_BASE}/files/${encodeURIComponent(folderId)}`);
1325
+ url.searchParams.set("supportsAllDrives", "true");
1326
+ url.searchParams.set("fields", "id,name,mimeType,driveId,trashed,capabilities(canAddChildren)");
1327
+ const response = await providerFetch(fetchImpl, url, {
1328
+ headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
1329
+ });
1330
+ if (!response.ok) {
1331
+ await response.body?.cancel().catch(() => undefined);
1332
+ throw new GoogleDriveCallbackError("output_folder_unavailable");
1333
+ }
1334
+ const record = objectRecord(
1335
+ await readResponseJsonBounded<unknown>(
1336
+ response,
1337
+ GOOGLE_RESPONSE_MAX_BYTES,
1338
+ "Google Drive picked output folder",
1339
+ ),
1340
+ );
1341
+ const driveId = optionalString(record.driveId);
1342
+ const capabilities = objectRecord(record.capabilities);
1343
+ if (
1344
+ requiredString(record.id, "Google Drive folder id") !== folderId ||
1345
+ requiredString(record.mimeType, "Google Drive folder MIME type") !==
1346
+ GOOGLE_DRIVE_FOLDER_MIME_TYPE ||
1347
+ record.trashed === true ||
1348
+ capabilities.canAddChildren !== true
1349
+ ) {
1350
+ throw new GoogleDriveCallbackError("output_folder_unavailable");
1351
+ }
1352
+ return GoogleDriveOutputDestination.parse({
1353
+ folderId,
1354
+ folderName: requiredString(record.name, "Google Drive folder name"),
1355
+ driveId: driveId ?? null,
1356
+ location: driveId ? "shared_drive" : "my_drive",
1357
+ selectedAt: new Date().toISOString(),
1358
+ });
1359
+ }
1360
+
938
1361
  async function requireGoogleDriveIntegrationFacet(
939
1362
  deps: ApiRouteDeps,
940
1363
  input: {
@@ -1008,6 +1431,24 @@ function googleDriveDocumentDestination(
1008
1431
  if (destinationSelection.authorityKind === "personal" && !input.canManagePersonalDestination) {
1009
1432
  throw new HTTPException(403, { message: "personal destination requires the exact actor" });
1010
1433
  }
1434
+ return bindGoogleDriveDocumentDestination(input, payload);
1435
+ }
1436
+
1437
+ function bindGoogleDriveDocumentDestination(
1438
+ input: {
1439
+ accountId: string;
1440
+ workspaceId: string;
1441
+ subjectId: string;
1442
+ },
1443
+ payload: {
1444
+ destination?: ConnectorDocumentDestinationSelection | undefined;
1445
+ targetScope?: "user" | "workspace" | "organization" | undefined;
1446
+ },
1447
+ ): ConnectorDocumentDestination {
1448
+ const destinationSelection: ConnectorDocumentDestinationSelection = payload.destination ?? {
1449
+ authorityKind: "workspace",
1450
+ collectionId: null,
1451
+ };
1011
1452
  return bindConnectorDocumentDestination(destinationSelection, {
1012
1453
  accountId: input.accountId,
1013
1454
  workspaceId: input.workspaceId,
@@ -1015,6 +1456,51 @@ function googleDriveDocumentDestination(
1015
1456
  });
1016
1457
  }
1017
1458
 
1459
+ function googleDriveFacetConfig(
1460
+ sources: ReadonlyArray<{
1461
+ id: string;
1462
+ name: string;
1463
+ mimeType: string;
1464
+ driveId: string | null;
1465
+ }>,
1466
+ documentDestination: ConnectorDocumentDestination,
1467
+ payload: {
1468
+ syncCadence: "manual" | "hourly" | "daily";
1469
+ readPolicy: "allow" | "ask" | "block";
1470
+ },
1471
+ ) {
1472
+ return GoogleDriveKnowledgeSourceConfig.parse({
1473
+ sources: sources.map((source) => ({
1474
+ id: source.id,
1475
+ name: source.name,
1476
+ mimeType: source.mimeType,
1477
+ ...(source.driveId ? { driveId: source.driveId } : {}),
1478
+ sourceKind:
1479
+ source.id === "root"
1480
+ ? "my_drive"
1481
+ : source.driveId === source.id
1482
+ ? "shared_drive"
1483
+ : "folder",
1484
+ includeDescendants: true,
1485
+ })),
1486
+ destination: {
1487
+ authorityKind: documentDestination.authorityKind,
1488
+ authorityAccountId: documentDestination.authorityAccountId,
1489
+ ...(documentDestination.authorityWorkspaceId
1490
+ ? { authorityWorkspaceId: documentDestination.authorityWorkspaceId }
1491
+ : {}),
1492
+ ...(documentDestination.authoritySubjectId
1493
+ ? { authoritySubjectId: documentDestination.authoritySubjectId }
1494
+ : {}),
1495
+ ...(documentDestination.collectionId
1496
+ ? { collectionId: documentDestination.collectionId }
1497
+ : {}),
1498
+ },
1499
+ syncCadence: payload.syncCadence,
1500
+ readPolicy: payload.readPolicy,
1501
+ });
1502
+ }
1503
+
1018
1504
  async function verifyGoogleDriveSources(
1019
1505
  deps: ApiRouteDeps,
1020
1506
  input: {
@@ -1087,11 +1573,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
1087
1573
  let source = null as Awaited<ReturnType<typeof upsertKnowledgeSource>> | null;
1088
1574
  let existingTask = null as (typeof connectionTasks)[number] | null;
1089
1575
  for (const task of connectionTasks) {
1090
- if (task.action.kind !== "knowledge_source_sync") continue;
1576
+ if (!scheduledTaskKnowledgeSource(task)) continue;
1091
1577
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1092
1578
  accountId: input.accountId,
1093
1579
  workspaceId: input.workspaceId,
1094
- sourceId: task.action.sourceId,
1580
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1095
1581
  initiatingSubjectId: input.subjectId,
1096
1582
  });
1097
1583
  if (resolved?.source.externalSourceId !== identity.externalSourceId) continue;
@@ -1182,13 +1668,8 @@ async function materializeGoogleDriveKnowledgeSchedules(
1182
1668
  kind: input.connection.kind,
1183
1669
  },
1184
1670
  limits: {
1185
- maxItems: 500,
1186
- maxBytes: 500_000_000,
1187
- maxFileBytes: 100_000_000,
1188
- maxProviderRequests: 1_000,
1189
- maxElapsedSeconds: 300,
1671
+ ...configuredGoogleDriveSyncLimits(deps.settings),
1190
1672
  maxConcurrency: 4,
1191
- maxFailureDetails: 25,
1192
1673
  },
1193
1674
  };
1194
1675
  const schedule = googleDriveSchedule(selectedSource.syncCadence);
@@ -1198,7 +1679,10 @@ async function materializeGoogleDriveKnowledgeSchedules(
1198
1679
  task = await updateScheduledTask(deps.db, input.workspaceId, existingTask.id, {
1199
1680
  name: `Sync Google Drive: ${selectedSource.name}`,
1200
1681
  overlapPolicy: "buffer_one",
1201
- action,
1682
+ action: { kind: "agent_turn" },
1683
+ agentConfig: knowledgeSourceAgentConfig(action, existingTask.agentConfig),
1684
+ refreshPersonalResourceAuthority: true,
1685
+ authorityUpdatedBy: { kind: "subject", subjectId: input.subjectId },
1202
1686
  metadata: {
1203
1687
  ...existingTask.metadata,
1204
1688
  connectorKind: "google_drive",
@@ -1230,15 +1714,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
1230
1714
  status: "active",
1231
1715
  schedule,
1232
1716
  overlapPolicy: "buffer_one",
1233
- action,
1717
+ action: { kind: "agent_turn" },
1234
1718
  runMode: "new_session_per_run",
1235
1719
  targetSessionId: null,
1236
- agentConfig: {
1237
- prompt: "Knowledge source synchronization",
1238
- resources: [],
1239
- tools: [],
1240
- metadata: {},
1241
- },
1720
+ connectionAccounts: [],
1721
+ agentConfig: knowledgeSourceAgentConfig(action),
1242
1722
  variableSetId: null,
1243
1723
  environmentId: null,
1244
1724
  rigId: null,
@@ -1263,11 +1743,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
1263
1743
  }
1264
1744
 
1265
1745
  for (const task of connectionTasks) {
1266
- if (task.action.kind !== "knowledge_source_sync") continue;
1746
+ if (!scheduledTaskKnowledgeSource(task)) continue;
1267
1747
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1268
1748
  accountId: input.accountId,
1269
1749
  workspaceId: input.workspaceId,
1270
- sourceId: task.action.sourceId,
1750
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1271
1751
  initiatingSubjectId: input.subjectId,
1272
1752
  });
1273
1753
  if (!resolved || enabledIds.has(resolved.source.externalSourceId)) continue;
@@ -1306,15 +1786,15 @@ async function materializeGoogleDriveKnowledgeSchedules(
1306
1786
  }
1307
1787
  }
1308
1788
 
1309
- export async function revokeKnowledgeSourceScheduleAuthorization(
1789
+ export async function preflightKnowledgeSourceScheduleAuthorization(
1310
1790
  deps: ApiRouteDeps,
1311
1791
  input: { task: ScheduledTask; subjectId: string },
1312
1792
  ): Promise<void> {
1313
1793
  const { task } = input;
1314
- if (task.action.kind !== "knowledge_source_sync") return;
1794
+ if (!scheduledTaskKnowledgeSource(task)) return;
1315
1795
  if (
1316
- task.action.initiatingSubjectId !== input.subjectId ||
1317
- task.action.connection.ownerSubjectId !== input.subjectId
1796
+ requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
1797
+ requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
1318
1798
  ) {
1319
1799
  throw new HTTPException(403, {
1320
1800
  message: "knowledge source schedule requires the exact initiating subject",
@@ -1330,65 +1810,138 @@ export async function revokeKnowledgeSourceScheduleAuthorization(
1330
1810
  if (!externalSourceId) {
1331
1811
  throw new HTTPException(409, { message: "knowledge source schedule identity is incomplete" });
1332
1812
  }
1333
- const connection = await getConnectionMetadata(
1334
- deps.db,
1335
- task.workspaceId,
1336
- task.action.connection.connectionId,
1337
- input.subjectId,
1338
- );
1339
- if (!connection) {
1340
- throw new HTTPException(409, {
1341
- message: "knowledge source connection is unavailable for durable disable",
1813
+ // A deselected/disconnected source still owns a retained task that must be
1814
+ // deletable. The row-locked task revision below freezes this descriptor;
1815
+ // cleanup mutates only its exact accepted generations.
1816
+ }
1817
+
1818
+ export async function revokeKnowledgeSourceScheduleAuthorization(
1819
+ deps: ApiRouteDeps,
1820
+ input: { task: ScheduledTask; subjectId: string },
1821
+ ): Promise<void> {
1822
+ const { task } = input;
1823
+ if (!scheduledTaskKnowledgeSource(task)) return;
1824
+ if (
1825
+ requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
1826
+ requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
1827
+ ) {
1828
+ throw new HTTPException(403, {
1829
+ message: "knowledge source schedule requires the exact initiating subject",
1342
1830
  });
1343
1831
  }
1344
- const metadata = requireGoogleDriveConnection(connection, input.subjectId);
1345
- const sources =
1346
- metadata.selectedSources ?? (metadata.selectedSource ? [metadata.selectedSource] : []);
1347
- const selected = sources.find((source) => source.id === externalSourceId);
1348
- let revocationVersion = connection.version;
1349
- if (selected?.syncEnabled) {
1350
- const updated = await transitionConnectionState(deps.db, {
1351
- workspaceId: task.workspaceId,
1352
- connectionId: connection.id,
1353
- visibleToSubjectId: input.subjectId,
1354
- expectedVersion: connection.version,
1355
- metadata: GoogleDriveConnectionMetadata.parse({
1356
- ...metadata,
1357
- selectedSource: null,
1358
- selectedSources: sources.map((source) =>
1359
- source.id === externalSourceId
1360
- ? {
1361
- ...source,
1362
- syncEnabled: false,
1363
- configGeneration: source.configGeneration + 1,
1364
- }
1365
- : source,
1366
- ),
1367
- }),
1368
- updatedBySubjectId: input.subjectId,
1832
+ if (task.metadata.connectorKind !== "google_drive") {
1833
+ throw new HTTPException(409, {
1834
+ message: "knowledge source schedule connector cannot be durably disabled",
1369
1835
  });
1370
- if (!updated) {
1371
- throw new HTTPException(409, {
1372
- message: "knowledge source connection changed; retry schedule deletion",
1373
- });
1374
- }
1375
- revocationVersion = updated.version;
1376
1836
  }
1377
- const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1837
+ const externalSourceId =
1838
+ typeof task.metadata.externalSourceId === "string" ? task.metadata.externalSourceId.trim() : "";
1839
+ if (!externalSourceId) {
1840
+ throw new HTTPException(409, { message: "knowledge source schedule identity is incomplete" });
1841
+ }
1842
+ await cleanupKnowledgeSourceScheduleAuthorization(deps, {
1843
+ taskId: task.id,
1378
1844
  accountId: task.accountId,
1379
1845
  workspaceId: task.workspaceId,
1380
- sourceId: task.action.sourceId,
1846
+ connectionId: requireScheduledTaskKnowledgeSource(task).connection.connectionId,
1847
+ connectionVersion: requireScheduledTaskKnowledgeSource(task).connection.connectionVersion,
1848
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1849
+ sourceLifecycleGeneration: requireScheduledTaskKnowledgeSource(task).sourceLifecycleGeneration,
1850
+ sourceConfigGeneration: requireScheduledTaskKnowledgeSource(task).sourceConfigGeneration,
1851
+ externalSourceId,
1852
+ subjectId: input.subjectId,
1853
+ });
1854
+ }
1855
+
1856
+ export async function cleanupKnowledgeSourceScheduleAuthorization(
1857
+ deps: ApiRouteDeps,
1858
+ input: {
1859
+ taskId: string;
1860
+ accountId: string;
1861
+ workspaceId: string;
1862
+ connectionId: string;
1863
+ connectionVersion: number;
1864
+ sourceId: string;
1865
+ sourceLifecycleGeneration: number;
1866
+ sourceConfigGeneration: number;
1867
+ externalSourceId: string;
1868
+ subjectId: string;
1869
+ },
1870
+ ): Promise<void> {
1871
+ const resolvedBeforeLock = await getKnowledgeSourceForSyncAuthority(deps.db, {
1872
+ accountId: input.accountId,
1873
+ workspaceId: input.workspaceId,
1874
+ sourceId: input.sourceId,
1381
1875
  initiatingSubjectId: input.subjectId,
1382
1876
  });
1383
- if (resolved?.source.lifecycleState === "active") {
1384
- await recordKnowledgeLifecycleEvent(deps.db, {
1385
- accountId: task.accountId,
1386
- workspaceId: task.workspaceId,
1877
+ if (
1878
+ resolvedBeforeLock?.source.lifecycleState !== "active" ||
1879
+ resolvedBeforeLock.source.lifecycleGeneration !== input.sourceLifecycleGeneration
1880
+ ) {
1881
+ return;
1882
+ }
1883
+ await withWorkspaceSubjectRls(deps.db, input.workspaceId, input.subjectId, async (tx) => {
1884
+ const connection = await getConnectionMetadata(
1885
+ tx,
1886
+ input.workspaceId,
1887
+ input.connectionId,
1888
+ input.subjectId,
1889
+ );
1890
+ let revocationVersion = input.connectionVersion;
1891
+ if (connection) {
1892
+ const metadata = requireGoogleDriveConnection(connection, input.subjectId);
1893
+ const sources =
1894
+ metadata.selectedSources ?? (metadata.selectedSource ? [metadata.selectedSource] : []);
1895
+ revocationVersion = connection.version;
1896
+ const updated = await transitionConnectionState(tx, {
1897
+ workspaceId: input.workspaceId,
1898
+ connectionId: connection.id,
1899
+ visibleToSubjectId: input.subjectId,
1900
+ expectedVersion: connection.version,
1901
+ metadata: GoogleDriveConnectionMetadata.parse({
1902
+ ...metadata,
1903
+ selectedSource: null,
1904
+ selectedSources: sources.map((source) =>
1905
+ source.id === input.externalSourceId
1906
+ ? {
1907
+ ...source,
1908
+ syncEnabled: false,
1909
+ configGeneration: source.configGeneration + 1,
1910
+ }
1911
+ : source,
1912
+ ),
1913
+ }),
1914
+ updatedBySubjectId: input.subjectId,
1915
+ });
1916
+ if (!updated) {
1917
+ throw new HTTPException(409, {
1918
+ message: "knowledge source connection changed; retry schedule deletion",
1919
+ });
1920
+ }
1921
+ revocationVersion = updated.version;
1922
+ }
1923
+ const resolved = await getKnowledgeSourceForSyncAuthority(tx, {
1924
+ accountId: input.accountId,
1925
+ workspaceId: input.workspaceId,
1926
+ sourceId: input.sourceId,
1927
+ initiatingSubjectId: input.subjectId,
1928
+ });
1929
+ if (
1930
+ resolved?.source.lifecycleState !== "active" ||
1931
+ resolved.source.lifecycleGeneration !== input.sourceLifecycleGeneration
1932
+ ) {
1933
+ throw new HTTPException(409, {
1934
+ message: "knowledge source changed; retry schedule deletion",
1935
+ });
1936
+ }
1937
+ await recordKnowledgeLifecycleEvent(tx, {
1938
+ accountId: input.accountId,
1939
+ workspaceId: input.workspaceId,
1387
1940
  targetKind: "source",
1388
1941
  targetId: resolved.source.id,
1389
1942
  eventType: "deleted",
1390
1943
  expectedGeneration: resolved.source.lifecycleGeneration,
1391
- operationId: `knowledge-schedule-delete:${task.id}:${revocationVersion}`,
1944
+ operationId: `knowledge-schedule-delete:${input.taskId}:${revocationVersion}`,
1392
1945
  reasonCode: "schedule_deleted",
1393
1946
  actor: {
1394
1947
  kind: "human",
@@ -1396,7 +1949,7 @@ export async function revokeKnowledgeSourceScheduleAuthorization(
1396
1949
  initiatingHumanSubjectId: input.subjectId,
1397
1950
  },
1398
1951
  });
1399
- }
1952
+ });
1400
1953
  }
1401
1954
 
1402
1955
  function knowledgeSourceScheduleControl(task: ScheduledTask): {
@@ -1514,6 +2067,16 @@ function requireGoogleDriveSettings(settings: Settings): {
1514
2067
  return { clientId, clientSecret };
1515
2068
  }
1516
2069
 
2070
+ function requireGoogleDriveOAuthCallbackUrl(settings: Settings): string {
2071
+ const callbackUrl = googleDriveOAuthCallbackUrl(settings.publicBaseUrl);
2072
+ if (!callbackUrl) {
2073
+ throw new HTTPException(503, {
2074
+ message: "Google Drive requires a canonical OPENGENI_PUBLIC_BASE_URL origin",
2075
+ });
2076
+ }
2077
+ return callbackUrl;
2078
+ }
2079
+
1517
2080
  function requireGoogleDriveConnection(connection: GoogleDriveConnectionRecord, subjectId: string) {
1518
2081
  const parsed = GoogleDriveConnectionMetadata.safeParse(connection.metadata);
1519
2082
  if (
@@ -1699,6 +2262,13 @@ function readGoogleDriveOAuthState(
1699
2262
  if (iat === undefined || now < iat || now - iat > oauthStateTtlMs / 1000) {
1700
2263
  throw new HTTPException(400, { message: "invalid or expired Google Drive OAuth state" });
1701
2264
  }
2265
+ // Reject a state minted by any other flow before reading anything else. A
2266
+ // pre-cutover state carries no kind and is refused; that is the same bounded
2267
+ // `oauthStateTtlMs` window the personal-owner claim already fails closed on,
2268
+ // so this adds no new breakage.
2269
+ if (payload.kind !== GOOGLE_DRIVE_OAUTH_STATE_KIND) {
2270
+ throw new HTTPException(400, { message: "invalid Google Drive OAuth state" });
2271
+ }
1702
2272
  const accountId = requiredString(payload.accountId, "state.accountId");
1703
2273
  const workspaceId = requiredString(payload.workspaceId, "state.workspaceId");
1704
2274
  const subjectId = requiredString(payload.subjectId, "state.subjectId");
@@ -1708,6 +2278,11 @@ function readGoogleDriveOAuthState(
1708
2278
  }
1709
2279
  const connectionId = optionalString(payload.connectionId) ?? undefined;
1710
2280
  const connectionVersion = numberValue(payload.connectionVersion);
2281
+ const capabilityValue = optionalString(payload.capability);
2282
+ const capability = capabilityValue ?? "source_read";
2283
+ if (capability !== "source_read" && capability !== "publish") {
2284
+ throw new HTTPException(400, { message: "invalid Google Drive OAuth capability" });
2285
+ }
1711
2286
  if (
1712
2287
  (connectionVersion !== undefined && !Number.isInteger(connectionVersion)) ||
1713
2288
  Boolean(connectionId) !== Boolean(connectionVersion)
@@ -1718,11 +2293,28 @@ function readGoogleDriveOAuthState(
1718
2293
  accountId,
1719
2294
  workspaceId,
1720
2295
  subjectId,
2296
+ personalOwnerVerified: personalOwnerVerifiedInState(payload),
2297
+ ...(typeof payload.connectAttemptId === "string"
2298
+ ? { connectAttemptId: payload.connectAttemptId }
2299
+ : {}),
2300
+ ...(typeof payload.encryptedExternalContinuation === "string"
2301
+ ? {
2302
+ externalContinuation: ExternalActorContinuation.parse(
2303
+ JSON.parse(
2304
+ decryptEnvironmentValue(
2305
+ requireEnvironmentEncryption(settings),
2306
+ payload.encryptedExternalContinuation,
2307
+ ),
2308
+ ),
2309
+ ),
2310
+ }
2311
+ : {}),
1721
2312
  returnPath,
1722
2313
  encryptedPkceVerifier: requiredString(
1723
2314
  payload.encryptedPkceVerifier,
1724
2315
  "state.encryptedPkceVerifier",
1725
2316
  ),
2317
+ capability,
1726
2318
  ...(connectionId ? { connectionId, connectionVersion: connectionVersion! } : {}),
1727
2319
  nonce: requiredString(payload.nonce, "state.nonce"),
1728
2320
  iat,
@@ -1733,16 +2325,22 @@ async function requireGoogleDriveCallbackGrant(
1733
2325
  deps: ApiRouteDeps,
1734
2326
  state: GoogleDriveOAuthState,
1735
2327
  ): Promise<void> {
1736
- const grant = await getWorkspaceGrant(deps.db, state.subjectId, state.workspaceId);
2328
+ // This connector is personal-only by construction: its start request carries
2329
+ // no ownership and the callback always writes `subjectId: state.subjectId`.
2330
+ // A state minted before the start-side principal fence existed carries no
2331
+ // `personalOwnerVerified` claim and must not land a personal Connection.
1737
2332
  if (
1738
- !grant ||
1739
- grant.accountId !== state.accountId ||
1740
- !hasPermission(grant.permissions, "connections:write")
2333
+ !personalOwnerStateAccepted({
2334
+ ownership: "personal",
2335
+ subjectId: state.subjectId,
2336
+ personalOwnerVerified: state.personalOwnerVerified,
2337
+ })
1741
2338
  ) {
1742
- throw new HTTPException(403, {
1743
- message: "Google Drive OAuth subject no longer has permission for this workspace",
2339
+ throw new HTTPException(422, {
2340
+ message: personalOnlyConnectionPrincipalMessage("Google Drive"),
1744
2341
  });
1745
2342
  }
2343
+ await requireConnectOwnerAuthority(deps.db, state, "connections:write");
1746
2344
  }
1747
2345
 
1748
2346
  async function exchangeGoogleAuthorizationCode(
@@ -1975,7 +2573,7 @@ async function googleDriveApiRequest(
1975
2573
  if (!resolved || resolved.version !== connectionVersion) {
1976
2574
  throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
1977
2575
  }
1978
- await requireGoogleDriveSourceConnection(deps, resolved, input.subjectId);
2576
+ await requireGoogleDriveApiConnection(deps, resolved, input.subjectId);
1979
2577
  };
1980
2578
  let credential = await resolve(false);
1981
2579
  if (credential.status !== "ok") {