@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.
- package/dist/access-grant-rls.d.ts +3 -0
- package/dist/admission-trace.d.ts +8 -0
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.d.ts +50 -2
- package/dist/app.js +12 -1
- package/dist/artifact-catalog-cursor.d.ts +27 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
- package/dist/auth/managed-auth.d.ts +102 -3
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/organization-user-setup.d.ts +80 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-6TZ5LG3O.js +123948 -0
- package/dist/chunk-6TZ5LG3O.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-subscription-account-conflicts.d.ts +1 -0
- package/dist/claude-subscription-account-usage.d.ts +39 -0
- package/dist/claude-subscription-oauth.d.ts +25 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +44 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +32 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-ownership.d.ts +54 -0
- package/dist/controller-data-plane.d.ts +32 -0
- package/dist/editable-artifact-office-import.d.ts +7 -4
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +10 -18
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/github-access.d.ts +31 -2
- package/dist/http/acting-person.d.ts +24 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +27 -1
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +52 -0
- package/dist/http/sse.d.ts +17 -6
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1129 -166
- package/dist/index.js.map +1 -1
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +29 -3
- package/dist/integrations/connect-authority.d.ts +1 -0
- package/dist/integrations/connect-callback-return.d.ts +6 -0
- package/dist/integrations/fiken.d.ts +16 -2
- package/dist/integrations/github-app-connect.d.ts +47 -0
- package/dist/integrations/github-installation-proof.d.ts +3 -0
- package/dist/integrations/github-lens-connect.d.ts +116 -0
- package/dist/integrations/github-skill-source.d.ts +1 -5
- package/dist/integrations/google-drive.d.ts +25 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +2691 -0
- package/dist/integrations/oauth-client.d.ts +178 -18
- package/dist/integrations/oauth-profiles.d.ts +222 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github-repositories.d.ts +84 -0
- package/dist/integrations/personal-github.d.ts +42 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +19 -1
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +367 -12
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/slack-interactions.d.ts +113 -10
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/social-oauth.d.ts +7 -0
- package/dist/interaction-agent-access.d.ts +15 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/action-catalog-types.d.ts +9 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/command-read.d.ts +24 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/contract-input.d.ts +11 -0
- package/dist/mcp/documents.d.ts +7 -5
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-input.d.ts +1189 -0
- package/dist/mcp/scheduled-task-view.d.ts +22 -4
- package/dist/mcp/server.d.ts +255 -3
- package/dist/mcp/session-event-view.d.ts +94 -0
- package/dist/mcp/session-view.d.ts +66 -2
- package/dist/mcp/session-wait.d.ts +174 -0
- package/dist/mcp-oauth.d.ts +52 -0
- package/dist/model-catalog.d.ts +11 -31
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +52 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +25 -0
- package/dist/routes/api-keys.d.ts +17 -0
- package/dist/routes/artifact-catalog.d.ts +6 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/billing.d.ts +49 -0
- package/dist/routes/browser-sessions.d.ts +71 -3
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +146 -0
- package/dist/routes/codex.d.ts +76 -2
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/computer-sessions.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/editable-artifacts.d.ts +2 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/feedback.d.ts +3 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/github-browser-pages.d.ts +12 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +30 -1
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/managed-auth-session-sets.d.ts +26 -0
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-connection-access.d.ts +3 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/organization-sessions.d.ts +26 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +9 -0
- package/dist/routes/sessions.d.ts +44 -6
- package/dist/routes/skill-content.d.ts +4 -0
- package/dist/routes/skill-install-authority.d.ts +6 -0
- package/dist/routes/subscription-account-pools.d.ts +34 -0
- package/dist/routes/subscription-pool-access.d.ts +16 -0
- package/dist/routes/supergrok.d.ts +6 -1
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +29 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +19 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +9 -1
- package/dist/sandbox/machines.d.ts +5 -2
- package/dist/sandbox/metrics-ingestion.d.ts +28 -20
- package/dist/sandbox/rematerialize.d.ts +2 -0
- package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
- package/dist/sandbox/viewer.d.ts +26 -1
- package/dist/sandbox-file-artifacts.d.ts +50 -0
- package/dist/scheduled-task-deletion.d.ts +13 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-MPS2OZD3.js +292 -0
- package/dist/session-event-view-MPS2OZD3.js.map +1 -0
- package/dist/site-session-origin.d.ts +13 -0
- package/dist/site-uploads.d.ts +41 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +1 -0
- package/dist/transcription/service.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +123 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/package.json +23 -19
- package/src/access-grant-rls.ts +75 -0
- package/src/admission-trace.ts +14 -0
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +1974 -256
- package/src/artifact-catalog-cursor.ts +60 -0
- package/src/auth/managed-auth-attempt-context.ts +70 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +222 -0
- package/src/auth/managed-auth.ts +536 -60
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +307 -0
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-subscription-account-conflicts.ts +12 -0
- package/src/claude-subscription-account-usage.ts +48 -0
- package/src/claude-subscription-oauth.ts +362 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +97 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +194 -30
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-ownership.ts +199 -0
- package/src/controller-data-plane.ts +81 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +77 -9
- package/src/editable-artifact-production.ts +23 -7
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +27 -21
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/fatal-process-boundary.ts +246 -0
- package/src/gateway-realtime.ts +1 -1
- package/src/github-access.ts +147 -5
- package/src/http/acting-person.ts +71 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +115 -1
- package/src/http/auth.ts +48 -0
- package/src/http/cors.ts +67 -0
- package/src/http/deprecation.ts +118 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +178 -0
- package/src/http/sse.ts +486 -132
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +157 -52
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +98 -11
- package/src/integrations/atlassian.ts +474 -136
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +354 -64
- package/src/integrations/github-app-connect.ts +435 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +76 -0
- package/src/integrations/github-skill-source.ts +2 -142
- package/src/integrations/google-drive.ts +820 -222
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +48 -0
- package/src/integrations/oauth-client.ts +1356 -459
- package/src/integrations/oauth-profiles.ts +537 -0
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github-repositories.ts +674 -0
- package/src/integrations/personal-github.ts +825 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +250 -22
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +963 -239
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/slack-interactions.ts +3170 -474
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/social-oauth.ts +213 -15
- package/src/interaction-agent-access.ts +48 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/action-catalog-types.ts +9 -0
- package/src/mcp/action-catalog.gen.ts +6696 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/command-read.ts +109 -0
- package/src/mcp/company-profile-agent-admin.ts +198 -0
- package/src/mcp/contract-input.ts +141 -0
- package/src/mcp/documents.ts +65 -377
- package/src/mcp/editable-artifacts.ts +12 -6
- package/src/mcp/files.ts +80 -38
- package/src/mcp/first-party-tool-permissions.ts +305 -0
- package/src/mcp/knowledge-entries.ts +322 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-input.ts +76 -0
- package/src/mcp/scheduled-task-view.ts +44 -3
- package/src/mcp/server.ts +3030 -1267
- package/src/mcp/session-event-view.ts +383 -0
- package/src/mcp/session-view.ts +84 -3
- package/src/mcp/session-wait.ts +665 -0
- package/src/mcp-oauth.ts +1123 -0
- package/src/model-catalog.ts +91 -333
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +397 -0
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +364 -74
- package/src/routes/api-keys.ts +336 -10
- package/src/routes/artifact-catalog.ts +279 -0
- package/src/routes/automations.ts +768 -0
- package/src/routes/billing.ts +461 -46
- package/src/routes/browser-sessions.ts +839 -131
- package/src/routes/canonical-human-identities.ts +90 -14
- package/src/routes/capabilities.ts +107 -6
- package/src/routes/channels.ts +20 -3
- package/src/routes/claude-subscription-accounts.ts +334 -0
- package/src/routes/claude-subscription-oauth.ts +142 -0
- package/src/routes/client-errors.ts +362 -0
- package/src/routes/codex.ts +833 -116
- package/src/routes/company-brain.ts +322 -0
- package/src/routes/company-profile.ts +65 -1
- package/src/routes/computer-sessions.ts +557 -106
- package/src/routes/connect.ts +1938 -0
- package/src/routes/connections.ts +1040 -349
- package/src/routes/documents.ts +488 -287
- package/src/routes/editable-artifacts.ts +79 -9
- package/src/routes/enrollments.ts +135 -39
- package/src/routes/environments.ts +154 -58
- package/src/routes/external-identity-links.ts +201 -0
- package/src/routes/feedback.ts +113 -0
- package/src/routes/files.ts +337 -39
- package/src/routes/github-browser-pages.ts +162 -0
- package/src/routes/github.ts +446 -144
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +153 -19
- package/src/routes/install.ts +133 -38
- package/src/routes/integration-facets.ts +107 -33
- package/src/routes/interaction-resources.ts +30 -3
- package/src/routes/knowledge.ts +488 -0
- package/src/routes/machines.ts +313 -15
- package/src/routes/managed-auth-session-sets.ts +1218 -0
- package/src/routes/managed-onboarding.ts +337 -0
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-connection-access.ts +208 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +178 -0
- package/src/routes/organization-memberships.ts +1019 -27
- package/src/routes/organization-model-providers.ts +293 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +350 -0
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +370 -0
- package/src/routes/plugins.ts +251 -22
- package/src/routes/pr-review-github.ts +881 -0
- package/src/routes/pr-review.ts +622 -0
- package/src/routes/preference-registry.ts +12 -93
- package/src/routes/rigs.ts +167 -50
- package/src/routes/scheduled-tasks.ts +346 -41
- package/src/routes/session-artifact-associations.ts +87 -0
- package/src/routes/session-history-imports.ts +280 -0
- package/src/routes/sessions.ts +2306 -264
- package/src/routes/skill-content.ts +297 -0
- package/src/routes/skill-install-authority.ts +64 -0
- package/src/routes/skills.ts +62 -9
- package/src/routes/social.ts +46 -18
- package/src/routes/subscription-account-pools.ts +436 -0
- package/src/routes/subscription-pool-access.ts +126 -0
- package/src/routes/supergrok.ts +219 -303
- package/src/routes/transcription-recordings.ts +38 -3
- package/src/routes/transcriptions.ts +4 -0
- package/src/routes/usage-allowances.ts +378 -0
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/video-generation.ts +14 -10
- package/src/routes/workspace-artifacts.ts +275 -69
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-learning.ts +159 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +38 -25
- package/src/routes/workspaces.ts +1004 -127
- package/src/sandbox/auth-callout.ts +96 -19
- package/src/sandbox/channel-a.ts +323 -24
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +81 -6
- package/src/sandbox/machines.ts +218 -76
- package/src/sandbox/metrics-ingestion.ts +411 -62
- package/src/sandbox/rematerialize.ts +121 -19
- package/src/sandbox/session-attach-variable-sets.ts +131 -0
- package/src/sandbox/viewer.ts +280 -25
- package/src/sandbox-file-artifacts.ts +413 -0
- package/src/scheduled-task-deletion.ts +138 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +6 -3
- package/src/site-session-origin.ts +48 -0
- package/src/site-uploads.ts +224 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/providers/codex-subscription.ts +2 -0
- package/src/transcription/providers/openai.ts +1 -0
- package/src/transcription/providers/xai-subscription.ts +49 -3
- package/src/transcription/segmenter.ts +8 -2
- package/src/transcription/service.ts +43 -6
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-artifact-content.ts +144 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/src/workspace-deletion.ts +73 -0
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +870 -0
- package/src/workspace-webhook-dispatch.ts +190 -0
- package/src/xai-subscription-auth.ts +9 -2
- package/dist/chunk-2PQMSRCB.js +0 -49759
- package/dist/chunk-2PQMSRCB.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- 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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
309
|
-
!scopeDecision.capabilities.includes(
|
|
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:
|
|
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
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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 ??
|
|
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
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
641
|
-
audience: task.
|
|
642
|
-
operationId: `google-drive-deauthorize:${connection.id}:${authorityVersion}:${task.
|
|
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
|
|
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
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
:
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
await
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
:
|
|
831
|
-
|
|
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(
|
|
1210
|
+
const existingMetadata = await requireGoogleDriveSourceConnection(
|
|
1211
|
+
deps,
|
|
1212
|
+
existing,
|
|
1213
|
+
input.subjectId,
|
|
1214
|
+
);
|
|
868
1215
|
const documentDestination = googleDriveDocumentDestination(input, payload);
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1237
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
1794
|
+
if (!scheduledTaskKnowledgeSource(task)) return;
|
|
1315
1795
|
if (
|
|
1316
|
-
task.
|
|
1317
|
-
task.
|
|
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
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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:${
|
|
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
|
-
|
|
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
|
-
!
|
|
1739
|
-
|
|
1740
|
-
|
|
2333
|
+
!personalOwnerStateAccepted({
|
|
2334
|
+
ownership: "personal",
|
|
2335
|
+
subjectId: state.subjectId,
|
|
2336
|
+
personalOwnerVerified: state.personalOwnerVerified,
|
|
2337
|
+
})
|
|
1741
2338
|
) {
|
|
1742
|
-
throw new HTTPException(
|
|
1743
|
-
message: "Google Drive
|
|
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
|
|
2576
|
+
await requireGoogleDriveApiConnection(deps, resolved, input.subjectId);
|
|
1979
2577
|
};
|
|
1980
2578
|
let credential = await resolve(false);
|
|
1981
2579
|
if (credential.status !== "ok") {
|