@opengeni/api-router 0.30.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-source-session.d.ts +3 -0
  35. package/dist/editable-artifact-websocket.d.ts +10 -18
  36. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  37. package/dist/fatal-process-boundary.d.ts +25 -0
  38. package/dist/github-access.d.ts +31 -2
  39. package/dist/http/acting-person.d.ts +24 -0
  40. package/dist/http/api-contract-compat.d.ts +19 -0
  41. package/dist/http/api-error.d.ts +27 -1
  42. package/dist/http/cors.d.ts +17 -0
  43. package/dist/http/deprecation.d.ts +42 -0
  44. package/dist/http/interaction-control-error.d.ts +13 -0
  45. package/dist/http/keyed-admission.d.ts +12 -0
  46. package/dist/http/local-browser-boundary.d.ts +72 -0
  47. package/dist/http/metrics-listener.d.ts +22 -0
  48. package/dist/http/path-identifier.d.ts +17 -0
  49. package/dist/http/registered-route-label.d.ts +16 -0
  50. package/dist/http/request-body.d.ts +27 -0
  51. package/dist/http/request-source.d.ts +52 -0
  52. package/dist/http/sse.d.ts +17 -6
  53. package/dist/http/unmatched-route.d.ts +18 -0
  54. package/dist/http/user-content.d.ts +36 -0
  55. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  56. package/dist/index.d.ts +10 -3
  57. package/dist/index.js +1091 -162
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-endpoint-test.d.ts +20 -0
  60. package/dist/integrations/api-integrations.d.ts +2 -2
  61. package/dist/integrations/atlassian.d.ts +29 -3
  62. package/dist/integrations/connect-authority.d.ts +1 -0
  63. package/dist/integrations/connect-callback-return.d.ts +6 -0
  64. package/dist/integrations/fiken.d.ts +16 -2
  65. package/dist/integrations/github-app-connect.d.ts +47 -0
  66. package/dist/integrations/github-installation-proof.d.ts +3 -0
  67. package/dist/integrations/github-lens-connect.d.ts +116 -0
  68. package/dist/integrations/github-skill-source.d.ts +1 -5
  69. package/dist/integrations/google-drive.d.ts +24 -0
  70. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  71. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  72. package/dist/integrations/oauth-client.d.ts +178 -18
  73. package/dist/integrations/oauth-profiles.d.ts +222 -0
  74. package/dist/integrations/oauth-return-path.d.ts +1 -0
  75. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  76. package/dist/integrations/personal-github.d.ts +42 -0
  77. package/dist/integrations/pr-review-provider.d.ts +22 -0
  78. package/dist/integrations/provider-oauth.d.ts +19 -1
  79. package/dist/integrations/slack-app-home.d.ts +24 -0
  80. package/dist/integrations/slack-bot.d.ts +367 -12
  81. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  82. package/dist/integrations/slack-install.d.ts +20 -0
  83. package/dist/integrations/slack-interactions.d.ts +107 -9
  84. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  85. package/dist/integrations/slack-routing.d.ts +120 -0
  86. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  87. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  88. package/dist/integrations/social-oauth.d.ts +7 -0
  89. package/dist/interaction-agent-access.d.ts +15 -0
  90. package/dist/interaction-frame-proxy.d.ts +68 -0
  91. package/dist/interaction-metrics.d.ts +2 -0
  92. package/dist/mcp/action-catalog-types.d.ts +9 -0
  93. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  94. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  95. package/dist/mcp/child-read-evidence.d.ts +9 -0
  96. package/dist/mcp/command-read.d.ts +24 -0
  97. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  98. package/dist/mcp/contract-input.d.ts +11 -0
  99. package/dist/mcp/documents.d.ts +7 -5
  100. package/dist/mcp/files.d.ts +2 -2
  101. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  102. package/dist/mcp/knowledge-entries.d.ts +5 -0
  103. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  104. package/dist/mcp/receipts.d.ts +9 -0
  105. package/dist/mcp/request-abort.d.ts +19 -0
  106. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  107. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  108. package/dist/mcp/server.d.ts +255 -3
  109. package/dist/mcp/session-event-view.d.ts +94 -0
  110. package/dist/mcp/session-view.d.ts +66 -2
  111. package/dist/mcp/session-wait.d.ts +174 -0
  112. package/dist/mcp-oauth.d.ts +52 -0
  113. package/dist/model-catalog.d.ts +11 -31
  114. package/dist/observability.d.ts +3 -1
  115. package/dist/organization-mcp.d.ts +52 -0
  116. package/dist/organization-recovery-notifications.d.ts +41 -0
  117. package/dist/routes/analytics-consent.d.ts +26 -0
  118. package/dist/routes/api-integrations.d.ts +25 -0
  119. package/dist/routes/api-keys.d.ts +17 -0
  120. package/dist/routes/artifact-catalog.d.ts +6 -0
  121. package/dist/routes/automations.d.ts +19 -0
  122. package/dist/routes/billing.d.ts +49 -0
  123. package/dist/routes/browser-sessions.d.ts +59 -2
  124. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  125. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  126. package/dist/routes/client-errors.d.ts +146 -0
  127. package/dist/routes/codex.d.ts +76 -2
  128. package/dist/routes/company-brain.d.ts +3 -0
  129. package/dist/routes/company-profile.d.ts +2 -1
  130. package/dist/routes/computer-sessions.d.ts +14 -0
  131. package/dist/routes/connect.d.ts +5 -0
  132. package/dist/routes/editable-artifacts.d.ts +2 -0
  133. package/dist/routes/external-identity-links.d.ts +6 -0
  134. package/dist/routes/feedback.d.ts +3 -0
  135. package/dist/routes/github-browser-pages.d.ts +12 -0
  136. package/dist/routes/github.d.ts +2 -0
  137. package/dist/routes/insights-response-cache.d.ts +10 -0
  138. package/dist/routes/insights-usage.d.ts +12 -0
  139. package/dist/routes/insights.d.ts +30 -1
  140. package/dist/routes/knowledge.d.ts +3 -0
  141. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  142. package/dist/routes/managed-onboarding.d.ts +29 -0
  143. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  144. package/dist/routes/model-connection-access.d.ts +3 -0
  145. package/dist/routes/organization-integration-policy.d.ts +3 -0
  146. package/dist/routes/organization-integrations.d.ts +6 -0
  147. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  148. package/dist/routes/organization-model-providers.d.ts +3 -0
  149. package/dist/routes/organization-recovery.d.ts +19 -0
  150. package/dist/routes/organization-service-accounts.d.ts +3 -0
  151. package/dist/routes/organization-sessions.d.ts +26 -0
  152. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  153. package/dist/routes/personal-github.d.ts +3 -0
  154. package/dist/routes/pr-review-github.d.ts +3 -0
  155. package/dist/routes/pr-review.d.ts +3 -0
  156. package/dist/routes/session-artifact-associations.d.ts +4 -0
  157. package/dist/routes/session-history-imports.d.ts +9 -0
  158. package/dist/routes/sessions.d.ts +44 -6
  159. package/dist/routes/skill-content.d.ts +4 -0
  160. package/dist/routes/skill-install-authority.d.ts +6 -0
  161. package/dist/routes/subscription-account-pools.d.ts +34 -0
  162. package/dist/routes/subscription-pool-access.d.ts +16 -0
  163. package/dist/routes/supergrok.d.ts +6 -1
  164. package/dist/routes/usage-allowances.d.ts +12 -0
  165. package/dist/routes/user-resource-authorities.d.ts +3 -0
  166. package/dist/routes/workspace-artifacts.d.ts +2 -1
  167. package/dist/routes/workspace-capture.d.ts +10 -2
  168. package/dist/routes/workspace-integrations.d.ts +35 -0
  169. package/dist/routes/workspace-learning.d.ts +3 -0
  170. package/dist/routes/workspace-model-providers.d.ts +4 -0
  171. package/dist/routes/workspaces.d.ts +29 -0
  172. package/dist/sandbox/auth-callout.d.ts +8 -0
  173. package/dist/sandbox/channel-a.d.ts +19 -1
  174. package/dist/sandbox/connection-authority.d.ts +3 -0
  175. package/dist/sandbox/enrollment.d.ts +9 -1
  176. package/dist/sandbox/machines.d.ts +5 -2
  177. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  178. package/dist/sandbox/rematerialize.d.ts +2 -0
  179. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  180. package/dist/sandbox/viewer.d.ts +26 -1
  181. package/dist/sandbox-file-artifacts.d.ts +50 -0
  182. package/dist/scheduled-task-deletion.d.ts +13 -0
  183. package/dist/session-create-observability.d.ts +5 -0
  184. package/dist/session-event-view-MPS2OZD3.js +292 -0
  185. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  186. package/dist/site-session-origin.d.ts +13 -0
  187. package/dist/site-uploads.d.ts +41 -0
  188. package/dist/slack-reaction-files.d.ts +1 -1
  189. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  190. package/dist/temporal-schedule-sync.d.ts +26 -0
  191. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  192. package/dist/transcription/service.d.ts +2 -1
  193. package/dist/work-discovery-observability.d.ts +33 -0
  194. package/dist/workspace-artifact-content.d.ts +28 -0
  195. package/dist/workspace-artifact-provenance.d.ts +7 -0
  196. package/dist/workspace-delete-observability.d.ts +10 -0
  197. package/dist/workspace-deletion.d.ts +6 -0
  198. package/dist/workspace-state-projection.d.ts +3 -5
  199. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  200. package/dist/workspace-tool-gateway.d.ts +123 -0
  201. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  202. package/package.json +23 -19
  203. package/src/access-grant-rls.ts +75 -0
  204. package/src/admission-trace.ts +14 -0
  205. package/src/api-websocket.ts +23 -0
  206. package/src/app.ts +1967 -252
  207. package/src/artifact-catalog-cursor.ts +60 -0
  208. package/src/auth/managed-auth-attempt-context.ts +70 -0
  209. package/src/auth/managed-auth-rate-limits.ts +252 -0
  210. package/src/auth/managed-auth-session-adapter.ts +222 -0
  211. package/src/auth/managed-auth.ts +536 -60
  212. package/src/auth/managed-email.ts +174 -0
  213. package/src/auth/managed-sign-in-notifications.ts +81 -0
  214. package/src/auth/new-signups-gate.ts +49 -0
  215. package/src/auth/organization-user-setup.ts +307 -0
  216. package/src/auth/signup-funnel-metrics.ts +164 -0
  217. package/src/browser-controller-authority.ts +1 -186
  218. package/src/browser-ephemeral.ts +39 -0
  219. package/src/browser-state-authority.ts +1 -236
  220. package/src/claude-subscription-account-conflicts.ts +12 -0
  221. package/src/claude-subscription-account-usage.ts +48 -0
  222. package/src/claude-subscription-oauth.ts +362 -0
  223. package/src/claude-subscription-profile.ts +73 -0
  224. package/src/claude-subscription-usage.ts +97 -0
  225. package/src/claude-workspace-connection.ts +82 -0
  226. package/src/codemode.ts +194 -30
  227. package/src/codex-realtime.ts +8 -2
  228. package/src/company-brain-okf.ts +340 -0
  229. package/src/connected-machine-computer-access.ts +33 -0
  230. package/src/connection-ownership.ts +199 -0
  231. package/src/controller-data-plane.ts +81 -0
  232. package/src/editable-artifact-native-kernel.ts +26 -15
  233. package/src/editable-artifact-office-import.ts +28 -3
  234. package/src/editable-artifact-production.ts +23 -7
  235. package/src/editable-artifact-source-session.ts +123 -0
  236. package/src/editable-artifact-websocket.ts +27 -21
  237. package/src/editable-artifact-workspace-files.ts +31 -6
  238. package/src/fatal-process-boundary.ts +246 -0
  239. package/src/gateway-realtime.ts +1 -1
  240. package/src/github-access.ts +147 -5
  241. package/src/http/acting-person.ts +71 -0
  242. package/src/http/api-contract-compat.ts +49 -0
  243. package/src/http/api-error.ts +115 -1
  244. package/src/http/auth.ts +48 -0
  245. package/src/http/cors.ts +67 -0
  246. package/src/http/deprecation.ts +118 -0
  247. package/src/http/interaction-control-error.ts +164 -0
  248. package/src/http/keyed-admission.ts +29 -0
  249. package/src/http/local-browser-boundary.ts +343 -0
  250. package/src/http/metrics-listener.ts +57 -0
  251. package/src/http/path-identifier.ts +54 -0
  252. package/src/http/registered-route-label.ts +34 -0
  253. package/src/http/request-body.ts +127 -0
  254. package/src/http/request-source.ts +178 -0
  255. package/src/http/sse.ts +486 -132
  256. package/src/http/unmatched-route.ts +60 -0
  257. package/src/http/user-content.ts +158 -0
  258. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  259. package/src/index.ts +157 -52
  260. package/src/integration-endpoint-test.ts +291 -0
  261. package/src/integrations/api-integrations.ts +98 -11
  262. package/src/integrations/atlassian.ts +474 -136
  263. package/src/integrations/connect-authority.ts +2 -0
  264. package/src/integrations/connect-callback-return.ts +86 -0
  265. package/src/integrations/fiken.ts +354 -64
  266. package/src/integrations/github-app-connect.ts +435 -0
  267. package/src/integrations/github-installation-proof.ts +60 -0
  268. package/src/integrations/github-lens-connect.ts +76 -0
  269. package/src/integrations/github-skill-source.ts +2 -142
  270. package/src/integrations/google-drive.ts +717 -225
  271. package/src/integrations/integration-commit-authority.ts +69 -0
  272. package/src/integrations/marketplace-plugin.ts +48 -0
  273. package/src/integrations/oauth-client.ts +1356 -459
  274. package/src/integrations/oauth-profiles.ts +537 -0
  275. package/src/integrations/oauth-return-path.ts +18 -0
  276. package/src/integrations/personal-github-repositories.ts +674 -0
  277. package/src/integrations/personal-github.ts +825 -0
  278. package/src/integrations/pr-review-provider.ts +246 -0
  279. package/src/integrations/provider-oauth.ts +250 -22
  280. package/src/integrations/slack-app-home.ts +300 -0
  281. package/src/integrations/slack-bot.ts +963 -239
  282. package/src/integrations/slack-file-upload-flow.ts +147 -0
  283. package/src/integrations/slack-install.ts +83 -0
  284. package/src/integrations/slack-interactions.ts +3167 -474
  285. package/src/integrations/slack-mrkdwn.ts +193 -0
  286. package/src/integrations/slack-routing.ts +337 -0
  287. package/src/integrations/slack-session-defaults.ts +224 -0
  288. package/src/integrations/slack-task-file-upload.ts +323 -0
  289. package/src/integrations/social-oauth.ts +213 -15
  290. package/src/interaction-agent-access.ts +48 -0
  291. package/src/interaction-frame-proxy.ts +409 -0
  292. package/src/interaction-metrics.ts +30 -0
  293. package/src/mcp/action-catalog-types.ts +9 -0
  294. package/src/mcp/action-catalog.gen.ts +6696 -0
  295. package/src/mcp/capability-account-readiness.ts +17 -0
  296. package/src/mcp/child-read-evidence.ts +19 -0
  297. package/src/mcp/command-read.ts +109 -0
  298. package/src/mcp/company-profile-agent-admin.ts +198 -0
  299. package/src/mcp/contract-input.ts +141 -0
  300. package/src/mcp/documents.ts +65 -377
  301. package/src/mcp/editable-artifacts.ts +12 -6
  302. package/src/mcp/files.ts +80 -43
  303. package/src/mcp/first-party-tool-permissions.ts +305 -0
  304. package/src/mcp/knowledge-entries.ts +322 -0
  305. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  306. package/src/mcp/receipts.ts +34 -0
  307. package/src/mcp/request-abort.ts +44 -0
  308. package/src/mcp/scheduled-task-input.ts +76 -0
  309. package/src/mcp/scheduled-task-view.ts +44 -3
  310. package/src/mcp/server.ts +3030 -1267
  311. package/src/mcp/session-event-view.ts +383 -0
  312. package/src/mcp/session-view.ts +84 -3
  313. package/src/mcp/session-wait.ts +665 -0
  314. package/src/mcp-oauth.ts +1123 -0
  315. package/src/model-catalog.ts +91 -333
  316. package/src/observability.ts +15 -1
  317. package/src/organization-mcp.ts +397 -0
  318. package/src/organization-recovery-notifications.ts +103 -0
  319. package/src/routes/analytics-consent.ts +123 -0
  320. package/src/routes/api-integrations.ts +364 -74
  321. package/src/routes/api-keys.ts +336 -10
  322. package/src/routes/artifact-catalog.ts +279 -0
  323. package/src/routes/automations.ts +768 -0
  324. package/src/routes/billing.ts +461 -46
  325. package/src/routes/browser-sessions.ts +780 -112
  326. package/src/routes/canonical-human-identities.ts +90 -14
  327. package/src/routes/capabilities.ts +107 -6
  328. package/src/routes/channels.ts +20 -3
  329. package/src/routes/claude-subscription-accounts.ts +334 -0
  330. package/src/routes/claude-subscription-oauth.ts +142 -0
  331. package/src/routes/client-errors.ts +362 -0
  332. package/src/routes/codex.ts +833 -116
  333. package/src/routes/company-brain.ts +322 -0
  334. package/src/routes/company-profile.ts +65 -1
  335. package/src/routes/computer-sessions.ts +557 -106
  336. package/src/routes/connect.ts +1938 -0
  337. package/src/routes/connections.ts +1039 -349
  338. package/src/routes/documents.ts +488 -287
  339. package/src/routes/editable-artifacts.ts +79 -9
  340. package/src/routes/enrollments.ts +135 -39
  341. package/src/routes/environments.ts +154 -58
  342. package/src/routes/external-identity-links.ts +201 -0
  343. package/src/routes/feedback.ts +113 -0
  344. package/src/routes/files.ts +267 -33
  345. package/src/routes/github-browser-pages.ts +162 -0
  346. package/src/routes/github.ts +446 -144
  347. package/src/routes/insights-response-cache.ts +83 -0
  348. package/src/routes/insights-usage.ts +345 -0
  349. package/src/routes/insights.ts +153 -19
  350. package/src/routes/install.ts +133 -38
  351. package/src/routes/integration-facets.ts +107 -33
  352. package/src/routes/interaction-resources.ts +30 -3
  353. package/src/routes/knowledge.ts +488 -0
  354. package/src/routes/machines.ts +313 -15
  355. package/src/routes/managed-auth-session-sets.ts +1218 -0
  356. package/src/routes/managed-onboarding.ts +337 -0
  357. package/src/routes/managed-sign-in-methods.ts +433 -0
  358. package/src/routes/memory-slack-publications.ts +3 -2
  359. package/src/routes/model-connection-access.ts +208 -0
  360. package/src/routes/organization-integration-policy.ts +116 -0
  361. package/src/routes/organization-integrations.ts +326 -0
  362. package/src/routes/organization-mcp-connections.ts +178 -0
  363. package/src/routes/organization-memberships.ts +1019 -27
  364. package/src/routes/organization-model-providers.ts +293 -0
  365. package/src/routes/organization-recovery.ts +325 -0
  366. package/src/routes/organization-service-accounts.ts +128 -0
  367. package/src/routes/organization-sessions.ts +350 -0
  368. package/src/routes/personal-github-git-broker.ts +785 -0
  369. package/src/routes/personal-github.ts +370 -0
  370. package/src/routes/plugins.ts +251 -22
  371. package/src/routes/pr-review-github.ts +881 -0
  372. package/src/routes/pr-review.ts +622 -0
  373. package/src/routes/preference-registry.ts +12 -93
  374. package/src/routes/rigs.ts +167 -50
  375. package/src/routes/scheduled-tasks.ts +346 -41
  376. package/src/routes/session-artifact-associations.ts +87 -0
  377. package/src/routes/session-history-imports.ts +280 -0
  378. package/src/routes/sessions.ts +2306 -264
  379. package/src/routes/skill-content.ts +297 -0
  380. package/src/routes/skill-install-authority.ts +64 -0
  381. package/src/routes/skills.ts +62 -9
  382. package/src/routes/social.ts +46 -18
  383. package/src/routes/subscription-account-pools.ts +436 -0
  384. package/src/routes/subscription-pool-access.ts +126 -0
  385. package/src/routes/supergrok.ts +219 -303
  386. package/src/routes/transcription-recordings.ts +38 -3
  387. package/src/routes/transcriptions.ts +4 -0
  388. package/src/routes/usage-allowances.ts +378 -0
  389. package/src/routes/user-resource-authorities.ts +138 -0
  390. package/src/routes/video-generation.ts +14 -10
  391. package/src/routes/workspace-artifacts.ts +275 -69
  392. package/src/routes/workspace-capture.ts +21 -0
  393. package/src/routes/workspace-integrations.ts +576 -0
  394. package/src/routes/workspace-learning.ts +159 -0
  395. package/src/routes/workspace-model-providers.ts +148 -0
  396. package/src/routes/workspace-state.ts +38 -25
  397. package/src/routes/workspaces.ts +1004 -127
  398. package/src/sandbox/auth-callout.ts +96 -19
  399. package/src/sandbox/channel-a.ts +323 -24
  400. package/src/sandbox/connection-authority.ts +3 -0
  401. package/src/sandbox/enrollment.ts +81 -6
  402. package/src/sandbox/machines.ts +218 -76
  403. package/src/sandbox/metrics-ingestion.ts +411 -62
  404. package/src/sandbox/rematerialize.ts +121 -19
  405. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  406. package/src/sandbox/viewer.ts +280 -25
  407. package/src/sandbox-file-artifacts.ts +413 -0
  408. package/src/scheduled-task-deletion.ts +138 -0
  409. package/src/session-create-observability.ts +46 -0
  410. package/src/session-realtime-context.ts +6 -3
  411. package/src/site-session-origin.ts +48 -0
  412. package/src/site-uploads.ts +224 -0
  413. package/src/slack-reaction-files.ts +16 -5
  414. package/src/temporal-schedule-cleanup.ts +13 -0
  415. package/src/temporal-schedule-sync.ts +74 -0
  416. package/src/transcription/providers/codex-subscription.ts +2 -0
  417. package/src/transcription/providers/openai.ts +1 -0
  418. package/src/transcription/providers/xai-subscription.ts +49 -3
  419. package/src/transcription/segmenter.ts +8 -2
  420. package/src/transcription/service.ts +43 -6
  421. package/src/work-discovery-observability.ts +121 -0
  422. package/src/workspace-artifact-content.ts +144 -0
  423. package/src/workspace-artifact-provenance.ts +104 -0
  424. package/src/workspace-delete-observability.ts +75 -0
  425. package/src/workspace-deletion.ts +73 -0
  426. package/src/workspace-state-projection.ts +18 -148
  427. package/src/workspace-tool-gateway-observability.ts +100 -0
  428. package/src/workspace-tool-gateway.ts +870 -0
  429. package/src/workspace-webhook-dispatch.ts +190 -0
  430. package/src/xai-subscription-auth.ts +9 -2
  431. package/dist/chunk-WCXHI3FF.js +0 -49920
  432. package/dist/chunk-WCXHI3FF.js.map +0 -1
  433. package/dist/routes/packs.d.ts +0 -3
  434. package/src/routes/packs.ts +0 -729
@@ -1,8 +1,30 @@
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 {
@@ -36,12 +58,12 @@ import {
36
58
  } from "@opengeni/contracts";
37
59
  import {
38
60
  GOOGLE_DRIVE_INTEGRATION_DEFINITION,
61
+ CORE_INTEGRATION_DEFINITIONS,
39
62
  integrationDefinitionProviderDomain,
40
63
  } from "@opengeni/capabilities";
41
64
  import {
42
65
  captureScheduledTaskRestoreState,
43
66
  createValidatedScheduledTask,
44
- hasPermission,
45
67
  manualScheduledTaskTriggerWorkflowId,
46
68
  requireEnvironmentEncryption,
47
69
  syncCreatedScheduledTask,
@@ -51,11 +73,16 @@ import type { ApiRouteDeps } from "@opengeni/core";
51
73
  import {
52
74
  buildConnectionTokenResolver,
53
75
  configureIntegrationFacet,
76
+ integrationFacetConfigureRequestDigest,
54
77
  appendKnowledgeSourceAclVersion,
55
78
  deauthorizeKnowledgeSourceRetrieval,
56
79
  ConnectionDisconnectGenerationError,
57
80
  ConnectionDisconnectIdempotencyError,
58
81
  consumeIntegrationOAuthStateNonce,
82
+ finishConnectOperation,
83
+ getConnectAttempt,
84
+ type Database,
85
+ listInstalledApiIntegrations,
59
86
  createConnection,
60
87
  decryptEnvironmentValue,
61
88
  disconnectConnectionIdempotently,
@@ -64,15 +91,16 @@ import {
64
91
  getConnectionMetadata,
65
92
  getKnowledgeSourceByExternalIdentityForSyncAuthority,
66
93
  getKnowledgeSourceForSyncAuthority,
67
- getWorkspaceGrant,
68
94
  listKnowledgeSourceSyncTasksForConnection,
69
95
  loadConnectionCredentialForBroker,
70
96
  listIntegrationInstanceFacets,
97
+ replayCompletedIntegrationFacetOperation,
71
98
  transitionConnectionState,
72
99
  updateConnection,
73
100
  updateScheduledTask,
74
101
  upsertKnowledgeProvider,
75
102
  upsertKnowledgeSource,
103
+ withWorkspaceSubjectRls,
76
104
  recordKnowledgeLifecycleEvent,
77
105
  type PermanentConnectionRefreshFailure,
78
106
  } from "@opengeni/db";
@@ -80,8 +108,15 @@ import { googleDriveKnowledgeSourceIdentity } from "@opengeni/documents/google-d
80
108
  import { createSignedState, readSignedState } from "@opengeni/github";
81
109
  import { readResponseJsonBounded, type FetchLike } from "@opengeni/network";
82
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";
83
117
  import {
84
118
  integrationBaseUrl,
119
+ oauthStateFailureReturn,
85
120
  oauthStateTtlMs,
86
121
  requireIntegrationsStateSecret,
87
122
  } from "./oauth-client";
@@ -94,6 +129,17 @@ const GOOGLE_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
94
129
  const GOOGLE_REQUEST_TIMEOUT_MS = 10_000;
95
130
  const GOOGLE_DRIVE_PAGE_SIZE = 100;
96
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";
97
143
  const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
98
144
  "appNotAuthorizedToFile",
99
145
  "authError",
@@ -102,9 +148,13 @@ const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
102
148
  ]);
103
149
 
104
150
  type GoogleDriveOAuthState = {
151
+ connectAttemptId?: string;
152
+ externalContinuation?: ExternalActorContinuation;
105
153
  accountId: string;
106
154
  workspaceId: string;
107
155
  subjectId: string;
156
+ /** Signed proof that a live managed human started this personal-only flow. */
157
+ personalOwnerVerified: boolean;
108
158
  returnPath: string;
109
159
  encryptedPkceVerifier: string;
110
160
  capability: "source_read" | "publish";
@@ -155,7 +205,7 @@ export async function wakeGoogleDriveSourcesFromWorkspaceEvent(
155
205
  );
156
206
  let triggered = 0;
157
207
  for (const task of tasks) {
158
- if (task.action.kind !== "knowledge_source_sync" || task.status !== "active") continue;
208
+ if (!scheduledTaskKnowledgeSource(task) || task.status !== "active") continue;
159
209
  const externalSourceId =
160
210
  typeof task.metadata.externalSourceId === "string" ? task.metadata.externalSourceId : null;
161
211
  const selectedSource = externalSourceId ? selectedById.get(externalSourceId) : null;
@@ -212,8 +262,11 @@ export async function startGoogleDriveOAuth(
212
262
  subjectId: string;
213
263
  requestUrl: string;
214
264
  payload: GoogleDriveOAuthStartRequest;
265
+ connectAttemptId?: string;
266
+ externalContinuation?: ExternalActorContinuation;
215
267
  },
216
268
  ): Promise<GoogleDriveOAuthStartResponse> {
269
+ await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
217
270
  const google = requireGoogleDriveSettings(deps.settings);
218
271
  const existing = input.payload.connectionId
219
272
  ? await getConnectionMetadata(
@@ -237,12 +290,24 @@ export async function startGoogleDriveOAuth(
237
290
 
238
291
  const key = requireEnvironmentEncryption(deps.settings);
239
292
  const verifier = randomBytes(48).toString("base64url");
240
- const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
241
- const redirectUri = `${baseUrl}/v1/integrations/google-drive/callback`;
293
+ const redirectUri = requireGoogleDriveOAuthCallbackUrl(deps.settings);
242
294
  const state = createSignedState(requireIntegrationsStateSecret(deps.settings), {
243
295
  accountId: input.accountId,
244
296
  workspaceId: input.workspaceId,
245
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,
246
311
  returnPath: GOOGLE_DRIVE_RETURN_PATH(input.workspaceId),
247
312
  encryptedPkceVerifier: encryptEnvironmentValue(key, verifier),
248
313
  capability: input.payload.capability,
@@ -289,12 +354,43 @@ export async function completeGoogleDriveOAuthCallback(
289
354
  pickedFileIds?: string | undefined;
290
355
  requestUrl: string;
291
356
  },
292
- ): Promise<{ redirectTo: string }> {
357
+ ): Promise<{ redirectTo: string; exactReturn?: boolean }> {
358
+ const redirectUri = requireGoogleDriveOAuthCallbackUrl(deps.settings);
293
359
  const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
294
360
  const returnBaseUrl = deps.settings.webBaseUrl?.replace(/\/+$/, "") ?? baseUrl;
295
361
  let state: GoogleDriveOAuthState | null = null;
362
+ let exactReturnUrl: string | undefined;
363
+ let operation: { attemptId: string; operationId: string; inputDigest: string } | undefined;
296
364
  try {
297
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
+ }
298
394
  await requireGoogleDriveCallbackGrant(deps, state);
299
395
  const consumed = await consumeIntegrationOAuthStateNonce(deps.db, {
300
396
  accountId: state.accountId,
@@ -305,7 +401,26 @@ export async function completeGoogleDriveOAuthCallback(
305
401
  now: new Date(),
306
402
  });
307
403
  if (!consumed) {
308
- 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 };
309
424
  }
310
425
  if (input.error) {
311
426
  throw new GoogleDriveCallbackError("provider_denied");
@@ -313,11 +428,11 @@ export async function completeGoogleDriveOAuthCallback(
313
428
  if (!input.code) {
314
429
  throw new GoogleDriveCallbackError("missing_code");
315
430
  }
431
+ await withOrganizationIntegrationAcquisition(deps.db, state, ["google-drive"], async () => {});
316
432
 
317
433
  const google = requireGoogleDriveSettings(deps.settings);
318
434
  const key = requireEnvironmentEncryption(deps.settings);
319
435
  const verifier = decryptEnvironmentValue(key, state.encryptedPkceVerifier);
320
- const redirectUri = `${baseUrl}/v1/integrations/google-drive/callback`;
321
436
  const fetchImpl = deps.googleDriveFetch ?? fetch;
322
437
  const token = await exchangeGoogleAuthorizationCode(
323
438
  {
@@ -372,16 +487,6 @@ export async function completeGoogleDriveOAuthCallback(
372
487
  : previousMetadata?.outputDestination;
373
488
  if (state.capability === "publish") {
374
489
  if (!existing) throw new GoogleDriveCallbackError("connection_conflict");
375
- await ensureConnectorActionPolicyDefault(deps.db, {
376
- accountId: state.accountId,
377
- workspaceId: state.workspaceId,
378
- subjectId: state.subjectId,
379
- connectionId: existing.id,
380
- serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
381
- toolName: GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
382
- actionName: GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
383
- policy: "ask",
384
- });
385
490
  }
386
491
  let refreshToken = token.refreshToken;
387
492
  if (!refreshToken && existing) {
@@ -431,34 +536,92 @@ export async function completeGoogleDriveOAuthCallback(
431
536
  ? { selectedSources: [previousMetadata.selectedSource] }
432
537
  : {}),
433
538
  });
434
- const connection = existing
435
- ? await updateConnection(deps.db, {
436
- workspaceId: state.workspaceId,
437
- connectionId: existing.id,
438
- visibleToSubjectId: state.subjectId,
439
- expectedVersion: existing.version,
440
- subjectId: state.subjectId,
441
- providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
442
- kind: "oauth2",
443
- status: "active",
444
- credentialEncrypted,
445
- grantedScopes,
446
- expiresAt: token.expiresAt,
447
- metadata,
448
- updatedBySubjectId: state.subjectId,
449
- })
450
- : 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, {
451
546
  accountId: state.accountId,
452
547
  workspaceId: state.workspaceId,
453
548
  subjectId: state.subjectId,
454
- providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
455
- kind: "oauth2",
456
- credentialEncrypted,
457
- grantedScopes,
458
- expiresAt: token.expiresAt,
459
- metadata,
460
- 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",
461
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
+ );
462
625
  if (!connection) {
463
626
  throw new GoogleDriveCallbackError("connection_conflict");
464
627
  }
@@ -466,12 +629,16 @@ export async function completeGoogleDriveOAuthCallback(
466
629
  redirectTo: googleDriveReturnUrl(returnBaseUrl, state.returnPath, "connected", connection.id),
467
630
  };
468
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);
469
636
  return {
470
637
  redirectTo: googleDriveReturnUrl(
471
638
  returnBaseUrl,
472
- state?.returnPath ?? "/integrations",
639
+ state?.returnPath ?? failure!.returnPath,
473
640
  "error",
474
- googleDriveErrorReason(error),
641
+ failure?.reason ?? googleDriveErrorReason(error),
475
642
  ),
476
643
  };
477
644
  }
@@ -548,19 +715,45 @@ export async function transitionGoogleDriveLifecycle(
548
715
  );
549
716
  }
550
717
 
551
- const updated = await transitionConnectionState(deps.db, {
552
- workspaceId: input.workspaceId,
553
- connectionId: existing.id,
554
- visibleToSubjectId: input.subjectId,
555
- expectedVersion: existing.version,
556
- status: "active",
557
- metadata: GoogleDriveConnectionMetadata.parse({
558
- ...metadata,
559
- lifecycle: googleDriveLifecycle(targetState),
560
- }),
561
- lastError: null,
562
- updatedBySubjectId: input.subjectId,
563
- });
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);
564
757
  if (!updated) {
565
758
  const converged = await getConnectionMetadata(
566
759
  deps.db,
@@ -679,20 +872,20 @@ async function deauthorizeGoogleDriveConnectionSources(
679
872
  connection.workspaceId,
680
873
  connection.id,
681
874
  async (task) => {
682
- if (task.action.initiatingSubjectId !== subjectId) return;
875
+ if (requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== subjectId) return;
683
876
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
684
877
  accountId: task.accountId,
685
878
  workspaceId: task.workspaceId,
686
- sourceId: task.action.sourceId,
879
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
687
880
  initiatingSubjectId: subjectId,
688
881
  });
689
882
  if (!resolved || resolved.source.lifecycleState !== "active") return;
690
883
  await deauthorizeKnowledgeSourceRetrieval(deps.db, {
691
884
  accountId: task.accountId,
692
885
  workspaceId: task.workspaceId,
693
- sourceId: task.action.sourceId,
694
- audience: task.action.destination,
695
- 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}`,
696
889
  reasonCode,
697
890
  actor: {
698
891
  kind: "human",
@@ -708,7 +901,7 @@ async function forEachGoogleDriveConnectionTask(
708
901
  deps: ApiRouteDeps,
709
902
  workspaceId: string,
710
903
  connectionId: string,
711
- fn: (task: ScheduledTask & { action: { kind: "knowledge_source_sync" } }) => Promise<void>,
904
+ fn: (task: ScheduledTask) => Promise<void>,
712
905
  ): Promise<void> {
713
906
  const tasks = await listKnowledgeSourceSyncTasksForConnection(deps.db, workspaceId, connectionId);
714
907
  for (const task of tasks) {
@@ -829,63 +1022,160 @@ export async function saveGoogleDriveFacetSource(
829
1022
  throw new HTTPException(400, { message: "invalid Google Drive source selection" });
830
1023
  }
831
1024
  const payload = parsedPayload.data;
832
- const context = await requireGoogleDriveIntegrationFacet(deps, input);
833
- const documentDestination = googleDriveDocumentDestination(input, payload);
834
- const verifiedSources = await verifyGoogleDriveSources(deps, {
835
- workspaceId: input.workspaceId,
836
- subjectId: input.subjectId,
837
- connectionId: context.connection.id,
838
- sources: payload.sources,
839
- });
840
- const config = GoogleDriveKnowledgeSourceConfig.parse({
841
- sources: verifiedSources.map((verified) => ({
842
- id: verified.id,
843
- name: verified.name,
844
- mimeType: verified.mimeType,
845
- ...(verified.driveId ? { driveId: verified.driveId } : {}),
846
- sourceKind:
847
- verified.id === "root"
848
- ? "my_drive"
849
- : verified.driveId === verified.id
850
- ? "shared_drive"
851
- : "folder",
852
- includeDescendants: true,
853
- })),
854
- destination: {
855
- authorityKind: documentDestination.authorityKind,
856
- authorityAccountId: documentDestination.authorityAccountId,
857
- ...(documentDestination.authorityWorkspaceId
858
- ? { authorityWorkspaceId: documentDestination.authorityWorkspaceId }
859
- : {}),
860
- ...(documentDestination.authoritySubjectId
861
- ? { authoritySubjectId: documentDestination.authoritySubjectId }
862
- : {}),
863
- ...(documentDestination.collectionId
864
- ? { collectionId: documentDestination.collectionId }
865
- : {}),
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 };
866
1087
  },
867
- syncCadence: payload.syncCadence,
868
- readPolicy: payload.readPolicy,
869
- });
870
- return IntegrationFacetMutationResult.parse(
871
- await configureIntegrationFacet(deps.db, {
872
- accountId: input.accountId,
1088
+ );
1089
+ if (admitted.replayed) return admitted.replayed;
1090
+ const context = admitted.context!;
1091
+ if (admitted.acquiring)
1092
+ await verifyGoogleDriveSources(deps, {
873
1093
  workspaceId: input.workspaceId,
874
1094
  subjectId: input.subjectId,
875
- capabilityId: input.capabilityId,
876
- instanceKey: input.instanceKey,
877
- facetKey: input.facetKey,
878
- displayName:
879
- context.facet.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
880
- config,
881
- ...(payload.expectedVersion !== undefined
882
- ? { expectedVersion: payload.expectedVersion }
883
- : {}),
884
- idempotencyKey: payload.idempotencyKey,
885
- }),
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),
886
1169
  );
887
1170
  }
888
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
+
889
1179
  export async function saveGoogleDriveSource(
890
1180
  deps: ApiRouteDeps,
891
1181
  input: {
@@ -917,14 +1207,34 @@ export async function saveGoogleDriveSource(
917
1207
  if (existing.accountId !== input.accountId || existing.workspaceId !== input.workspaceId) {
918
1208
  throw new HTTPException(403, { message: "Google Drive connection authority mismatch" });
919
1209
  }
920
- await requireGoogleDriveSourceConnection(deps, existing, input.subjectId);
1210
+ const existingMetadata = await requireGoogleDriveSourceConnection(
1211
+ deps,
1212
+ existing,
1213
+ input.subjectId,
1214
+ );
921
1215
  const documentDestination = googleDriveDocumentDestination(input, payload);
922
- const verifiedSources = await verifyGoogleDriveSources(deps, {
923
- workspaceId: input.workspaceId,
924
- subjectId: input.subjectId,
925
- connectionId: input.connectionId,
926
- sources: payload.sources,
927
- });
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;
928
1238
  const latest =
929
1239
  (await getConnectionMetadata(
930
1240
  deps.db,
@@ -936,33 +1246,46 @@ export async function saveGoogleDriveSource(
936
1246
  const previousSources =
937
1247
  latestMetadata.selectedSources ??
938
1248
  (latestMetadata.selectedSource ? [latestMetadata.selectedSource] : []);
939
- const updated = await transitionConnectionState(deps.db, {
940
- workspaceId: input.workspaceId,
941
- connectionId: latest.id,
942
- visibleToSubjectId: input.subjectId,
943
- expectedVersion: latest.version,
944
- metadata: GoogleDriveConnectionMetadata.parse({
945
- ...latestMetadata,
946
- documentDestination,
947
- selectedSource: null,
948
- selectedSources: verifiedSources.map((verified) => {
949
- const previous = previousSources.find((source) => source.id === verified.id);
950
- return {
951
- id: verified.id,
952
- name: verified.name,
953
- mimeType: verified.mimeType,
954
- driveId: verified.driveId,
955
- destination: documentDestination,
956
- syncCadence: payload.syncCadence,
957
- syncEnabled: payload.syncEnabled,
958
- configGeneration: (previous?.configGeneration ?? 0) + 1,
959
- readPolicy: payload.readPolicy,
960
- selectedAt: new Date().toISOString(),
961
- };
962
- }),
963
- }),
964
- updatedBySubjectId: input.subjectId,
965
- });
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
+ );
966
1289
  if (!updated) {
967
1290
  throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
968
1291
  }
@@ -1108,6 +1431,24 @@ function googleDriveDocumentDestination(
1108
1431
  if (destinationSelection.authorityKind === "personal" && !input.canManagePersonalDestination) {
1109
1432
  throw new HTTPException(403, { message: "personal destination requires the exact actor" });
1110
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
+ };
1111
1452
  return bindConnectorDocumentDestination(destinationSelection, {
1112
1453
  accountId: input.accountId,
1113
1454
  workspaceId: input.workspaceId,
@@ -1115,6 +1456,51 @@ function googleDriveDocumentDestination(
1115
1456
  });
1116
1457
  }
1117
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
+
1118
1504
  async function verifyGoogleDriveSources(
1119
1505
  deps: ApiRouteDeps,
1120
1506
  input: {
@@ -1187,11 +1573,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
1187
1573
  let source = null as Awaited<ReturnType<typeof upsertKnowledgeSource>> | null;
1188
1574
  let existingTask = null as (typeof connectionTasks)[number] | null;
1189
1575
  for (const task of connectionTasks) {
1190
- if (task.action.kind !== "knowledge_source_sync") continue;
1576
+ if (!scheduledTaskKnowledgeSource(task)) continue;
1191
1577
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1192
1578
  accountId: input.accountId,
1193
1579
  workspaceId: input.workspaceId,
1194
- sourceId: task.action.sourceId,
1580
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1195
1581
  initiatingSubjectId: input.subjectId,
1196
1582
  });
1197
1583
  if (resolved?.source.externalSourceId !== identity.externalSourceId) continue;
@@ -1282,13 +1668,8 @@ async function materializeGoogleDriveKnowledgeSchedules(
1282
1668
  kind: input.connection.kind,
1283
1669
  },
1284
1670
  limits: {
1285
- maxItems: 500,
1286
- maxBytes: 500_000_000,
1287
- maxFileBytes: 100_000_000,
1288
- maxProviderRequests: 1_000,
1289
- maxElapsedSeconds: 300,
1671
+ ...configuredGoogleDriveSyncLimits(deps.settings),
1290
1672
  maxConcurrency: 4,
1291
- maxFailureDetails: 25,
1292
1673
  },
1293
1674
  };
1294
1675
  const schedule = googleDriveSchedule(selectedSource.syncCadence);
@@ -1298,7 +1679,10 @@ async function materializeGoogleDriveKnowledgeSchedules(
1298
1679
  task = await updateScheduledTask(deps.db, input.workspaceId, existingTask.id, {
1299
1680
  name: `Sync Google Drive: ${selectedSource.name}`,
1300
1681
  overlapPolicy: "buffer_one",
1301
- action,
1682
+ action: { kind: "agent_turn" },
1683
+ agentConfig: knowledgeSourceAgentConfig(action, existingTask.agentConfig),
1684
+ refreshPersonalResourceAuthority: true,
1685
+ authorityUpdatedBy: { kind: "subject", subjectId: input.subjectId },
1302
1686
  metadata: {
1303
1687
  ...existingTask.metadata,
1304
1688
  connectorKind: "google_drive",
@@ -1330,15 +1714,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
1330
1714
  status: "active",
1331
1715
  schedule,
1332
1716
  overlapPolicy: "buffer_one",
1333
- action,
1717
+ action: { kind: "agent_turn" },
1334
1718
  runMode: "new_session_per_run",
1335
1719
  targetSessionId: null,
1336
- agentConfig: {
1337
- prompt: "Knowledge source synchronization",
1338
- resources: [],
1339
- tools: [],
1340
- metadata: {},
1341
- },
1720
+ connectionAccounts: [],
1721
+ agentConfig: knowledgeSourceAgentConfig(action),
1342
1722
  variableSetId: null,
1343
1723
  environmentId: null,
1344
1724
  rigId: null,
@@ -1363,11 +1743,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
1363
1743
  }
1364
1744
 
1365
1745
  for (const task of connectionTasks) {
1366
- if (task.action.kind !== "knowledge_source_sync") continue;
1746
+ if (!scheduledTaskKnowledgeSource(task)) continue;
1367
1747
  const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
1368
1748
  accountId: input.accountId,
1369
1749
  workspaceId: input.workspaceId,
1370
- sourceId: task.action.sourceId,
1750
+ sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
1371
1751
  initiatingSubjectId: input.subjectId,
1372
1752
  });
1373
1753
  if (!resolved || enabledIds.has(resolved.source.externalSourceId)) continue;
@@ -1406,15 +1786,15 @@ async function materializeGoogleDriveKnowledgeSchedules(
1406
1786
  }
1407
1787
  }
1408
1788
 
1409
- export async function revokeKnowledgeSourceScheduleAuthorization(
1789
+ export async function preflightKnowledgeSourceScheduleAuthorization(
1410
1790
  deps: ApiRouteDeps,
1411
1791
  input: { task: ScheduledTask; subjectId: string },
1412
1792
  ): Promise<void> {
1413
1793
  const { task } = input;
1414
- if (task.action.kind !== "knowledge_source_sync") return;
1794
+ if (!scheduledTaskKnowledgeSource(task)) return;
1415
1795
  if (
1416
- task.action.initiatingSubjectId !== input.subjectId ||
1417
- task.action.connection.ownerSubjectId !== input.subjectId
1796
+ requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
1797
+ requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
1418
1798
  ) {
1419
1799
  throw new HTTPException(403, {
1420
1800
  message: "knowledge source schedule requires the exact initiating subject",
@@ -1430,65 +1810,138 @@ export async function revokeKnowledgeSourceScheduleAuthorization(
1430
1810
  if (!externalSourceId) {
1431
1811
  throw new HTTPException(409, { message: "knowledge source schedule identity is incomplete" });
1432
1812
  }
1433
- const connection = await getConnectionMetadata(
1434
- deps.db,
1435
- task.workspaceId,
1436
- task.action.connection.connectionId,
1437
- input.subjectId,
1438
- );
1439
- if (!connection) {
1440
- throw new HTTPException(409, {
1441
- 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",
1442
1830
  });
1443
1831
  }
1444
- const metadata = requireGoogleDriveConnection(connection, input.subjectId);
1445
- const sources =
1446
- metadata.selectedSources ?? (metadata.selectedSource ? [metadata.selectedSource] : []);
1447
- const selected = sources.find((source) => source.id === externalSourceId);
1448
- let revocationVersion = connection.version;
1449
- if (selected?.syncEnabled) {
1450
- const updated = await transitionConnectionState(deps.db, {
1451
- workspaceId: task.workspaceId,
1452
- connectionId: connection.id,
1453
- visibleToSubjectId: input.subjectId,
1454
- expectedVersion: connection.version,
1455
- metadata: GoogleDriveConnectionMetadata.parse({
1456
- ...metadata,
1457
- selectedSource: null,
1458
- selectedSources: sources.map((source) =>
1459
- source.id === externalSourceId
1460
- ? {
1461
- ...source,
1462
- syncEnabled: false,
1463
- configGeneration: source.configGeneration + 1,
1464
- }
1465
- : source,
1466
- ),
1467
- }),
1468
- 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",
1469
1835
  });
1470
- if (!updated) {
1471
- throw new HTTPException(409, {
1472
- message: "knowledge source connection changed; retry schedule deletion",
1473
- });
1474
- }
1475
- revocationVersion = updated.version;
1476
1836
  }
1477
- 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,
1478
1844
  accountId: task.accountId,
1479
1845
  workspaceId: task.workspaceId,
1480
- 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,
1481
1875
  initiatingSubjectId: input.subjectId,
1482
1876
  });
1483
- if (resolved?.source.lifecycleState === "active") {
1484
- await recordKnowledgeLifecycleEvent(deps.db, {
1485
- accountId: task.accountId,
1486
- 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,
1487
1940
  targetKind: "source",
1488
1941
  targetId: resolved.source.id,
1489
1942
  eventType: "deleted",
1490
1943
  expectedGeneration: resolved.source.lifecycleGeneration,
1491
- operationId: `knowledge-schedule-delete:${task.id}:${revocationVersion}`,
1944
+ operationId: `knowledge-schedule-delete:${input.taskId}:${revocationVersion}`,
1492
1945
  reasonCode: "schedule_deleted",
1493
1946
  actor: {
1494
1947
  kind: "human",
@@ -1496,7 +1949,7 @@ export async function revokeKnowledgeSourceScheduleAuthorization(
1496
1949
  initiatingHumanSubjectId: input.subjectId,
1497
1950
  },
1498
1951
  });
1499
- }
1952
+ });
1500
1953
  }
1501
1954
 
1502
1955
  function knowledgeSourceScheduleControl(task: ScheduledTask): {
@@ -1614,6 +2067,16 @@ function requireGoogleDriveSettings(settings: Settings): {
1614
2067
  return { clientId, clientSecret };
1615
2068
  }
1616
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
+
1617
2080
  function requireGoogleDriveConnection(connection: GoogleDriveConnectionRecord, subjectId: string) {
1618
2081
  const parsed = GoogleDriveConnectionMetadata.safeParse(connection.metadata);
1619
2082
  if (
@@ -1799,6 +2262,13 @@ function readGoogleDriveOAuthState(
1799
2262
  if (iat === undefined || now < iat || now - iat > oauthStateTtlMs / 1000) {
1800
2263
  throw new HTTPException(400, { message: "invalid or expired Google Drive OAuth state" });
1801
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
+ }
1802
2272
  const accountId = requiredString(payload.accountId, "state.accountId");
1803
2273
  const workspaceId = requiredString(payload.workspaceId, "state.workspaceId");
1804
2274
  const subjectId = requiredString(payload.subjectId, "state.subjectId");
@@ -1823,6 +2293,22 @@ function readGoogleDriveOAuthState(
1823
2293
  accountId,
1824
2294
  workspaceId,
1825
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
+ : {}),
1826
2312
  returnPath,
1827
2313
  encryptedPkceVerifier: requiredString(
1828
2314
  payload.encryptedPkceVerifier,
@@ -1839,16 +2325,22 @@ async function requireGoogleDriveCallbackGrant(
1839
2325
  deps: ApiRouteDeps,
1840
2326
  state: GoogleDriveOAuthState,
1841
2327
  ): Promise<void> {
1842
- 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.
1843
2332
  if (
1844
- !grant ||
1845
- grant.accountId !== state.accountId ||
1846
- !hasPermission(grant.permissions, "connections:write")
2333
+ !personalOwnerStateAccepted({
2334
+ ownership: "personal",
2335
+ subjectId: state.subjectId,
2336
+ personalOwnerVerified: state.personalOwnerVerified,
2337
+ })
1847
2338
  ) {
1848
- throw new HTTPException(403, {
1849
- message: "Google Drive OAuth subject no longer has permission for this workspace",
2339
+ throw new HTTPException(422, {
2340
+ message: personalOnlyConnectionPrincipalMessage("Google Drive"),
1850
2341
  });
1851
2342
  }
2343
+ await requireConnectOwnerAuthority(deps.db, state, "connections:write");
1852
2344
  }
1853
2345
 
1854
2346
  async function exchangeGoogleAuthorizationCode(