@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.
- 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-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 +1091 -162
- 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 +24 -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 +107 -9
- 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 +59 -2
- 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/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 +1967 -252
- 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 +28 -3
- 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 +717 -225
- 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 +3167 -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 -43
- 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 +780 -112
- 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 +1039 -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 +267 -33
- 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-WCXHI3FF.js +0 -49920
- package/dist/chunk-WCXHI3FF.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
package/dist/index.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
+
InteractionFrameProxyTransport,
|
|
2
3
|
SlackBotProviderError,
|
|
4
|
+
apiRequestBindingsForTransportPeer,
|
|
5
|
+
cleanupScheduledTaskConnectorAuthorization,
|
|
3
6
|
createAppComposition,
|
|
7
|
+
createLocalBrowserBoundary,
|
|
4
8
|
createOpenGeniSlackBotInteractionClient,
|
|
9
|
+
localBrowserBoundaryResponse,
|
|
10
|
+
sql,
|
|
11
|
+
startApiMetricsListener,
|
|
5
12
|
startSlackInteractionPump,
|
|
6
|
-
startTemporalScheduleCleanupPump
|
|
7
|
-
|
|
13
|
+
startTemporalScheduleCleanupPump,
|
|
14
|
+
withAccessGrantSessionRlsContext
|
|
15
|
+
} from "./chunk-6TZ5LG3O.js";
|
|
16
|
+
import "./chunk-OGQDXVXY.js";
|
|
8
17
|
|
|
9
18
|
// src/index.ts
|
|
19
|
+
import { scheduledTaskKnowledgeSource } from "@opengeni/contracts";
|
|
10
20
|
import {
|
|
11
21
|
dbSearchPath,
|
|
12
22
|
getSettings,
|
|
23
|
+
resolveFirstPartyDelegationSecret,
|
|
13
24
|
resolveNatsCalloutConfig,
|
|
14
25
|
resolveNatsControlPlaneAuth,
|
|
15
26
|
retryStartupDependency,
|
|
@@ -18,15 +29,22 @@ import {
|
|
|
18
29
|
} from "@opengeni/config";
|
|
19
30
|
import {
|
|
20
31
|
assertRuntimeDatabasePosture,
|
|
32
|
+
isRetryableRuntimeDatabaseStartupError,
|
|
21
33
|
createDb,
|
|
22
34
|
markSessionWorkflowWakeDelivered,
|
|
23
35
|
runtimeDatabaseReadyCheck
|
|
24
36
|
} from "@opengeni/db";
|
|
25
37
|
import { createNatsEventBus } from "@opengeni/events";
|
|
26
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
createObservability,
|
|
40
|
+
logStartupDependencyRetry
|
|
41
|
+
} from "@opengeni/observability";
|
|
27
42
|
import { createObjectStorage } from "@opengeni/storage";
|
|
28
43
|
import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
|
|
29
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
resolveCatalogSettings,
|
|
46
|
+
SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID
|
|
47
|
+
} from "@opengeni/core";
|
|
30
48
|
import {
|
|
31
49
|
Connection,
|
|
32
50
|
Client as TemporalClient,
|
|
@@ -36,6 +54,15 @@ import {
|
|
|
36
54
|
} from "@temporalio/client";
|
|
37
55
|
|
|
38
56
|
// src/observability.ts
|
|
57
|
+
import {
|
|
58
|
+
natsSubscriptionTerminationCounter
|
|
59
|
+
} from "@opengeni/events";
|
|
60
|
+
function observabilityEventBusOptions(observability) {
|
|
61
|
+
return {
|
|
62
|
+
logger: observabilityEventLogger(observability),
|
|
63
|
+
onSubscriptionTerminated: natsSubscriptionTerminationCounter(observability)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
39
66
|
function observabilityEventLogger(observability) {
|
|
40
67
|
return {
|
|
41
68
|
debug: (message, attributes) => observability.debug(message, eventAttributes(attributes)),
|
|
@@ -64,20 +91,45 @@ function eventAttributeValue(value) {
|
|
|
64
91
|
}
|
|
65
92
|
|
|
66
93
|
// src/sandbox/auth-callout.ts
|
|
94
|
+
import { createHash } from "crypto";
|
|
95
|
+
import { createLogThrottle } from "@opengeni/observability";
|
|
67
96
|
import {
|
|
68
97
|
resolveEnrollmentSigningSecret
|
|
69
98
|
} from "@opengeni/config";
|
|
70
99
|
import { verifyEnrollmentBearer } from "@opengeni/contracts";
|
|
71
|
-
import { getEnrollment } from "@opengeni/db";
|
|
100
|
+
import { claimEnrollmentConnection, getEnrollment } from "@opengeni/db";
|
|
72
101
|
import {
|
|
73
102
|
createResponderConnection,
|
|
74
103
|
decodeAuthRequest,
|
|
75
104
|
mintAuthResponse,
|
|
76
105
|
mintUserJwt,
|
|
106
|
+
parseAgentConnectionName,
|
|
77
107
|
workspaceAgentPermissions
|
|
78
108
|
} from "@opengeni/events";
|
|
109
|
+
|
|
110
|
+
// src/sandbox/connection-authority.ts
|
|
111
|
+
var AGENT_CONNECTION_LEASE_MS = 2e4;
|
|
112
|
+
|
|
113
|
+
// src/sandbox/auth-callout.ts
|
|
79
114
|
var AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
|
|
80
115
|
var NATS_USER_JWT_TTL_SECONDS = 5 * 60;
|
|
116
|
+
var AUTH_CALLOUT_DENIAL_WARNING_INTERVAL_MS = 6e4;
|
|
117
|
+
var processDenialWarningThrottle = createLogThrottle({
|
|
118
|
+
intervalMs: AUTH_CALLOUT_DENIAL_WARNING_INTERVAL_MS,
|
|
119
|
+
maxKeys: 1024
|
|
120
|
+
});
|
|
121
|
+
function warnDenial(deps, key, reason, message, attributes = {}) {
|
|
122
|
+
const admission = (deps.denialWarningThrottle ?? processDenialWarningThrottle).admit(key);
|
|
123
|
+
if (!admission) return;
|
|
124
|
+
deps.observability?.warn?.(message, {
|
|
125
|
+
...attributes,
|
|
126
|
+
reason,
|
|
127
|
+
...admission.suppressedCount > 0 ? { suppressedCount: admission.suppressedCount } : {}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function rejectedBearerKey(bearer) {
|
|
131
|
+
return `invalid-bearer:${createHash("sha256").update(bearer).digest("hex").slice(0, 32)}`;
|
|
132
|
+
}
|
|
81
133
|
async function handleAuthorizationRequest(deps, requestBytes) {
|
|
82
134
|
const requestJwt = Buffer.from(requestBytes).toString("utf8");
|
|
83
135
|
const decoded = decodeAuthRequest(requestJwt);
|
|
@@ -104,15 +156,23 @@ async function handleAuthorizationRequest(deps, requestBytes) {
|
|
|
104
156
|
}
|
|
105
157
|
const claims = await verifyEnrollmentBearer(secret, bearer);
|
|
106
158
|
if (!claims) {
|
|
107
|
-
|
|
159
|
+
warnDenial(
|
|
160
|
+
deps,
|
|
161
|
+
rejectedBearerKey(bearer),
|
|
162
|
+
"invalid_bearer",
|
|
163
|
+
"auth-callout: rejected an invalid enrollment bearer"
|
|
164
|
+
);
|
|
108
165
|
return deny("invalid or expired enrollment bearer");
|
|
109
166
|
}
|
|
110
167
|
const enrollment = await getEnrollment(deps.db, claims.workspaceId, claims.enrollmentId);
|
|
111
168
|
if (!enrollment || enrollment.status !== "active") {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
169
|
+
warnDenial(
|
|
170
|
+
deps,
|
|
171
|
+
`inactive:${claims.workspaceId}:${claims.agentId}`,
|
|
172
|
+
"inactive_enrollment",
|
|
173
|
+
"auth-callout: denied a revoked or unknown enrollment",
|
|
174
|
+
{ workspaceId: claims.workspaceId, agentId: claims.agentId }
|
|
175
|
+
);
|
|
116
176
|
return deny("enrollment is not active");
|
|
117
177
|
}
|
|
118
178
|
if (enrollment.workspaceId !== claims.workspaceId || enrollment.id !== claims.enrollmentId || enrollment.id !== claims.agentId || claims.agentId !== claims.enrollmentId || claims.subjectPrefix !== `agent.${claims.workspaceId}.${claims.agentId}`) {
|
|
@@ -121,7 +181,32 @@ async function handleAuthorizationRequest(deps, requestBytes) {
|
|
|
121
181
|
if (enrollment.credentialGeneration !== claims.credentialGeneration) {
|
|
122
182
|
return deny("enrollment credential generation mismatch");
|
|
123
183
|
}
|
|
124
|
-
const
|
|
184
|
+
const connectionInstanceId = parseAgentConnectionName(decoded.name);
|
|
185
|
+
if (!connectionInstanceId) {
|
|
186
|
+
return deny("missing or invalid agent connection instance");
|
|
187
|
+
}
|
|
188
|
+
const claim = await claimEnrollmentConnection(deps.db, {
|
|
189
|
+
workspaceId: claims.workspaceId,
|
|
190
|
+
enrollmentId: claims.enrollmentId,
|
|
191
|
+
credentialGeneration: claims.credentialGeneration,
|
|
192
|
+
connectionInstanceId,
|
|
193
|
+
leaseMs: AGENT_CONNECTION_LEASE_MS
|
|
194
|
+
});
|
|
195
|
+
if (!claim.claimed) {
|
|
196
|
+
warnDenial(
|
|
197
|
+
deps,
|
|
198
|
+
`duplicate:${claims.workspaceId}:${claims.agentId}`,
|
|
199
|
+
"duplicate_runner",
|
|
200
|
+
"auth-callout: denied a duplicate live runner",
|
|
201
|
+
{ workspaceId: claims.workspaceId, agentId: claims.agentId }
|
|
202
|
+
);
|
|
203
|
+
return deny("another runner instance currently owns this enrollment");
|
|
204
|
+
}
|
|
205
|
+
const permissions = workspaceAgentPermissions(
|
|
206
|
+
claims.workspaceId,
|
|
207
|
+
claims.agentId,
|
|
208
|
+
connectionInstanceId
|
|
209
|
+
);
|
|
125
210
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
126
211
|
const userJwt = mintUserJwt({
|
|
127
212
|
userPublicKey: decoded.userNkey,
|
|
@@ -145,7 +230,8 @@ async function handleAuthorizationRequest(deps, requestBytes) {
|
|
|
145
230
|
});
|
|
146
231
|
deps.observability?.info?.("auth-callout: granted a workspace-scoped NATS credential", {
|
|
147
232
|
workspaceId: claims.workspaceId,
|
|
148
|
-
agentId: claims.agentId
|
|
233
|
+
agentId: claims.agentId,
|
|
234
|
+
connectionGeneration: claim.connectionGeneration
|
|
149
235
|
});
|
|
150
236
|
return Buffer.from(response, "utf8");
|
|
151
237
|
}
|
|
@@ -157,7 +243,7 @@ async function startAuthCalloutResponder(deps, natsUrl) {
|
|
|
157
243
|
(bytes) => handleAuthorizationRequest(deps, bytes),
|
|
158
244
|
{
|
|
159
245
|
name: "opengeni-auth-callout",
|
|
160
|
-
...deps.observability ?
|
|
246
|
+
...deps.observability ? observabilityEventBusOptions(deps.observability) : {}
|
|
161
247
|
}
|
|
162
248
|
);
|
|
163
249
|
deps.observability?.info?.("OpenGeni NATS auth-callout responder started", {
|
|
@@ -170,35 +256,132 @@ async function startAuthCalloutResponder(deps, natsUrl) {
|
|
|
170
256
|
import {
|
|
171
257
|
clearEnrollmentWentOffline,
|
|
172
258
|
disconnectAttachedBrowserDevices,
|
|
259
|
+
advanceEnrollmentAgentUpdate,
|
|
173
260
|
getEnrollment as getEnrollment2,
|
|
261
|
+
getLiveEnrollmentConnection,
|
|
174
262
|
ingestMachineMetricsSample,
|
|
175
263
|
reconcileAttachedBrowserInventory,
|
|
264
|
+
releaseEnrollmentConnection,
|
|
265
|
+
renewEnrollmentConnection,
|
|
176
266
|
sessionsWithActiveOpOnEnrollment,
|
|
177
267
|
setEnrollmentDisplayState,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
touchEnrollmentLastSeen
|
|
268
|
+
setEnrollmentAgentRuntime,
|
|
269
|
+
setEnrollmentOpStreamState
|
|
181
270
|
} from "@opengeni/db";
|
|
182
271
|
import {
|
|
183
272
|
AttachedBrowserInventorySnapshot as AttachedBrowserInventoryContract
|
|
184
273
|
} from "@opengeni/contracts";
|
|
185
274
|
import { appendAndPublishEvents } from "@opengeni/events";
|
|
275
|
+
import { normalizeConnectedMachineWorkspaceRoot } from "@opengeni/runtime/sandbox";
|
|
186
276
|
import {
|
|
187
277
|
AgentEvent,
|
|
278
|
+
AgentUpdateStage,
|
|
188
279
|
Arch,
|
|
189
280
|
GoingOfflineReason,
|
|
190
281
|
Hello,
|
|
191
282
|
Os,
|
|
192
283
|
goingOfflineReasonToJSON
|
|
193
284
|
} from "@opengeni/agent-proto";
|
|
194
|
-
var AGENT_EVENTS_SUBJECT = "agent.*.*.events";
|
|
195
|
-
var AGENT_HELLO_SUBJECT = "agent.*.*.hello";
|
|
285
|
+
var AGENT_EVENTS_SUBJECT = "agent.*.*.connection.*.events";
|
|
286
|
+
var AGENT_HELLO_SUBJECT = "agent.*.*.connection.*.hello";
|
|
287
|
+
function payloadIsHeartbeat(payload) {
|
|
288
|
+
try {
|
|
289
|
+
return AgentEvent.decode(payload).event?.$case === "heartbeat";
|
|
290
|
+
} catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function createAgentEventIngestionScheduler(handler, options = {}) {
|
|
295
|
+
const maxConcurrentSubjects = options.maxConcurrentSubjects ?? 32;
|
|
296
|
+
if (!Number.isSafeInteger(maxConcurrentSubjects) || maxConcurrentSubjects <= 0) {
|
|
297
|
+
throw new RangeError("maxConcurrentSubjects must be a positive safe integer");
|
|
298
|
+
}
|
|
299
|
+
const states = /* @__PURE__ */ new Map();
|
|
300
|
+
const readySubjects = [];
|
|
301
|
+
const idleWaiters = /* @__PURE__ */ new Set();
|
|
302
|
+
let accepting = true;
|
|
303
|
+
let activeSubjects = 0;
|
|
304
|
+
const resolveIdleWaiters = () => {
|
|
305
|
+
if (states.size !== 0) return;
|
|
306
|
+
for (const resolve of idleWaiters) resolve();
|
|
307
|
+
idleWaiters.clear();
|
|
308
|
+
};
|
|
309
|
+
async function drain(subject, state) {
|
|
310
|
+
while (true) {
|
|
311
|
+
const event = state.pending.shift();
|
|
312
|
+
if (!event) break;
|
|
313
|
+
try {
|
|
314
|
+
await handler(event.payload, event.subject);
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
activeSubjects -= 1;
|
|
319
|
+
state.scheduled = false;
|
|
320
|
+
if (state.pending.length > 0) {
|
|
321
|
+
schedule(subject, state);
|
|
322
|
+
} else {
|
|
323
|
+
if (states.get(subject) === state) states.delete(subject);
|
|
324
|
+
resolveIdleWaiters();
|
|
325
|
+
}
|
|
326
|
+
pump();
|
|
327
|
+
}
|
|
328
|
+
function pump() {
|
|
329
|
+
while (activeSubjects < maxConcurrentSubjects) {
|
|
330
|
+
const next = readySubjects.shift();
|
|
331
|
+
if (!next) return;
|
|
332
|
+
if (states.get(next.subject) !== next.state || !next.state.scheduled) continue;
|
|
333
|
+
activeSubjects += 1;
|
|
334
|
+
void drain(next.subject, next.state);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function schedule(subject, state) {
|
|
338
|
+
if (state.scheduled) return;
|
|
339
|
+
state.scheduled = true;
|
|
340
|
+
readySubjects.push({ subject, state });
|
|
341
|
+
pump();
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
enqueue(payload, subject) {
|
|
345
|
+
if (!accepting) return;
|
|
346
|
+
const heartbeat = payloadIsHeartbeat(payload);
|
|
347
|
+
let state = states.get(subject);
|
|
348
|
+
if (!state) {
|
|
349
|
+
state = { scheduled: false, pending: [] };
|
|
350
|
+
states.set(subject, state);
|
|
351
|
+
}
|
|
352
|
+
const queued = { payload, subject, heartbeat };
|
|
353
|
+
const lastIndex = state.pending.length - 1;
|
|
354
|
+
if (heartbeat && lastIndex >= 0 && state.pending[lastIndex].heartbeat) {
|
|
355
|
+
state.pending[lastIndex] = queued;
|
|
356
|
+
options.onHeartbeatCoalesced?.();
|
|
357
|
+
} else {
|
|
358
|
+
state.pending.push(queued);
|
|
359
|
+
}
|
|
360
|
+
schedule(subject, state);
|
|
361
|
+
},
|
|
362
|
+
async whenIdle() {
|
|
363
|
+
if (states.size === 0) return;
|
|
364
|
+
await new Promise((resolve) => idleWaiters.add(resolve));
|
|
365
|
+
},
|
|
366
|
+
close() {
|
|
367
|
+
accepting = false;
|
|
368
|
+
for (const state of states.values()) {
|
|
369
|
+
state.pending.length = 0;
|
|
370
|
+
}
|
|
371
|
+
resolveIdleWaiters();
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
196
375
|
function parseAgentSubject(subject, tail) {
|
|
197
376
|
const parts = subject.split(".");
|
|
198
|
-
if (parts.length !==
|
|
377
|
+
if (parts.length !== 6 || parts[0] !== "agent" || parts[3] !== "connection" || !parts[4] || parts[5] !== tail) {
|
|
199
378
|
return null;
|
|
200
379
|
}
|
|
201
|
-
return {
|
|
380
|
+
return {
|
|
381
|
+
workspaceId: parts[1],
|
|
382
|
+
agentId: parts[2],
|
|
383
|
+
connectionInstanceId: parts[4]
|
|
384
|
+
};
|
|
202
385
|
}
|
|
203
386
|
function parseAgentEventSubject(subject) {
|
|
204
387
|
return parseAgentSubject(subject, "events");
|
|
@@ -294,19 +477,10 @@ async function ingestAttachedBrowserInventory(db, input) {
|
|
|
294
477
|
snapshot: wireAttachedBrowserInventoryToContract(input.inventory)
|
|
295
478
|
});
|
|
296
479
|
}
|
|
297
|
-
async function
|
|
298
|
-
const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
|
|
299
|
-
if (!enrollment) {
|
|
300
|
-
return { ingested: false, seriesAppended: false };
|
|
301
|
-
}
|
|
480
|
+
async function ingestHeartbeatMetrics(db, input) {
|
|
302
481
|
const sample = wireSampleToDbSample(input.sample);
|
|
303
|
-
await touchEnrollmentLastSeen(db, {
|
|
304
|
-
accountId: enrollment.accountId,
|
|
305
|
-
workspaceId: input.workspaceId,
|
|
306
|
-
enrollmentId: input.agentId
|
|
307
|
-
});
|
|
308
482
|
const result = await ingestMachineMetricsSample(db, {
|
|
309
|
-
accountId:
|
|
483
|
+
accountId: input.accountId,
|
|
310
484
|
workspaceId: input.workspaceId,
|
|
311
485
|
enrollmentId: input.agentId,
|
|
312
486
|
sample
|
|
@@ -356,17 +530,19 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
|
|
|
356
530
|
labels: { reason }
|
|
357
531
|
});
|
|
358
532
|
try {
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
361
|
-
await
|
|
362
|
-
accountId:
|
|
533
|
+
const enrollment2 = await getEnrollment2(db, ids.workspaceId, ids.agentId);
|
|
534
|
+
if (enrollment2) {
|
|
535
|
+
const released = await releaseEnrollmentConnection(db, {
|
|
536
|
+
accountId: enrollment2.accountId,
|
|
363
537
|
workspaceId: ids.workspaceId,
|
|
364
538
|
enrollmentId: ids.agentId,
|
|
539
|
+
connectionInstanceId: ids.connectionInstanceId,
|
|
365
540
|
reason
|
|
366
541
|
});
|
|
542
|
+
if (!released.released) return;
|
|
367
543
|
try {
|
|
368
544
|
await disconnectAttachedBrowserDevices(db, {
|
|
369
|
-
accountId:
|
|
545
|
+
accountId: enrollment2.accountId,
|
|
370
546
|
workspaceId: ids.workspaceId,
|
|
371
547
|
enrollmentId: ids.agentId
|
|
372
548
|
});
|
|
@@ -408,10 +584,75 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
|
|
|
408
584
|
}
|
|
409
585
|
return;
|
|
410
586
|
}
|
|
587
|
+
if (event.event?.$case === "agentUpdateProgress") {
|
|
588
|
+
const enrollment2 = await getEnrollment2(db, ids.workspaceId, ids.agentId).catch(() => null);
|
|
589
|
+
if (!enrollment2 || enrollment2.connectionInstanceId !== ids.connectionInstanceId) return;
|
|
590
|
+
const progress = event.event.agentUpdateProgress;
|
|
591
|
+
const status = (() => {
|
|
592
|
+
switch (progress.stage) {
|
|
593
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_ACCEPTED:
|
|
594
|
+
return "accepted";
|
|
595
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_WAITING_FOR_IDLE:
|
|
596
|
+
return "waiting_for_idle";
|
|
597
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_DOWNLOADING:
|
|
598
|
+
return "downloading";
|
|
599
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_VERIFYING:
|
|
600
|
+
return "verifying";
|
|
601
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_APPLYING:
|
|
602
|
+
return "applying";
|
|
603
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_RESTARTING:
|
|
604
|
+
return "restarting";
|
|
605
|
+
case AgentUpdateStage.AGENT_UPDATE_STAGE_FAILED:
|
|
606
|
+
return "failed";
|
|
607
|
+
default:
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
})();
|
|
611
|
+
if (!status) return;
|
|
612
|
+
try {
|
|
613
|
+
await advanceEnrollmentAgentUpdate(db, {
|
|
614
|
+
accountId: enrollment2.accountId,
|
|
615
|
+
workspaceId: ids.workspaceId,
|
|
616
|
+
enrollmentId: ids.agentId,
|
|
617
|
+
connectionInstanceId: ids.connectionInstanceId,
|
|
618
|
+
connectionGeneration: enrollment2.connectionGeneration,
|
|
619
|
+
operationId: progress.operationId,
|
|
620
|
+
status,
|
|
621
|
+
expectedBinarySha256: /^[0-9a-f]{64}$/.test(progress.expectedBinarySha256) ? progress.expectedBinarySha256 : null,
|
|
622
|
+
errorCode: progress.errorCode || null,
|
|
623
|
+
retryable: progress.retryable,
|
|
624
|
+
rolledBack: progress.rolledBack
|
|
625
|
+
});
|
|
626
|
+
} catch (error) {
|
|
627
|
+
observability?.warn?.("Failed to ingest a machine self-update progress event", {
|
|
628
|
+
subject,
|
|
629
|
+
error: error instanceof Error ? error.message : String(error)
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
411
634
|
if (event.event?.$case !== "heartbeat") {
|
|
412
635
|
return;
|
|
413
636
|
}
|
|
414
637
|
const heartbeat = event.event.heartbeat;
|
|
638
|
+
const enrollment = await getEnrollment2(db, ids.workspaceId, ids.agentId).catch(() => null);
|
|
639
|
+
if (!enrollment || enrollment.connectionInstanceId !== ids.connectionInstanceId) return;
|
|
640
|
+
try {
|
|
641
|
+
const renewed = await renewEnrollmentConnection(db, {
|
|
642
|
+
accountId: enrollment.accountId,
|
|
643
|
+
workspaceId: ids.workspaceId,
|
|
644
|
+
enrollmentId: ids.agentId,
|
|
645
|
+
connectionInstanceId: ids.connectionInstanceId,
|
|
646
|
+
leaseMs: AGENT_CONNECTION_LEASE_MS
|
|
647
|
+
});
|
|
648
|
+
if (!renewed.renewed) return;
|
|
649
|
+
} catch (error) {
|
|
650
|
+
observability?.warn?.("Failed to renew a machine connection heartbeat", {
|
|
651
|
+
subject,
|
|
652
|
+
error: error instanceof Error ? error.message : String(error)
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
415
656
|
if (heartbeat.attachedBrowserInventory) {
|
|
416
657
|
try {
|
|
417
658
|
await ingestAttachedBrowserInventory(db, {
|
|
@@ -426,10 +667,33 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
|
|
|
426
667
|
});
|
|
427
668
|
}
|
|
428
669
|
}
|
|
670
|
+
if (heartbeat.desktopStatus) {
|
|
671
|
+
try {
|
|
672
|
+
const status = heartbeat.desktopStatus;
|
|
673
|
+
const available = status.available && !status.unavailableReason;
|
|
674
|
+
const reason = status.unavailableReason || null;
|
|
675
|
+
if (enrollment.hasDisplay !== available || (enrollment.desktopUnavailableReason ?? null) !== reason || enrollment.agentCapabilities?.desktop !== available) {
|
|
676
|
+
await refreshEnrollmentDisplay(db, {
|
|
677
|
+
workspaceId: ids.workspaceId,
|
|
678
|
+
agentId: ids.agentId,
|
|
679
|
+
hasDisplay: available,
|
|
680
|
+
desktopUnavailableReason: reason,
|
|
681
|
+
runtimeDesktop: available,
|
|
682
|
+
connectionInstanceId: ids.connectionInstanceId
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
} catch (error) {
|
|
686
|
+
observability?.warn?.("Failed to refresh a machine desktop heartbeat", {
|
|
687
|
+
subject,
|
|
688
|
+
error: error instanceof Error ? error.message : String(error)
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
429
692
|
const metrics = heartbeat.metrics;
|
|
430
693
|
if (!metrics) return;
|
|
431
694
|
try {
|
|
432
|
-
await
|
|
695
|
+
await ingestHeartbeatMetrics(db, {
|
|
696
|
+
accountId: enrollment.accountId,
|
|
433
697
|
workspaceId: ids.workspaceId,
|
|
434
698
|
agentId: ids.agentId,
|
|
435
699
|
sample: metrics
|
|
@@ -442,10 +706,22 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
|
|
|
442
706
|
}
|
|
443
707
|
}
|
|
444
708
|
function startMetricsIngestion(deps) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
709
|
+
const scheduler = createAgentEventIngestionScheduler(
|
|
710
|
+
(payload, subject) => handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),
|
|
711
|
+
{
|
|
712
|
+
onHeartbeatCoalesced: () => deps.observability?.incrementCounter({
|
|
713
|
+
name: "opengeni_machine_heartbeat_coalesced_total",
|
|
714
|
+
help: "Connected Machine heartbeats collapsed while an exact runner event queue was busy."
|
|
715
|
+
})
|
|
716
|
+
}
|
|
448
717
|
);
|
|
718
|
+
const unsubscribe = deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) => {
|
|
719
|
+
scheduler.enqueue(payload, subject);
|
|
720
|
+
});
|
|
721
|
+
return () => {
|
|
722
|
+
unsubscribe();
|
|
723
|
+
scheduler.close();
|
|
724
|
+
};
|
|
449
725
|
}
|
|
450
726
|
function helloReportsDisplay(hello) {
|
|
451
727
|
const caps = hello.capabilities;
|
|
@@ -464,13 +740,41 @@ function helloDesktopUnavailableReason(hello) {
|
|
|
464
740
|
function helloReportsOpStream(hello) {
|
|
465
741
|
return hello.capabilities?.opStream === true;
|
|
466
742
|
}
|
|
743
|
+
function helloRuntimeCapabilities(hello) {
|
|
744
|
+
const caps = hello.capabilities;
|
|
745
|
+
if (!caps) return {};
|
|
746
|
+
return {
|
|
747
|
+
exec: caps.exec === true,
|
|
748
|
+
filesystem: caps.filesystem === true,
|
|
749
|
+
git: caps.git === true,
|
|
750
|
+
pty: caps.pty === true,
|
|
751
|
+
desktop: caps.desktop === true,
|
|
752
|
+
opStream: caps.opStream === true,
|
|
753
|
+
browserBridge: caps.browserBridge === true,
|
|
754
|
+
operationResourcePolicy: caps.operationResourcePolicy === true,
|
|
755
|
+
operationCpuQuota: caps.operationCpuQuota === true,
|
|
756
|
+
transactionalFsWrite: caps.transactionalFsWrite === true
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function helloCompletedUpdate(hello) {
|
|
760
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
|
761
|
+
hello.completedUpdateOperationId
|
|
762
|
+
) || !/^(?:v)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(hello.completedUpdateTargetVersion) || !/^[0-9a-f]{64}$/.test(hello.completedUpdateBinarySha256)) {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
operationId: hello.completedUpdateOperationId,
|
|
767
|
+
targetVersion: hello.completedUpdateTargetVersion,
|
|
768
|
+
binarySha256: hello.completedUpdateBinarySha256
|
|
769
|
+
};
|
|
770
|
+
}
|
|
467
771
|
async function refreshEnrollmentDisplay(db, input) {
|
|
468
772
|
const desktopUnavailableReason = input.desktopUnavailableReason ?? null;
|
|
469
773
|
const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
|
|
470
774
|
if (!enrollment) {
|
|
471
775
|
return { updated: false };
|
|
472
776
|
}
|
|
473
|
-
if (enrollment.hasDisplay === input.hasDisplay && (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason) {
|
|
777
|
+
if (enrollment.hasDisplay === input.hasDisplay && (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason && (input.runtimeDesktop === void 0 || enrollment.agentCapabilities?.desktop === input.runtimeDesktop)) {
|
|
474
778
|
return { updated: false };
|
|
475
779
|
}
|
|
476
780
|
return await setEnrollmentDisplayState(db, {
|
|
@@ -478,7 +782,9 @@ async function refreshEnrollmentDisplay(db, input) {
|
|
|
478
782
|
workspaceId: input.workspaceId,
|
|
479
783
|
enrollmentId: input.agentId,
|
|
480
784
|
hasDisplay: input.hasDisplay,
|
|
481
|
-
desktopUnavailableReason
|
|
785
|
+
desktopUnavailableReason,
|
|
786
|
+
...input.runtimeDesktop !== void 0 ? { runtimeDesktop: input.runtimeDesktop } : {},
|
|
787
|
+
...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
|
|
482
788
|
});
|
|
483
789
|
}
|
|
484
790
|
async function refreshEnrollmentOpStream(db, input) {
|
|
@@ -493,7 +799,8 @@ async function refreshEnrollmentOpStream(db, input) {
|
|
|
493
799
|
accountId: enrollment.accountId,
|
|
494
800
|
workspaceId: input.workspaceId,
|
|
495
801
|
enrollmentId: input.agentId,
|
|
496
|
-
opStream: input.opStream
|
|
802
|
+
opStream: input.opStream,
|
|
803
|
+
...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
|
|
497
804
|
});
|
|
498
805
|
}
|
|
499
806
|
async function handleHelloPayload(db, observability, payload, subject, bus) {
|
|
@@ -511,17 +818,53 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
|
|
|
511
818
|
});
|
|
512
819
|
return;
|
|
513
820
|
}
|
|
821
|
+
const authority = await getLiveEnrollmentConnection(db, ids.workspaceId, ids.agentId).catch(
|
|
822
|
+
() => null
|
|
823
|
+
);
|
|
824
|
+
if (!authority || authority.connectionInstanceId !== ids.connectionInstanceId) {
|
|
825
|
+
observability?.warn?.("Ignored a Hello from a superseded runner instance", {
|
|
826
|
+
workspaceId: ids.workspaceId,
|
|
827
|
+
agentId: ids.agentId
|
|
828
|
+
});
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
let workspaceRoot;
|
|
832
|
+
if (hello.workspaceRoot.length > 0) {
|
|
833
|
+
try {
|
|
834
|
+
workspaceRoot = normalizeConnectedMachineWorkspaceRoot(hello.workspaceRoot, authority.os);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
observability?.warn?.("Ignored an invalid Connected Machine workspace root", {
|
|
837
|
+
workspaceId: ids.workspaceId,
|
|
838
|
+
agentId: ids.agentId,
|
|
839
|
+
error: error instanceof Error ? error.message : String(error)
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
514
843
|
try {
|
|
515
844
|
await refreshEnrollmentDisplay(db, {
|
|
516
845
|
workspaceId: ids.workspaceId,
|
|
517
846
|
agentId: ids.agentId,
|
|
518
847
|
hasDisplay: helloReportsDisplay(hello),
|
|
519
|
-
desktopUnavailableReason: helloDesktopUnavailableReason(hello)
|
|
848
|
+
desktopUnavailableReason: helloDesktopUnavailableReason(hello),
|
|
849
|
+
connectionInstanceId: ids.connectionInstanceId
|
|
520
850
|
});
|
|
521
851
|
await refreshEnrollmentOpStream(db, {
|
|
522
852
|
workspaceId: ids.workspaceId,
|
|
523
853
|
agentId: ids.agentId,
|
|
524
|
-
opStream: helloReportsOpStream(hello)
|
|
854
|
+
opStream: helloReportsOpStream(hello),
|
|
855
|
+
connectionInstanceId: ids.connectionInstanceId
|
|
856
|
+
});
|
|
857
|
+
await setEnrollmentAgentRuntime(db, {
|
|
858
|
+
accountId: authority.accountId,
|
|
859
|
+
workspaceId: ids.workspaceId,
|
|
860
|
+
enrollmentId: ids.agentId,
|
|
861
|
+
connectionInstanceId: ids.connectionInstanceId,
|
|
862
|
+
agentVersion: hello.agentVersion.trim() || null,
|
|
863
|
+
binarySha256: /^[0-9a-f]{64}$/.test(hello.binarySha256) ? hello.binarySha256 : null,
|
|
864
|
+
updateChannel: hello.updateChannel === "stable" || hello.updateChannel === "beta" ? hello.updateChannel : null,
|
|
865
|
+
capabilities: helloRuntimeCapabilities(hello),
|
|
866
|
+
...workspaceRoot !== void 0 ? { workspaceRoot } : {},
|
|
867
|
+
completedUpdate: helloCompletedUpdate(hello)
|
|
525
868
|
});
|
|
526
869
|
} catch (error) {
|
|
527
870
|
observability?.warn?.("Failed to refresh an enrollment's capabilities from a Hello", {
|
|
@@ -530,23 +873,21 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
|
|
|
530
873
|
});
|
|
531
874
|
}
|
|
532
875
|
try {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
);
|
|
549
|
-
}
|
|
876
|
+
const { cleared } = await clearEnrollmentWentOffline(db, {
|
|
877
|
+
accountId: authority.accountId,
|
|
878
|
+
workspaceId: ids.workspaceId,
|
|
879
|
+
enrollmentId: ids.agentId,
|
|
880
|
+
connectionInstanceId: ids.connectionInstanceId
|
|
881
|
+
});
|
|
882
|
+
if (cleared && bus) {
|
|
883
|
+
await fanOutMachineLinkEvents(
|
|
884
|
+
db,
|
|
885
|
+
bus,
|
|
886
|
+
observability,
|
|
887
|
+
ids.workspaceId,
|
|
888
|
+
ids.agentId,
|
|
889
|
+
(activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }]
|
|
890
|
+
);
|
|
550
891
|
}
|
|
551
892
|
} catch (error) {
|
|
552
893
|
observability?.warn?.("Failed to clear a machine going-offline marker on a Hello", {
|
|
@@ -733,6 +1074,195 @@ function escapeSlackText(value) {
|
|
|
733
1074
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
734
1075
|
}
|
|
735
1076
|
|
|
1077
|
+
// src/temporal-schedule-sync.ts
|
|
1078
|
+
import { getScheduledTaskIncludingDeleted } from "@opengeni/db";
|
|
1079
|
+
var LOCK_TIMEOUT = "5s";
|
|
1080
|
+
var WRITE_TIMEOUT_MS = 5e3;
|
|
1081
|
+
function createTemporalScheduleSynchronizer(deps) {
|
|
1082
|
+
const write = async (temporalScheduleId, work) => deps.db.transaction(async (tx) => {
|
|
1083
|
+
await tx.execute(sql`select set_config('lock_timeout', ${LOCK_TIMEOUT}, true)`);
|
|
1084
|
+
await tx.execute(sql`select pg_advisory_xact_lock(
|
|
1085
|
+
hashtextextended(${`scheduled-task-temporal:${temporalScheduleId}`}, 0)
|
|
1086
|
+
)`);
|
|
1087
|
+
return await work(tx);
|
|
1088
|
+
});
|
|
1089
|
+
return {
|
|
1090
|
+
async sync(task, onFailure) {
|
|
1091
|
+
const result = await write(task.temporalScheduleId, async (tx) => {
|
|
1092
|
+
try {
|
|
1093
|
+
await deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, async () => {
|
|
1094
|
+
const current = await getScheduledTaskIncludingDeleted(tx, task.workspaceId, task.id);
|
|
1095
|
+
if (current && current.temporalScheduleId !== task.temporalScheduleId)
|
|
1096
|
+
throw new Error("Scheduled task Temporal identity changed");
|
|
1097
|
+
if (!current || current.deletedAt !== null || current.schedule.type === "manual") {
|
|
1098
|
+
await deps.remove(task.temporalScheduleId);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const { deletedAt: _deletedAt, ...latest } = current;
|
|
1102
|
+
await deps.upsert(latest);
|
|
1103
|
+
});
|
|
1104
|
+
return { ok: true };
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
return { ok: false, error: onFailure ? await onFailure(tx, error) : error };
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
if (!result.ok) throw result.error;
|
|
1110
|
+
},
|
|
1111
|
+
async remove(temporalScheduleId) {
|
|
1112
|
+
await write(
|
|
1113
|
+
temporalScheduleId,
|
|
1114
|
+
async () => deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, () => deps.remove(temporalScheduleId))
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/workspace-webhook-dispatch.ts
|
|
1121
|
+
import { randomUUID } from "crypto";
|
|
1122
|
+
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
1123
|
+
import {
|
|
1124
|
+
OPENGENI_DELIVERY_ID_HEADER,
|
|
1125
|
+
OPENGENI_EVENT_ID_HEADER,
|
|
1126
|
+
OPENGENI_SIGNATURE_HEADER,
|
|
1127
|
+
signOpenGeniPayload
|
|
1128
|
+
} from "@opengeni/contracts";
|
|
1129
|
+
import {
|
|
1130
|
+
claimWorkspaceWebhookDeliveries,
|
|
1131
|
+
claimOrganizationWebhookDeliveries,
|
|
1132
|
+
decryptEnvironmentValue,
|
|
1133
|
+
maintainWorkspaceAllowances,
|
|
1134
|
+
pruneOrganizationWebhookDeliveries,
|
|
1135
|
+
pruneWorkspaceWebhookDeliveries,
|
|
1136
|
+
settleOrganizationWebhookDelivery,
|
|
1137
|
+
settleWorkspaceWebhookDelivery
|
|
1138
|
+
} from "@opengeni/db";
|
|
1139
|
+
import { pinnedFetch } from "@opengeni/network";
|
|
1140
|
+
var WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE = 32;
|
|
1141
|
+
var CLAIM_SECONDS = 60;
|
|
1142
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
1143
|
+
var DELIVERY_TIMEOUT_MS = 1e4;
|
|
1144
|
+
var PRUNE_EVERY_TICKS = 300;
|
|
1145
|
+
function errorText(error) {
|
|
1146
|
+
if (error instanceof Error) {
|
|
1147
|
+
if (error.name === "TimeoutError" || error.name === "AbortError") return "timeout";
|
|
1148
|
+
return `${error.name}: ${error.message}`.slice(0, 500);
|
|
1149
|
+
}
|
|
1150
|
+
return String(error).slice(0, 500);
|
|
1151
|
+
}
|
|
1152
|
+
async function deliverOne(deps, key, claimId, delivery, organization = false) {
|
|
1153
|
+
let status = null;
|
|
1154
|
+
let error = null;
|
|
1155
|
+
try {
|
|
1156
|
+
const body = JSON.stringify(delivery.payload);
|
|
1157
|
+
const secret = decryptEnvironmentValue(key, delivery.secretEncrypted);
|
|
1158
|
+
const response = await (deps.fetch ?? pinnedFetch)(
|
|
1159
|
+
delivery.url,
|
|
1160
|
+
{
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
headers: {
|
|
1163
|
+
"content-type": "application/json",
|
|
1164
|
+
"user-agent": "OpenGeni-Webhooks/1",
|
|
1165
|
+
[OPENGENI_SIGNATURE_HEADER]: await signOpenGeniPayload(secret, body),
|
|
1166
|
+
[OPENGENI_EVENT_ID_HEADER]: delivery.eventId,
|
|
1167
|
+
[OPENGENI_DELIVERY_ID_HEADER]: delivery.deliveryId
|
|
1168
|
+
},
|
|
1169
|
+
body,
|
|
1170
|
+
signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS)
|
|
1171
|
+
},
|
|
1172
|
+
deps.settings,
|
|
1173
|
+
{
|
|
1174
|
+
label: organization ? "Organization webhook" : "Workspace webhook",
|
|
1175
|
+
requireHttpsOutsideLocalTest: true
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1178
|
+
status = response.status;
|
|
1179
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1180
|
+
if (status < 200 || status >= 300) error = `HTTP ${status}`;
|
|
1181
|
+
} catch (caught) {
|
|
1182
|
+
error = errorText(caught);
|
|
1183
|
+
}
|
|
1184
|
+
await (organization ? settleOrganizationWebhookDelivery : settleWorkspaceWebhookDelivery)(
|
|
1185
|
+
deps.db,
|
|
1186
|
+
{
|
|
1187
|
+
deliveryId: delivery.deliveryId,
|
|
1188
|
+
claimId,
|
|
1189
|
+
status,
|
|
1190
|
+
error
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
return error === null ? "delivered" : "failed";
|
|
1194
|
+
}
|
|
1195
|
+
async function drainWorkspaceWebhookDeliveries(deps) {
|
|
1196
|
+
await maintainWorkspaceAllowances(deps.db);
|
|
1197
|
+
const key = environmentsEncryptionKeyBytes(deps.settings);
|
|
1198
|
+
if (!key) return { claimed: 0, delivered: 0, failed: 0 };
|
|
1199
|
+
const claimId = randomUUID();
|
|
1200
|
+
const claims = await claimWorkspaceWebhookDeliveries(deps.db, {
|
|
1201
|
+
claimId,
|
|
1202
|
+
limit: WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE,
|
|
1203
|
+
claimSeconds: CLAIM_SECONDS
|
|
1204
|
+
});
|
|
1205
|
+
const organizationClaims = await claimOrganizationWebhookDeliveries(deps.db, {
|
|
1206
|
+
claimId,
|
|
1207
|
+
limit: WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE,
|
|
1208
|
+
claimSeconds: CLAIM_SECONDS
|
|
1209
|
+
});
|
|
1210
|
+
const outcomes = await Promise.all(
|
|
1211
|
+
[
|
|
1212
|
+
...claims.map((delivery) => ({ delivery, organization: false })),
|
|
1213
|
+
...organizationClaims.map((delivery) => ({ delivery, organization: true }))
|
|
1214
|
+
].map(async ({ delivery, organization }) => {
|
|
1215
|
+
try {
|
|
1216
|
+
return await deliverOne(deps, key, claimId, delivery, organization);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
deps.observability?.warn("Workspace webhook settlement failed", {
|
|
1219
|
+
deliveryId: delivery.deliveryId,
|
|
1220
|
+
error: errorText(error)
|
|
1221
|
+
});
|
|
1222
|
+
return "failed";
|
|
1223
|
+
}
|
|
1224
|
+
})
|
|
1225
|
+
);
|
|
1226
|
+
return {
|
|
1227
|
+
claimed: claims.length + organizationClaims.length,
|
|
1228
|
+
delivered: outcomes.filter((outcome) => outcome === "delivered").length,
|
|
1229
|
+
failed: outcomes.filter((outcome) => outcome === "failed").length
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
function startWorkspaceWebhookDispatchPump(deps, options = {}) {
|
|
1233
|
+
const intervalMs = Math.max(100, options.intervalMs ?? POLL_INTERVAL_MS);
|
|
1234
|
+
const drain = options.drain ?? drainWorkspaceWebhookDeliveries;
|
|
1235
|
+
let stopped = false;
|
|
1236
|
+
let running;
|
|
1237
|
+
let ticks = 0;
|
|
1238
|
+
const tick = () => {
|
|
1239
|
+
if (stopped || running) return;
|
|
1240
|
+
ticks += 1;
|
|
1241
|
+
let backlog = false;
|
|
1242
|
+
running = (async () => {
|
|
1243
|
+
const result = await drain(deps);
|
|
1244
|
+
backlog = result.claimed >= WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE;
|
|
1245
|
+
if (result.claimed > 0) deps.observability?.info("Workspace webhook batch settled", result);
|
|
1246
|
+
if (ticks % PRUNE_EVERY_TICKS === 0) {
|
|
1247
|
+
await pruneWorkspaceWebhookDeliveries(deps.db);
|
|
1248
|
+
await pruneOrganizationWebhookDeliveries(deps.db);
|
|
1249
|
+
}
|
|
1250
|
+
})().catch((error) => {
|
|
1251
|
+
deps.observability?.error("Workspace webhook dispatch failed", { error: errorText(error) });
|
|
1252
|
+
}).finally(() => {
|
|
1253
|
+
running = void 0;
|
|
1254
|
+
if (backlog && !stopped) queueMicrotask(tick);
|
|
1255
|
+
});
|
|
1256
|
+
};
|
|
1257
|
+
const timer = setInterval(tick, intervalMs);
|
|
1258
|
+
queueMicrotask(tick);
|
|
1259
|
+
return async () => {
|
|
1260
|
+
stopped = true;
|
|
1261
|
+
clearInterval(timer);
|
|
1262
|
+
await running;
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
736
1266
|
// src/editable-artifact-websocket.ts
|
|
737
1267
|
import {
|
|
738
1268
|
EditableArtifactDomainError,
|
|
@@ -741,13 +1271,14 @@ import {
|
|
|
741
1271
|
encodeEditableArtifactLiveServerWireFrame
|
|
742
1272
|
} from "@opengeni/core";
|
|
743
1273
|
var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PATH = "/v1/editable-artifacts/live";
|
|
744
|
-
var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-
|
|
1274
|
+
var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-v2";
|
|
745
1275
|
var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES = 8 * 1024 * 1024 + 64 * 1024;
|
|
746
1276
|
var MAX_QUEUED_MESSAGES = 64;
|
|
747
1277
|
var MAX_QUEUED_BYTES = 12 * 1024 * 1024;
|
|
748
1278
|
var EditableArtifactWebSocketTransport = class {
|
|
749
|
-
constructor(application) {
|
|
1279
|
+
constructor(application, authorizeSourceSession) {
|
|
750
1280
|
this.application = application;
|
|
1281
|
+
this.authorizeSourceSession = authorizeSourceSession;
|
|
751
1282
|
this.websocket = Object.freeze({
|
|
752
1283
|
open: (socket) => socket.data.attach(socket),
|
|
753
1284
|
message: (socket, message) => socket.data.receive(message),
|
|
@@ -771,7 +1302,10 @@ var EditableArtifactWebSocketTransport = class {
|
|
|
771
1302
|
if (!offeredProtocol(request.headers.get("sec-websocket-protocol"))) {
|
|
772
1303
|
return new Response("WebSocket protocol required", { status: 426 });
|
|
773
1304
|
}
|
|
774
|
-
const connection = new EditableArtifactWebSocketConnection(
|
|
1305
|
+
const connection = new EditableArtifactWebSocketConnection(
|
|
1306
|
+
this.application,
|
|
1307
|
+
this.authorizeSourceSession
|
|
1308
|
+
);
|
|
775
1309
|
const upgraded = server.upgrade(request, {
|
|
776
1310
|
data: connection,
|
|
777
1311
|
headers: {
|
|
@@ -782,8 +1316,9 @@ var EditableArtifactWebSocketTransport = class {
|
|
|
782
1316
|
}
|
|
783
1317
|
};
|
|
784
1318
|
var EditableArtifactWebSocketConnection = class {
|
|
785
|
-
constructor(application) {
|
|
1319
|
+
constructor(application, authorizeSourceSession) {
|
|
786
1320
|
this.application = application;
|
|
1321
|
+
this.authorizeSourceSession = authorizeSourceSession;
|
|
787
1322
|
}
|
|
788
1323
|
socket = null;
|
|
789
1324
|
session = null;
|
|
@@ -869,7 +1404,8 @@ var EditableArtifactWebSocketConnection = class {
|
|
|
869
1404
|
artifactId: frame.artifactId,
|
|
870
1405
|
protocolVersion: frame.protocolVersion,
|
|
871
1406
|
resume: frame.resume,
|
|
872
|
-
sink: this
|
|
1407
|
+
sink: this,
|
|
1408
|
+
...this.authorizeSourceSession ? { authorizeSourceSession: this.authorizeSourceSession } : {}
|
|
873
1409
|
});
|
|
874
1410
|
if (this.terminal) {
|
|
875
1411
|
await session.close("transport_error");
|
|
@@ -976,14 +1512,121 @@ function closeCode(reason) {
|
|
|
976
1512
|
return 4400;
|
|
977
1513
|
}
|
|
978
1514
|
|
|
1515
|
+
// src/editable-artifact-source-session.ts
|
|
1516
|
+
import {
|
|
1517
|
+
hasPermission,
|
|
1518
|
+
requireExternalContinuationAuthority,
|
|
1519
|
+
requireSessionAuthorization
|
|
1520
|
+
} from "@opengeni/core";
|
|
1521
|
+
import {
|
|
1522
|
+
findActiveWorkspaceApiKeyById,
|
|
1523
|
+
getSession,
|
|
1524
|
+
getWorkspaceGrant,
|
|
1525
|
+
hasEditableArtifactSessionLink,
|
|
1526
|
+
lockExternalWorkspaceMembershipLifecycle,
|
|
1527
|
+
withRlsContext
|
|
1528
|
+
} from "@opengeni/db";
|
|
1529
|
+
function editableArtifactSourceSessionAuthorizer(deps) {
|
|
1530
|
+
return async (ticket, permission, transaction, signal) => {
|
|
1531
|
+
signal?.throwIfAborted();
|
|
1532
|
+
const authority = ticket.sourceSessionAuthority;
|
|
1533
|
+
if (!authority) return true;
|
|
1534
|
+
const { grant } = authority;
|
|
1535
|
+
const required = permission === "edit" ? "artifacts:publish" : "artifacts:read";
|
|
1536
|
+
if (!hasPermission(grant.permissions, required) || !hasPermission(grant.permissions, "sessions:read")) {
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
if (grant.metadata?.externalActor && !authority.externalContinuation) return false;
|
|
1540
|
+
try {
|
|
1541
|
+
const scopedDeps = transaction ? { ...deps, db: transaction } : deps;
|
|
1542
|
+
if (transaction) {
|
|
1543
|
+
await lockExternalWorkspaceMembershipLifecycle(transaction, ticket.scope.accountId);
|
|
1544
|
+
signal?.throwIfAborted();
|
|
1545
|
+
await transaction.execute(sql`select pg_advisory_xact_lock_shared(
|
|
1546
|
+
hashtextextended(${`session-tenancy:${ticket.scope.workspaceId}`}, 0))`);
|
|
1547
|
+
signal?.throwIfAborted();
|
|
1548
|
+
}
|
|
1549
|
+
return await withAccessGrantSessionRlsContext(scopedDeps, grant, async () => {
|
|
1550
|
+
return await withRlsContext(scopedDeps.db, ticket.scope, async (tx) => {
|
|
1551
|
+
if (authority.externalContinuation) {
|
|
1552
|
+
await requireExternalContinuationAuthority(
|
|
1553
|
+
tx,
|
|
1554
|
+
authority.externalContinuation,
|
|
1555
|
+
{ ...ticket.scope, subjectId: grant.subjectId },
|
|
1556
|
+
["sessions:read", required]
|
|
1557
|
+
);
|
|
1558
|
+
signal?.throwIfAborted();
|
|
1559
|
+
} else if (transaction) {
|
|
1560
|
+
const actor = ticket.actor;
|
|
1561
|
+
if (actor.kind === "human" || actor.kind === "service" && actor.service === "configured_key") {
|
|
1562
|
+
await tx.execute(sql`select subject_id from workspace_memberships
|
|
1563
|
+
where account_id = ${ticket.scope.accountId}::uuid
|
|
1564
|
+
and workspace_id = ${ticket.scope.workspaceId}::uuid
|
|
1565
|
+
and subject_id = ${grant.subjectId} for share`);
|
|
1566
|
+
const live = await getWorkspaceGrant(tx, grant.subjectId, ticket.scope.workspaceId);
|
|
1567
|
+
if (!live || !hasPermission(live.permissions, "sessions:read")) return false;
|
|
1568
|
+
} else if (actor.kind === "service" && actor.service === "api_key") {
|
|
1569
|
+
await tx.execute(sql`select id from api_keys
|
|
1570
|
+
where account_id = ${ticket.scope.accountId}::uuid
|
|
1571
|
+
and id::text = ${grant.subjectId.slice("api_key:".length)}
|
|
1572
|
+
for share`);
|
|
1573
|
+
const live = await findActiveWorkspaceApiKeyById(tx, {
|
|
1574
|
+
...ticket.scope,
|
|
1575
|
+
apiKeyId: grant.subjectId.slice("api_key:".length)
|
|
1576
|
+
});
|
|
1577
|
+
if (!live || !hasPermission(live.permissions, "sessions:read")) return false;
|
|
1578
|
+
} else if (actor.kind === "agent") {
|
|
1579
|
+
const live = await getSession(tx, ticket.scope.workspaceId, actor.sessionId);
|
|
1580
|
+
if (!live || live.firstPartyMcpPermissions !== null && !hasPermission(live.firstPartyMcpPermissions, "sessions:read")) {
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
} else {
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
signal?.throwIfAborted();
|
|
1587
|
+
}
|
|
1588
|
+
const linked = await hasEditableArtifactSessionLink(
|
|
1589
|
+
tx,
|
|
1590
|
+
ticket.scope,
|
|
1591
|
+
authority.sessionId,
|
|
1592
|
+
ticket.artifactId,
|
|
1593
|
+
{ lockForCommit: transaction !== void 0 }
|
|
1594
|
+
);
|
|
1595
|
+
signal?.throwIfAborted();
|
|
1596
|
+
if (!linked) return false;
|
|
1597
|
+
await requireSessionAuthorization({ ...scopedDeps, db: tx }, grant, {
|
|
1598
|
+
sessionId: authority.sessionId,
|
|
1599
|
+
operation: "session.read",
|
|
1600
|
+
surface: "http"
|
|
1601
|
+
});
|
|
1602
|
+
signal?.throwIfAborted();
|
|
1603
|
+
return true;
|
|
1604
|
+
});
|
|
1605
|
+
});
|
|
1606
|
+
} catch {
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/http/websocket-upgrade-dispatch.ts
|
|
1613
|
+
function dispatchApiWebSocketUpgrade(request, server, routes, localBrowserBoundary) {
|
|
1614
|
+
const route = routes.find((candidate) => candidate.handles(request));
|
|
1615
|
+
if (!route) return { handled: false };
|
|
1616
|
+
const rejection = localBrowserBoundary?.rejection(request);
|
|
1617
|
+
if (rejection) return { handled: true, response: localBrowserBoundaryResponse(rejection) };
|
|
1618
|
+
return { handled: true, response: route.upgrade(request, server) };
|
|
1619
|
+
}
|
|
1620
|
+
|
|
979
1621
|
// src/editable-artifact-production.ts
|
|
980
|
-
import { createHash as
|
|
1622
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
|
|
981
1623
|
import {
|
|
982
1624
|
editableArtifactMaterializerCapabilitiesForRuntime
|
|
983
1625
|
} from "@opengeni/artifact-tool/runtime/materializer";
|
|
984
1626
|
import {
|
|
985
1627
|
EditableArtifactDurableExportService,
|
|
986
1628
|
EditableArtifactAgentApplication,
|
|
1629
|
+
EDITABLE_ARTIFACT_EXPORT_FORMATS,
|
|
987
1630
|
EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES,
|
|
988
1631
|
EditableArtifactCompactionPipeline,
|
|
989
1632
|
EditableArtifactGenesisPipeline,
|
|
@@ -999,7 +1642,6 @@ import {
|
|
|
999
1642
|
editableArtifactId
|
|
1000
1643
|
} from "@opengeni/core/editable-artifacts";
|
|
1001
1644
|
import {
|
|
1002
|
-
EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
|
|
1003
1645
|
EditableArtifactApplication,
|
|
1004
1646
|
EditableArtifactCompatibilityError,
|
|
1005
1647
|
EditableArtifactLiveServer,
|
|
@@ -1007,14 +1649,16 @@ import {
|
|
|
1007
1649
|
SystemEditableArtifactLiveScheduler,
|
|
1008
1650
|
WebCryptoEditableArtifactLiveTokens
|
|
1009
1651
|
} from "@opengeni/core/editable-artifact-live";
|
|
1652
|
+
import { currentEditableArtifactCompatibility } from "@opengeni/contracts/editable-artifacts";
|
|
1010
1653
|
import {
|
|
1011
1654
|
PostgresEditableArtifactLiveReadStore,
|
|
1012
1655
|
PostgresEditableArtifactLiveTicketStore,
|
|
1013
1656
|
PostgresEditableArtifactStore,
|
|
1014
1657
|
listEditableArtifactIdsForSession,
|
|
1015
1658
|
touchEditableArtifactSessionLink,
|
|
1659
|
+
recordNativeDocumentInspection,
|
|
1016
1660
|
dbSql,
|
|
1017
|
-
withRlsContext
|
|
1661
|
+
withRlsContext as withRlsContext2
|
|
1018
1662
|
} from "@opengeni/db";
|
|
1019
1663
|
import { PostgresEditableArtifactDurableExportStore } from "@opengeni/db/editable-artifact-durable-export";
|
|
1020
1664
|
import {
|
|
@@ -1078,7 +1722,7 @@ async function requireFlushBarrier(connection) {
|
|
|
1078
1722
|
}
|
|
1079
1723
|
|
|
1080
1724
|
// src/editable-artifact-native-kernel.ts
|
|
1081
|
-
import { createHash } from "crypto";
|
|
1725
|
+
import { createHash as createHash2 } from "crypto";
|
|
1082
1726
|
import { pathToFileURL } from "url";
|
|
1083
1727
|
import {
|
|
1084
1728
|
createNativeArtifactSession,
|
|
@@ -1093,6 +1737,13 @@ import {
|
|
|
1093
1737
|
} from "@opengeni/contracts/editable-artifact-causal-frontier";
|
|
1094
1738
|
import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
|
|
1095
1739
|
import { decodeEditableArtifactMutationIntent } from "@opengeni/contracts/editable-artifacts";
|
|
1740
|
+
import {
|
|
1741
|
+
COMMITTED_TRANSACTION_PROTOCOL_VERSION,
|
|
1742
|
+
DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION,
|
|
1743
|
+
PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION,
|
|
1744
|
+
SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
|
|
1745
|
+
SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
|
|
1746
|
+
} from "@opengeni/contracts/editable-artifact-versions";
|
|
1096
1747
|
import {
|
|
1097
1748
|
MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
|
|
1098
1749
|
causalFrontiersEqual,
|
|
@@ -1101,9 +1752,6 @@ import {
|
|
|
1101
1752
|
editableArtifactReplicaId,
|
|
1102
1753
|
editableArtifactStateHash
|
|
1103
1754
|
} from "@opengeni/core";
|
|
1104
|
-
var MODEL_SCHEMA_VERSION = 1;
|
|
1105
|
-
var OPERATION_PROTOCOL_VERSION = 1;
|
|
1106
|
-
var CRDT_STATE_VERSION = 1;
|
|
1107
1755
|
var SNAPSHOT_CHUNK_BYTES = 1024 * 1024;
|
|
1108
1756
|
async function loadVerifiedNativeArtifactRuntimeBinding() {
|
|
1109
1757
|
const location = await doctorConfiguredArtifactRuntime();
|
|
@@ -1136,7 +1784,7 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1136
1784
|
modality: "spreadsheet",
|
|
1137
1785
|
committedTransactionBytes,
|
|
1138
1786
|
kernelVersion: this.runtime.buildIdentity,
|
|
1139
|
-
modelSchemaVersion:
|
|
1787
|
+
modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION
|
|
1140
1788
|
});
|
|
1141
1789
|
}
|
|
1142
1790
|
if (session.modality !== request.modality) {
|
|
@@ -1148,7 +1796,7 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1148
1796
|
nativeReceiptBytes,
|
|
1149
1797
|
resultingStateHash: editableArtifactStateHash(session.stateHash()),
|
|
1150
1798
|
kernelVersion: this.runtime.buildIdentity,
|
|
1151
|
-
modelSchemaVersion:
|
|
1799
|
+
modelSchemaVersion: modelSchemaVersionFor(request.modality)
|
|
1152
1800
|
});
|
|
1153
1801
|
} finally {
|
|
1154
1802
|
session.dispose();
|
|
@@ -1198,11 +1846,11 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1198
1846
|
canonicalChunks: fixedChunks(snapshot, input.signal),
|
|
1199
1847
|
canonicalByteSize: snapshot.byteLength,
|
|
1200
1848
|
canonicalContentHash: editableArtifactContentHash(
|
|
1201
|
-
`sha256:${
|
|
1849
|
+
`sha256:${createHash2("sha256").update(snapshot).digest("hex")}`
|
|
1202
1850
|
),
|
|
1203
1851
|
stateHash: editableArtifactStateHash(session.stateHash()),
|
|
1204
1852
|
coveredHeadSequence: 0,
|
|
1205
|
-
modelSchemaVersion:
|
|
1853
|
+
modelSchemaVersion: modelSchemaVersionFor(input.modality),
|
|
1206
1854
|
kernelVersion: this.runtime.buildIdentity
|
|
1207
1855
|
};
|
|
1208
1856
|
if (session.modality === "spreadsheet") {
|
|
@@ -1210,8 +1858,8 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1210
1858
|
...common,
|
|
1211
1859
|
modality: "spreadsheet",
|
|
1212
1860
|
coveredCausalFrontier: domainFrontier(session.frontier()),
|
|
1213
|
-
operationProtocolVersion:
|
|
1214
|
-
crdtStateVersion:
|
|
1861
|
+
operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
|
|
1862
|
+
crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
|
|
1215
1863
|
});
|
|
1216
1864
|
}
|
|
1217
1865
|
return Object.freeze({
|
|
@@ -1245,11 +1893,11 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1245
1893
|
canonicalChunks: fixedChunks(canonical, input.signal),
|
|
1246
1894
|
canonicalByteSize: canonical.byteLength,
|
|
1247
1895
|
canonicalContentHash: editableArtifactContentHash(
|
|
1248
|
-
`sha256:${
|
|
1896
|
+
`sha256:${createHash2("sha256").update(canonical).digest("hex")}`
|
|
1249
1897
|
),
|
|
1250
1898
|
stateHash: editableArtifactStateHash(session.stateHash()),
|
|
1251
1899
|
coveredHeadSequence: input.state.artifact.headSequence,
|
|
1252
|
-
modelSchemaVersion:
|
|
1900
|
+
modelSchemaVersion: modelSchemaVersionFor(input.state.modality),
|
|
1253
1901
|
kernelVersion: this.runtime.buildIdentity
|
|
1254
1902
|
};
|
|
1255
1903
|
if (session.modality === "spreadsheet") {
|
|
@@ -1258,8 +1906,8 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1258
1906
|
...common,
|
|
1259
1907
|
modality: session.modality,
|
|
1260
1908
|
coveredCausalFrontier: domainFrontier(session.frontier()),
|
|
1261
|
-
operationProtocolVersion:
|
|
1262
|
-
crdtStateVersion:
|
|
1909
|
+
operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
|
|
1910
|
+
crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
|
|
1263
1911
|
});
|
|
1264
1912
|
}
|
|
1265
1913
|
if (session.modality !== input.state.modality) throw new Error("Native modality mismatch");
|
|
@@ -1292,7 +1940,7 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1292
1940
|
const stateHash = editableArtifactStateHash(session.stateHash());
|
|
1293
1941
|
if (stateHash !== expected.stateHash) throw new Error("Snapshot state hash mismatch");
|
|
1294
1942
|
const canonicalContentHash = editableArtifactContentHash(
|
|
1295
|
-
`sha256:${
|
|
1943
|
+
`sha256:${createHash2("sha256").update(canonical).digest("hex")}`
|
|
1296
1944
|
);
|
|
1297
1945
|
if (session.modality === "spreadsheet") {
|
|
1298
1946
|
const coveredCausalFrontier = domainFrontier(session.frontier());
|
|
@@ -1312,12 +1960,12 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1312
1960
|
canonicalContentHash,
|
|
1313
1961
|
coveredHeadSequence,
|
|
1314
1962
|
stateHash,
|
|
1315
|
-
modelSchemaVersion:
|
|
1963
|
+
modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
|
|
1316
1964
|
kernelVersion: this.runtime.buildIdentity,
|
|
1317
1965
|
fullCrdtStateVerified: true,
|
|
1318
1966
|
coveredCausalFrontier,
|
|
1319
|
-
operationProtocolVersion:
|
|
1320
|
-
crdtStateVersion:
|
|
1967
|
+
operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
|
|
1968
|
+
crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
|
|
1321
1969
|
...replayedTarget2 ? { replayedTarget: replayedTarget2 } : {}
|
|
1322
1970
|
});
|
|
1323
1971
|
}
|
|
@@ -1339,7 +1987,7 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1339
1987
|
canonicalContentHash,
|
|
1340
1988
|
coveredHeadSequence,
|
|
1341
1989
|
stateHash,
|
|
1342
|
-
modelSchemaVersion:
|
|
1990
|
+
modelSchemaVersion: modelSchemaVersionFor(session.modality),
|
|
1343
1991
|
kernelVersion: this.runtime.buildIdentity,
|
|
1344
1992
|
nativeRevision,
|
|
1345
1993
|
...replayedTarget ? { replayedTarget } : {}
|
|
@@ -1349,6 +1997,10 @@ var NativeEditableArtifactKernelAdapter = class {
|
|
|
1349
1997
|
}
|
|
1350
1998
|
}
|
|
1351
1999
|
};
|
|
2000
|
+
function modelSchemaVersionFor(modality) {
|
|
2001
|
+
if (modality === "spreadsheet") return SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION;
|
|
2002
|
+
return modality === "document" ? DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION : PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION;
|
|
2003
|
+
}
|
|
1352
2004
|
function assertRuntime(candidate, location) {
|
|
1353
2005
|
if (typeof candidate !== "object" || candidate === null) {
|
|
1354
2006
|
throw new Error("Verified artifact facade returned no runtime");
|
|
@@ -1504,7 +2156,7 @@ async function readVerifiedSnapshot(objects, snapshot, signal) {
|
|
|
1504
2156
|
snapshot.byteSize,
|
|
1505
2157
|
signal
|
|
1506
2158
|
);
|
|
1507
|
-
const contentHash = `sha256:${
|
|
2159
|
+
const contentHash = `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
|
|
1508
2160
|
if (contentHash !== snapshot.contentHash) throw new Error("Snapshot content hash mismatch");
|
|
1509
2161
|
await object.assertUnchanged(signal);
|
|
1510
2162
|
return bytes;
|
|
@@ -1540,7 +2192,7 @@ async function* fixedChunks(bytes, signal) {
|
|
|
1540
2192
|
}
|
|
1541
2193
|
}
|
|
1542
2194
|
function namespaceForArtifact(artifactId) {
|
|
1543
|
-
const digest =
|
|
2195
|
+
const digest = createHash2("sha256").update(artifactId).digest();
|
|
1544
2196
|
let value = 0n;
|
|
1545
2197
|
for (let index = 0; index < 8; index += 1) value = value << 8n | BigInt(digest[index]);
|
|
1546
2198
|
return value === 0n ? 1n : value;
|
|
@@ -1564,7 +2216,7 @@ function throwIfAborted(signal) {
|
|
|
1564
2216
|
}
|
|
1565
2217
|
|
|
1566
2218
|
// src/editable-artifact-office-import.ts
|
|
1567
|
-
import { createHash as
|
|
2219
|
+
import { createHash as createHash3 } from "crypto";
|
|
1568
2220
|
import {
|
|
1569
2221
|
ArtifactOfficeSourceUnsupportedError,
|
|
1570
2222
|
prepareArtifactOfficeImport
|
|
@@ -1581,7 +2233,8 @@ import {
|
|
|
1581
2233
|
import { requireLiveAgentAttemptAuthorization } from "@opengeni/core";
|
|
1582
2234
|
import { getFilesForSubject } from "@opengeni/db";
|
|
1583
2235
|
import {
|
|
1584
|
-
MAX_BOUNDED_OBJECT_CHUNK_BYTES
|
|
2236
|
+
MAX_BOUNDED_OBJECT_CHUNK_BYTES,
|
|
2237
|
+
retryWhileMissing
|
|
1585
2238
|
} from "@opengeni/storage";
|
|
1586
2239
|
var SNAPSHOT_MIME = "application/vnd.opengeni.editable-artifact-snapshot";
|
|
1587
2240
|
var EditableArtifactOfficeImportAdapter = class {
|
|
@@ -1712,23 +2365,47 @@ function officeFilenameMatches(filename, modality) {
|
|
|
1712
2365
|
}
|
|
1713
2366
|
async function readVerifiedWorkspaceFile(storage, file, signal) {
|
|
1714
2367
|
throwIfAborted2(signal);
|
|
1715
|
-
const before = await
|
|
2368
|
+
const before = await retryWhileMissing(async () => {
|
|
2369
|
+
try {
|
|
2370
|
+
return await storage.headFile(file);
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
if (typeof storage.fileExists === "function" && !await storage.fileExists(file)) {
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
throw error;
|
|
2376
|
+
}
|
|
2377
|
+
});
|
|
2378
|
+
if (!before) {
|
|
2379
|
+
throw new EditableArtifactOfficeImportError("source_changed");
|
|
2380
|
+
}
|
|
1716
2381
|
if (before.ContentLength !== file.sizeBytes || before.ContentType !== file.contentType || before.Metadata?.sha256 !== file.sha256 || typeof before.VersionToken !== "string" || before.VersionToken.length < 1) {
|
|
1717
2382
|
throw new EditableArtifactOfficeImportError("source_changed");
|
|
1718
2383
|
}
|
|
1719
2384
|
const bytes = new Uint8Array(file.sizeBytes);
|
|
1720
|
-
const digest =
|
|
2385
|
+
const digest = createHash3("sha256");
|
|
1721
2386
|
for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
|
|
1722
2387
|
throwIfAborted2(signal);
|
|
1723
2388
|
const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
|
|
1724
|
-
const chunk = await storage.getFileRange(file, { start, end });
|
|
2389
|
+
const chunk = await retryWhileMissing(async () => storage.getFileRange(file, { start, end }));
|
|
1725
2390
|
if (!chunk || chunk.byteLength !== end - start + 1) {
|
|
1726
2391
|
throw new EditableArtifactOfficeImportError("source_changed");
|
|
1727
2392
|
}
|
|
1728
2393
|
bytes.set(chunk, start);
|
|
1729
2394
|
digest.update(chunk);
|
|
1730
2395
|
}
|
|
1731
|
-
const after = await
|
|
2396
|
+
const after = await retryWhileMissing(async () => {
|
|
2397
|
+
try {
|
|
2398
|
+
return await storage.headFile(file);
|
|
2399
|
+
} catch (error) {
|
|
2400
|
+
if (typeof storage.fileExists === "function" && !await storage.fileExists(file)) {
|
|
2401
|
+
return null;
|
|
2402
|
+
}
|
|
2403
|
+
throw error;
|
|
2404
|
+
}
|
|
2405
|
+
});
|
|
2406
|
+
if (!after) {
|
|
2407
|
+
throw new EditableArtifactOfficeImportError("source_changed");
|
|
2408
|
+
}
|
|
1732
2409
|
const sha256 = digest.digest("hex");
|
|
1733
2410
|
if (after.ContentLength !== before.ContentLength || after.ContentType !== before.ContentType || after.Metadata?.sha256 !== before.Metadata?.sha256 || after.VersionToken !== before.VersionToken || sha256 !== file.sha256) {
|
|
1734
2411
|
throw new EditableArtifactOfficeImportError("source_changed");
|
|
@@ -1766,8 +2443,9 @@ function throwIfAborted2(signal) {
|
|
|
1766
2443
|
}
|
|
1767
2444
|
|
|
1768
2445
|
// src/editable-artifact-workspace-files.ts
|
|
1769
|
-
import { createHash as
|
|
2446
|
+
import { createHash as createHash4 } from "crypto";
|
|
1770
2447
|
import { completeFileUpload, prepareGeneratedWorkspaceFile } from "@opengeni/db";
|
|
2448
|
+
import { retryWhileMissing as retryWhileMissing2 } from "@opengeni/storage";
|
|
1771
2449
|
var UPLOAD_INTENT_TTL_MS = 60 * 6e4;
|
|
1772
2450
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
1773
2451
|
var EditableArtifactWorkspaceFileAdapter = class {
|
|
@@ -1824,7 +2502,7 @@ var EditableArtifactWorkspaceFileAdapter = class {
|
|
|
1824
2502
|
if (existing) {
|
|
1825
2503
|
assertStoredExport(existing, download.byteSize, download.mimeType, sha256);
|
|
1826
2504
|
} else {
|
|
1827
|
-
await storage.putObjectStreamIfAbsent({
|
|
2505
|
+
const created = await storage.putObjectStreamIfAbsent({
|
|
1828
2506
|
key: objectKey,
|
|
1829
2507
|
contentType: download.mimeType,
|
|
1830
2508
|
chunks: download.chunks(input.signal ? { signal: input.signal } : {}),
|
|
@@ -1832,18 +2510,28 @@ var EditableArtifactWorkspaceFileAdapter = class {
|
|
|
1832
2510
|
sha256,
|
|
1833
2511
|
...input.signal ? { signal: input.signal } : {}
|
|
1834
2512
|
});
|
|
1835
|
-
|
|
1836
|
-
|
|
2513
|
+
if (!created) {
|
|
2514
|
+
await assertVisibleExport(
|
|
2515
|
+
storage,
|
|
2516
|
+
objectKey,
|
|
2517
|
+
download.byteSize,
|
|
2518
|
+
download.mimeType,
|
|
2519
|
+
sha256,
|
|
2520
|
+
input.signal
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
1837
2523
|
}
|
|
1838
2524
|
await download.assertUnchanged(input.signal);
|
|
1839
2525
|
throwIfAborted3(input.signal);
|
|
1840
2526
|
file = await this.completeFile(this.dependencies.db, input.scope.workspaceId, uploadId);
|
|
1841
2527
|
} else {
|
|
1842
|
-
|
|
1843
|
-
|
|
2528
|
+
await assertVisibleExport(
|
|
2529
|
+
storage,
|
|
2530
|
+
objectKey,
|
|
1844
2531
|
download.byteSize,
|
|
1845
2532
|
download.mimeType,
|
|
1846
|
-
sha256
|
|
2533
|
+
sha256,
|
|
2534
|
+
input.signal
|
|
1847
2535
|
);
|
|
1848
2536
|
await download.assertUnchanged(input.signal);
|
|
1849
2537
|
}
|
|
@@ -1867,6 +2555,13 @@ var EditableArtifactWorkspaceFileAdapter = class {
|
|
|
1867
2555
|
}
|
|
1868
2556
|
}
|
|
1869
2557
|
};
|
|
2558
|
+
async function assertVisibleExport(storage, objectKey, byteSize, contentType, sha256, signal) {
|
|
2559
|
+
const head = await retryWhileMissing2(
|
|
2560
|
+
async () => await storage.headObject(objectKey),
|
|
2561
|
+
signal ? { signal } : {}
|
|
2562
|
+
);
|
|
2563
|
+
assertStoredExport(head, byteSize, contentType, sha256);
|
|
2564
|
+
}
|
|
1870
2565
|
function assertStoredExport(head, byteSize, contentType, sha256) {
|
|
1871
2566
|
if (!head || head.ContentLength !== byteSize || head.ContentType !== contentType || head.Metadata?.sha256 !== sha256 || typeof head.VersionToken !== "string" || head.VersionToken.length < 1) {
|
|
1872
2567
|
throw new Error("Workspace export object differs from its durable materialization");
|
|
@@ -1884,7 +2579,7 @@ function plainSha256(value) {
|
|
|
1884
2579
|
return match[1];
|
|
1885
2580
|
}
|
|
1886
2581
|
function deterministicUuid(seed) {
|
|
1887
|
-
const bytes =
|
|
2582
|
+
const bytes = createHash4("sha256").update(seed, "utf8").digest().subarray(0, 16);
|
|
1888
2583
|
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
1889
2584
|
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
1890
2585
|
const hex = bytes.toString("hex");
|
|
@@ -1973,6 +2668,11 @@ async function createStandaloneEditableArtifactApplication(input) {
|
|
|
1973
2668
|
touch: async ({ scope, sessionId, artifactId }) => await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId)
|
|
1974
2669
|
}),
|
|
1975
2670
|
inspector: kernel,
|
|
2671
|
+
recordInspection: async (inspection) => {
|
|
2672
|
+
if (inspection.actor.kind !== "agent")
|
|
2673
|
+
throw new Error("Inspection proof requires an agent attempt");
|
|
2674
|
+
return recordNativeDocumentInspection(input.db, { ...inspection, actor: inspection.actor });
|
|
2675
|
+
},
|
|
1976
2676
|
officeImports,
|
|
1977
2677
|
workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
|
|
1978
2678
|
db: input.db,
|
|
@@ -1998,7 +2698,8 @@ async function createStandaloneEditableArtifactApplication(input) {
|
|
|
1998
2698
|
});
|
|
1999
2699
|
const compatibility = Object.freeze({
|
|
2000
2700
|
assertCompatible(request) {
|
|
2001
|
-
|
|
2701
|
+
const current = currentEditableArtifactCompatibility(request.artifact.modality);
|
|
2702
|
+
if (request.modality !== request.artifact.modality || request.liveProtocolVersion !== current.liveProtocolVersion || request.modelSchemaVersion !== current.modelSchemaVersion || request.snapshotVersion !== current.snapshotVersion || request.commandProtocolVersion !== current.commandProtocolVersion || request.committedTransactionProtocolVersion !== current.committedTransactionProtocolVersion) {
|
|
2002
2703
|
throw new EditableArtifactCompatibilityError();
|
|
2003
2704
|
}
|
|
2004
2705
|
}
|
|
@@ -2030,7 +2731,7 @@ var PostgresEditableArtifactAuthorization = class {
|
|
|
2030
2731
|
}
|
|
2031
2732
|
async authorize(request) {
|
|
2032
2733
|
const actor = request.actor;
|
|
2033
|
-
const rows = await
|
|
2734
|
+
const rows = await withRlsContext2(
|
|
2034
2735
|
this.db,
|
|
2035
2736
|
request.scope,
|
|
2036
2737
|
async (tx) => rawRows(
|
|
@@ -2108,22 +2809,24 @@ var PollingEditableArtifactAuthorizationInvalidations = class {
|
|
|
2108
2809
|
};
|
|
2109
2810
|
var RandomEditableArtifactStableIds = class {
|
|
2110
2811
|
next(_kind) {
|
|
2111
|
-
return
|
|
2812
|
+
return randomUUID2().replaceAll("-", "");
|
|
2112
2813
|
}
|
|
2113
2814
|
};
|
|
2114
2815
|
var RandomEditableArtifactDurableExportIds = class {
|
|
2115
2816
|
next(_kind) {
|
|
2116
|
-
return
|
|
2817
|
+
return randomUUID2().replaceAll("-", "");
|
|
2117
2818
|
}
|
|
2118
2819
|
};
|
|
2119
2820
|
function materializationProfiles(capabilities) {
|
|
2120
|
-
|
|
2821
|
+
const spreadsheetCompatibility = currentEditableArtifactCompatibility("spreadsheet");
|
|
2822
|
+
if (capabilities.protocol !== "OGAMC001" || capabilities.runtimeKind !== "native" || capabilities.maxOutputBytes !== EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES || capabilities.codecVersions["opengeni.xlsx"] === void 0 || capabilities.supportedModelSchemaVersions[0] !== spreadsheetCompatibility.modelSchemaVersion || capabilities.supportedOperationProtocolVersions[0] !== spreadsheetCompatibility.committedTransactionProtocolVersion || capabilities.supportedSnapshotProtocolVersions[0] !== spreadsheetCompatibility.snapshotVersion) {
|
|
2121
2823
|
throw new Error("Verified artifact materializer profile is incompatible");
|
|
2122
2824
|
}
|
|
2123
2825
|
const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
|
|
2124
2826
|
return Object.freeze({
|
|
2827
|
+
supportedFormats: EDITABLE_ARTIFACT_EXPORT_FORMATS,
|
|
2125
2828
|
async resolve(input) {
|
|
2126
|
-
if (input.modality !== "spreadsheet" || input.format !== "xlsx" || Object.keys(input.options).length !== 0) {
|
|
2829
|
+
if (input.modality !== "spreadsheet" || input.format !== "xlsx" || !EDITABLE_ARTIFACT_EXPORT_FORMATS[input.modality].includes(input.format) || Object.keys(input.options).length !== 0) {
|
|
2127
2830
|
return null;
|
|
2128
2831
|
}
|
|
2129
2832
|
return Object.freeze({
|
|
@@ -2152,7 +2855,7 @@ function snapshotBytesPort(objects) {
|
|
|
2152
2855
|
throw new Error("Editable artifact snapshot content type mismatch");
|
|
2153
2856
|
}
|
|
2154
2857
|
const bytes = new Uint8Array(snapshot.byteSize);
|
|
2155
|
-
const digest =
|
|
2858
|
+
const digest = createHash5("sha256");
|
|
2156
2859
|
let offset = 0;
|
|
2157
2860
|
for await (const chunk of object.chunks({
|
|
2158
2861
|
chunkBytes: MAX_BOUNDED_OBJECT_CHUNK_BYTES2
|
|
@@ -2182,6 +2885,169 @@ function rawRows(result) {
|
|
|
2182
2885
|
throw new Error("Unsupported database execute result shape");
|
|
2183
2886
|
}
|
|
2184
2887
|
|
|
2888
|
+
// src/fatal-process-boundary.ts
|
|
2889
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2890
|
+
var API_FATAL_FLUSH_TIMEOUT_MS = 1e3;
|
|
2891
|
+
var API_FATAL_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2892
|
+
var FATAL_ERROR_CODES = {
|
|
2893
|
+
startup_failure: "api_startup_failed",
|
|
2894
|
+
unhandled_rejection: "api_unhandled_rejection",
|
|
2895
|
+
uncaught_exception: "api_uncaught_exception"
|
|
2896
|
+
};
|
|
2897
|
+
function installApiFatalProcessBoundary(options = {}) {
|
|
2898
|
+
const runtimeProcess = options.process ?? defaultProcessBoundary();
|
|
2899
|
+
const flushTimeoutMs = options.flushTimeoutMs ?? API_FATAL_FLUSH_TIMEOUT_MS;
|
|
2900
|
+
const correlationId = options.correlationId ?? (() => `api-fatal.${randomUUID3()}`);
|
|
2901
|
+
const fallbackLog = options.fallbackLog ?? ((message) => console.error(message));
|
|
2902
|
+
let observability = options.observability;
|
|
2903
|
+
let phase = "startup";
|
|
2904
|
+
let reporting = false;
|
|
2905
|
+
const report = async (event, reason) => {
|
|
2906
|
+
if (reporting) return;
|
|
2907
|
+
reporting = true;
|
|
2908
|
+
const diagnostic = apiFatalDiagnostic(event, phase, reason, correlationId);
|
|
2909
|
+
let message = apiFatalMessage(diagnostic);
|
|
2910
|
+
const activeObservability = observability;
|
|
2911
|
+
try {
|
|
2912
|
+
if (activeObservability) {
|
|
2913
|
+
try {
|
|
2914
|
+
const diagnosticId = activeObservability.recordFailureDiagnostic?.({
|
|
2915
|
+
code: FATAL_ERROR_CODES[event],
|
|
2916
|
+
stage: phase,
|
|
2917
|
+
retryDecision: "not_retryable",
|
|
2918
|
+
error: reason
|
|
2919
|
+
});
|
|
2920
|
+
if (diagnosticId) {
|
|
2921
|
+
diagnostic.correlationId = diagnosticId;
|
|
2922
|
+
message = apiFatalMessage(diagnostic);
|
|
2923
|
+
}
|
|
2924
|
+
} catch {
|
|
2925
|
+
}
|
|
2926
|
+
let logged = false;
|
|
2927
|
+
try {
|
|
2928
|
+
activeObservability.error(message, diagnostic);
|
|
2929
|
+
logged = true;
|
|
2930
|
+
} catch {
|
|
2931
|
+
}
|
|
2932
|
+
if (!logged) {
|
|
2933
|
+
safeFallbackLog(fallbackLog, message);
|
|
2934
|
+
}
|
|
2935
|
+
try {
|
|
2936
|
+
const span = activeObservability.startSpan("api.process.fatal", diagnostic);
|
|
2937
|
+
span.end({ error: true });
|
|
2938
|
+
} catch {
|
|
2939
|
+
}
|
|
2940
|
+
await flushWithin(activeObservability, flushTimeoutMs);
|
|
2941
|
+
} else {
|
|
2942
|
+
safeFallbackLog(fallbackLog, message);
|
|
2943
|
+
}
|
|
2944
|
+
} finally {
|
|
2945
|
+
runtimeProcess.exit(1);
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
const onUnhandledRejection = (reason) => {
|
|
2949
|
+
void report("unhandled_rejection", reason);
|
|
2950
|
+
};
|
|
2951
|
+
const onUncaughtException = (reason) => {
|
|
2952
|
+
void report("uncaught_exception", reason);
|
|
2953
|
+
};
|
|
2954
|
+
runtimeProcess.on("unhandledRejection", onUnhandledRejection);
|
|
2955
|
+
runtimeProcess.on("uncaughtException", onUncaughtException);
|
|
2956
|
+
return {
|
|
2957
|
+
attachObservability: (value) => {
|
|
2958
|
+
observability = value;
|
|
2959
|
+
},
|
|
2960
|
+
markRunning: () => {
|
|
2961
|
+
phase = "running";
|
|
2962
|
+
},
|
|
2963
|
+
reportStartupFailure: async (reason) => {
|
|
2964
|
+
await report("startup_failure", reason);
|
|
2965
|
+
},
|
|
2966
|
+
dispose: () => {
|
|
2967
|
+
runtimeProcess.off("unhandledRejection", onUnhandledRejection);
|
|
2968
|
+
runtimeProcess.off("uncaughtException", onUncaughtException);
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
function apiFatalDiagnostic(event, phase, reason, correlationId) {
|
|
2973
|
+
let safeCorrelationId = "api-fatal.fallback";
|
|
2974
|
+
try {
|
|
2975
|
+
const candidate = correlationId();
|
|
2976
|
+
if (API_FATAL_CORRELATION_ID_PATTERN.test(candidate)) {
|
|
2977
|
+
safeCorrelationId = candidate;
|
|
2978
|
+
}
|
|
2979
|
+
} catch {
|
|
2980
|
+
}
|
|
2981
|
+
return {
|
|
2982
|
+
errorClass: "ApiFatalOperationError",
|
|
2983
|
+
errorCode: FATAL_ERROR_CODES[event],
|
|
2984
|
+
origin: "api",
|
|
2985
|
+
phase,
|
|
2986
|
+
reasonKind: apiFatalReasonKind(reason),
|
|
2987
|
+
correlationId: safeCorrelationId
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
function apiFatalReasonKind(reason) {
|
|
2991
|
+
if (reason === null) return "null";
|
|
2992
|
+
const kind = typeof reason;
|
|
2993
|
+
if (kind !== "object") return kind;
|
|
2994
|
+
try {
|
|
2995
|
+
return reason instanceof Error ? "error" : "object";
|
|
2996
|
+
} catch {
|
|
2997
|
+
return "object";
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
function apiFatalMessage(diagnostic) {
|
|
3001
|
+
return `OpenGeni API fatal process failure (${diagnostic.errorCode}; phase=${diagnostic.phase}; reason_kind=${diagnostic.reasonKind}; correlation_id=${diagnostic.correlationId})`;
|
|
3002
|
+
}
|
|
3003
|
+
async function flushWithin(observability, timeoutMs) {
|
|
3004
|
+
let flush;
|
|
3005
|
+
try {
|
|
3006
|
+
flush = Promise.resolve(observability.flush()).catch(() => void 0);
|
|
3007
|
+
} catch {
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return;
|
|
3011
|
+
let timeout;
|
|
3012
|
+
try {
|
|
3013
|
+
await Promise.race([
|
|
3014
|
+
flush,
|
|
3015
|
+
new Promise((resolve) => {
|
|
3016
|
+
timeout = setTimeout(resolve, timeoutMs);
|
|
3017
|
+
})
|
|
3018
|
+
]);
|
|
3019
|
+
} finally {
|
|
3020
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
function safeFallbackLog(fallbackLog, message) {
|
|
3024
|
+
try {
|
|
3025
|
+
fallbackLog(message);
|
|
3026
|
+
} catch {
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
function defaultProcessBoundary() {
|
|
3030
|
+
return {
|
|
3031
|
+
on: (event, listener) => {
|
|
3032
|
+
if (event === "unhandledRejection") {
|
|
3033
|
+
process.on("unhandledRejection", listener);
|
|
3034
|
+
} else {
|
|
3035
|
+
process.on("uncaughtException", listener);
|
|
3036
|
+
}
|
|
3037
|
+
},
|
|
3038
|
+
off: (event, listener) => {
|
|
3039
|
+
if (event === "unhandledRejection") {
|
|
3040
|
+
process.off("unhandledRejection", listener);
|
|
3041
|
+
} else {
|
|
3042
|
+
process.off("uncaughtException", listener);
|
|
3043
|
+
}
|
|
3044
|
+
},
|
|
3045
|
+
exit: (code) => {
|
|
3046
|
+
process.exit(code);
|
|
3047
|
+
}
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
|
|
2185
3051
|
// src/index.ts
|
|
2186
3052
|
function isWorkflowAlreadyStarted(error) {
|
|
2187
3053
|
return error instanceof WorkflowExecutionAlreadyStartedError;
|
|
@@ -2206,7 +3072,40 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2206
3072
|
connection,
|
|
2207
3073
|
namespace: settings.temporalNamespace
|
|
2208
3074
|
});
|
|
3075
|
+
const scheduledTasks = createTemporalScheduleSynchronizer({
|
|
3076
|
+
db,
|
|
3077
|
+
withDeadline: (deadline, work) => temporal.withDeadline(deadline, work),
|
|
3078
|
+
upsert: async (task) => {
|
|
3079
|
+
const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
|
|
3080
|
+
try {
|
|
3081
|
+
await temporal.schedule.getHandle(task.temporalScheduleId).update(() => temporalScheduleUpdateOptions(options));
|
|
3082
|
+
} catch (error) {
|
|
3083
|
+
if (!shouldCreateScheduleAfterUpdateError(error)) throw error;
|
|
3084
|
+
await temporal.schedule.create(options);
|
|
3085
|
+
}
|
|
3086
|
+
},
|
|
3087
|
+
remove: async (temporalScheduleId) => {
|
|
3088
|
+
try {
|
|
3089
|
+
await temporal.schedule.getHandle(temporalScheduleId).delete();
|
|
3090
|
+
} catch (error) {
|
|
3091
|
+
if (!(error instanceof ScheduleNotFoundError)) throw error;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
});
|
|
2209
3095
|
const client = {
|
|
3096
|
+
triggerAutomationRun: async ({ accountId, workspaceId, runId }) => {
|
|
3097
|
+
try {
|
|
3098
|
+
await temporal.workflow.start("automationRunWorkflow", {
|
|
3099
|
+
taskQueue: settings.temporalTaskQueue,
|
|
3100
|
+
workflowId: `automation-run:${runId}`,
|
|
3101
|
+
workflowIdReusePolicy: "REJECT_DUPLICATE",
|
|
3102
|
+
args: [{ accountId, workspaceId, runId }]
|
|
3103
|
+
});
|
|
3104
|
+
} catch (error) {
|
|
3105
|
+
if (isWorkflowAlreadyStarted(error)) return;
|
|
3106
|
+
throw error;
|
|
3107
|
+
}
|
|
3108
|
+
},
|
|
2210
3109
|
signalUserMessage: async ({ eventId, workflowId }) => {
|
|
2211
3110
|
await temporal.workflow.getHandle(workflowId).signal("userMessage", eventId);
|
|
2212
3111
|
},
|
|
@@ -2216,7 +3115,8 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2216
3115
|
sessionId,
|
|
2217
3116
|
workflowId,
|
|
2218
3117
|
wakeRevision,
|
|
2219
|
-
interruptionRequested
|
|
3118
|
+
interruptionRequested,
|
|
3119
|
+
onSignalAccepted
|
|
2220
3120
|
}) => {
|
|
2221
3121
|
await temporal.workflow.signalWithStart("sessionWorkflow", {
|
|
2222
3122
|
taskQueue: settings.temporalTaskQueue,
|
|
@@ -2225,7 +3125,8 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2225
3125
|
args: [{ accountId, workspaceId, sessionId }],
|
|
2226
3126
|
signal: interruptionRequested ? "sessionControl" : "queueChanged"
|
|
2227
3127
|
});
|
|
2228
|
-
|
|
3128
|
+
onSignalAccepted?.();
|
|
3129
|
+
return await markSessionWorkflowWakeDelivered(db, {
|
|
2229
3130
|
accountId,
|
|
2230
3131
|
workspaceId,
|
|
2231
3132
|
sessionId,
|
|
@@ -2284,41 +3185,14 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2284
3185
|
wakeRevision: workflowWakeRevision
|
|
2285
3186
|
});
|
|
2286
3187
|
},
|
|
2287
|
-
syncScheduledTask: async ({ task }) =>
|
|
2288
|
-
|
|
2289
|
-
if (task.schedule.type === "manual") {
|
|
2290
|
-
try {
|
|
2291
|
-
await schedule.delete();
|
|
2292
|
-
} catch (error) {
|
|
2293
|
-
if (!(error instanceof ScheduleNotFoundError)) throw error;
|
|
2294
|
-
}
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
|
-
const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
|
|
2298
|
-
try {
|
|
2299
|
-
await schedule.update(() => temporalScheduleUpdateOptions(options));
|
|
2300
|
-
} catch (error) {
|
|
2301
|
-
if (!shouldCreateScheduleAfterUpdateError(error)) {
|
|
2302
|
-
throw error;
|
|
2303
|
-
}
|
|
2304
|
-
await temporal.schedule.create(options);
|
|
2305
|
-
}
|
|
2306
|
-
},
|
|
2307
|
-
deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
|
|
2308
|
-
try {
|
|
2309
|
-
await temporal.withDeadline(Date.now() + 5e3, async () => {
|
|
2310
|
-
await temporal.schedule.getHandle(temporalScheduleId).delete();
|
|
2311
|
-
});
|
|
2312
|
-
} catch (error) {
|
|
2313
|
-
if (error instanceof ScheduleNotFoundError) return;
|
|
2314
|
-
throw error;
|
|
2315
|
-
}
|
|
2316
|
-
},
|
|
3188
|
+
syncScheduledTask: async ({ task, onFailure }) => scheduledTasks.sync(task, onFailure),
|
|
3189
|
+
deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => scheduledTasks.remove(temporalScheduleId),
|
|
2317
3190
|
triggerScheduledTask: async ({
|
|
2318
3191
|
task,
|
|
2319
3192
|
agentRunUsageIdempotencyKey,
|
|
2320
3193
|
triggerWorkflowId,
|
|
2321
3194
|
initiator,
|
|
3195
|
+
credentialRestriction,
|
|
2322
3196
|
triggerType = "manual"
|
|
2323
3197
|
}) => {
|
|
2324
3198
|
const workflowId = triggerWorkflowId;
|
|
@@ -2334,7 +3208,8 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2334
3208
|
taskId: task.id,
|
|
2335
3209
|
triggerType,
|
|
2336
3210
|
agentRunUsageIdempotencyKey,
|
|
2337
|
-
initiator
|
|
3211
|
+
initiator,
|
|
3212
|
+
...credentialRestriction ? { credentialRestriction } : {}
|
|
2338
3213
|
}
|
|
2339
3214
|
]
|
|
2340
3215
|
});
|
|
@@ -2348,7 +3223,7 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2348
3223
|
startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {
|
|
2349
3224
|
const targetId = changeId ?? versionId;
|
|
2350
3225
|
if (!targetId) {
|
|
2351
|
-
throw new Error("
|
|
3226
|
+
throw new Error("sandbox environment verification requires changeId or versionId");
|
|
2352
3227
|
}
|
|
2353
3228
|
try {
|
|
2354
3229
|
await temporal.workflow.start("rigVerificationWorkflow", {
|
|
@@ -2391,11 +3266,12 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2391
3266
|
}
|
|
2392
3267
|
};
|
|
2393
3268
|
}
|
|
2394
|
-
async function startApi() {
|
|
2395
|
-
const settings = getSettings();
|
|
2396
|
-
const observability = createObservability(settings, { component: "api" });
|
|
3269
|
+
async function startApi(options = {}) {
|
|
3270
|
+
const settings = options.settings ?? getSettings();
|
|
3271
|
+
const observability = options.observability ?? createObservability(settings, { component: "api" });
|
|
2397
3272
|
const searchPath = dbSearchPath(settings);
|
|
2398
3273
|
const dbClient = createDb(settings.databaseUrl, {
|
|
3274
|
+
max: settings.apiDatabasePoolMax,
|
|
2399
3275
|
...searchPath ? { searchPath } : {},
|
|
2400
3276
|
rlsStrategy: settings.rlsStrategy
|
|
2401
3277
|
});
|
|
@@ -2406,21 +3282,37 @@ async function startApi() {
|
|
|
2406
3282
|
const databasePosture = {
|
|
2407
3283
|
rlsStrategy: settings.rlsStrategy,
|
|
2408
3284
|
expectedRole: settings.runtimeDatabaseRole,
|
|
2409
|
-
targetSchema: settings.dbSchema.trim() || "public"
|
|
3285
|
+
targetSchema: settings.dbSchema.trim() || "public",
|
|
3286
|
+
organizationTenancyCanonicalActivationEnabled: settings.organizationTenancyCanonicalActivationEnabled
|
|
2410
3287
|
};
|
|
2411
3288
|
const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);
|
|
2412
3289
|
try {
|
|
3290
|
+
const { ensureModalRegistryImage } = await import("@opengeni/runtime/sandbox");
|
|
3291
|
+
await retryStartupDependency(
|
|
3292
|
+
"Modal private-registry image",
|
|
3293
|
+
() => ensureModalRegistryImage(settings),
|
|
3294
|
+
{ ...retryOptions, onRetry }
|
|
3295
|
+
);
|
|
2413
3296
|
await retryStartupDependency(
|
|
2414
3297
|
"PostgreSQL runtime posture",
|
|
2415
3298
|
() => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
|
|
3299
|
+
{ ...retryOptions, onRetry, shouldRetry: isRetryableRuntimeDatabaseStartupError }
|
|
3300
|
+
);
|
|
3301
|
+
const resolvedCatalog = await retryStartupDependency(
|
|
3302
|
+
"model catalog",
|
|
3303
|
+
() => resolveCatalogSettings(dbClient.db, settings),
|
|
2416
3304
|
{ ...retryOptions, onRetry }
|
|
2417
3305
|
);
|
|
3306
|
+
observability.info("OpenGeni model catalog resolved", {
|
|
3307
|
+
catalogSource: resolvedCatalog.source,
|
|
3308
|
+
catalogVersion: resolvedCatalog.version
|
|
3309
|
+
});
|
|
2418
3310
|
bus = await retryStartupDependency(
|
|
2419
3311
|
"NATS",
|
|
2420
3312
|
() => createNatsEventBus(
|
|
2421
3313
|
settings.natsUrl,
|
|
2422
3314
|
controlPlaneAuth ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password } : void 0,
|
|
2423
|
-
|
|
3315
|
+
observabilityEventBusOptions(observability)
|
|
2424
3316
|
),
|
|
2425
3317
|
{
|
|
2426
3318
|
...retryOptions,
|
|
@@ -2481,30 +3373,54 @@ async function startApi() {
|
|
|
2481
3373
|
behavior: "http_and_websocket_fail_closed"
|
|
2482
3374
|
});
|
|
2483
3375
|
}
|
|
2484
|
-
const artifactWebSockets = new EditableArtifactWebSocketTransport(
|
|
3376
|
+
const artifactWebSockets = new EditableArtifactWebSocketTransport(
|
|
3377
|
+
routeDeps.editableArtifacts,
|
|
3378
|
+
editableArtifactSourceSessionAuthorizer(routeDeps)
|
|
3379
|
+
);
|
|
3380
|
+
const interactionFrameProxies = new InteractionFrameProxyTransport(
|
|
3381
|
+
resolveFirstPartyDelegationSecret(settings)
|
|
3382
|
+
);
|
|
3383
|
+
const localBrowserBoundary = createLocalBrowserBoundary(settings, {
|
|
3384
|
+
warn: (message, attributes) => observability.warn(message, attributes)
|
|
3385
|
+
});
|
|
3386
|
+
const webSocketUpgrades = [interactionFrameProxies, artifactWebSockets];
|
|
2485
3387
|
const server = Bun.serve({
|
|
2486
3388
|
hostname: settings.apiHost,
|
|
2487
3389
|
port: settings.apiPort,
|
|
2488
3390
|
idleTimeout: 255,
|
|
2489
3391
|
fetch: (request, bunServer) => {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
3392
|
+
const upgrade = dispatchApiWebSocketUpgrade(
|
|
3393
|
+
request,
|
|
3394
|
+
bunServer,
|
|
3395
|
+
webSocketUpgrades,
|
|
3396
|
+
localBrowserBoundary
|
|
3397
|
+
);
|
|
3398
|
+
if (upgrade.handled) return upgrade.response;
|
|
3399
|
+
return app.fetch(
|
|
3400
|
+
request,
|
|
3401
|
+
apiRequestBindingsForTransportPeer(bunServer.requestIP(request)?.address)
|
|
3402
|
+
);
|
|
2494
3403
|
},
|
|
2495
3404
|
websocket: {
|
|
2496
3405
|
maxPayloadLength: EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES,
|
|
2497
3406
|
backpressureLimit: 16 * 1024 * 1024,
|
|
2498
3407
|
closeOnBackpressureLimit: true,
|
|
2499
|
-
open: (socket) =>
|
|
2500
|
-
message: (socket, message) =>
|
|
2501
|
-
close: (socket) =>
|
|
3408
|
+
open: (socket) => socket.data.attach(socket),
|
|
3409
|
+
message: (socket, message) => socket.data.receive(message),
|
|
3410
|
+
close: (socket) => socket.data.transportClosed()
|
|
2502
3411
|
}
|
|
2503
3412
|
});
|
|
3413
|
+
const metricsServer = startApiMetricsListener(settings, observability);
|
|
2504
3414
|
const stopSlackInteractionPump = settings.slackSigningSecret ? startSlackInteractionPump(routeDeps) : void 0;
|
|
2505
3415
|
const stopMemorySlackPublicationPump = startMemorySlackPublicationPump(routeDeps);
|
|
3416
|
+
const stopWorkspaceWebhookDispatchPump = startWorkspaceWebhookDispatchPump({
|
|
3417
|
+
db: dbClient.db,
|
|
3418
|
+
settings,
|
|
3419
|
+
observability
|
|
3420
|
+
});
|
|
2506
3421
|
const stopTemporalScheduleCleanupPump = startTemporalScheduleCleanupPump({
|
|
2507
3422
|
db: dbClient.db,
|
|
3423
|
+
cleanupConnectorAuthorization: async (claim) => await cleanupScheduledTaskConnectorAuthorization(routeDeps, claim),
|
|
2508
3424
|
deleteSchedule: async (temporalScheduleId) => {
|
|
2509
3425
|
await workflowClient.client.deleteScheduledTaskSchedule({ temporalScheduleId });
|
|
2510
3426
|
},
|
|
@@ -2548,17 +3464,21 @@ async function startApi() {
|
|
|
2548
3464
|
}
|
|
2549
3465
|
observability.info("OpenGeni API listening", {
|
|
2550
3466
|
host: settings.apiHost,
|
|
2551
|
-
port: settings.apiPort
|
|
3467
|
+
port: settings.apiPort,
|
|
3468
|
+
...metricsServer ? { metricsPort: settings.apiMetricsPort } : {}
|
|
2552
3469
|
});
|
|
2553
3470
|
return {
|
|
2554
3471
|
server,
|
|
2555
3472
|
close: async () => {
|
|
2556
3473
|
server.stop(true);
|
|
3474
|
+
metricsServer?.stop(true);
|
|
2557
3475
|
stopSlackInteractionPump?.();
|
|
2558
3476
|
await stopMemorySlackPublicationPump();
|
|
2559
3477
|
stopMetricsIngestion?.();
|
|
2560
3478
|
stopHelloIngestion?.();
|
|
2561
3479
|
await stopTemporalScheduleCleanupPump();
|
|
3480
|
+
await stopWorkspaceWebhookDispatchPump();
|
|
3481
|
+
await routeDeps.userPresence?.close().catch(() => void 0);
|
|
2562
3482
|
await Promise.allSettled([
|
|
2563
3483
|
Promise.resolve(editableArtifactComposition?.close()),
|
|
2564
3484
|
authCalloutResponder?.close(),
|
|
@@ -2570,7 +3490,16 @@ async function startApi() {
|
|
|
2570
3490
|
};
|
|
2571
3491
|
}
|
|
2572
3492
|
if (import.meta.main) {
|
|
2573
|
-
|
|
3493
|
+
const fatalBoundary = installApiFatalProcessBoundary();
|
|
3494
|
+
try {
|
|
3495
|
+
const settings = getSettings();
|
|
3496
|
+
const observability = createObservability(settings, { component: "api" });
|
|
3497
|
+
fatalBoundary.attachObservability(observability);
|
|
3498
|
+
await startApi({ settings, observability });
|
|
3499
|
+
fatalBoundary.markRunning();
|
|
3500
|
+
} catch (error) {
|
|
3501
|
+
await fatalBoundary.reportStartupFailure(error);
|
|
3502
|
+
}
|
|
2574
3503
|
}
|
|
2575
3504
|
function temporalOverlapPolicy(policy) {
|
|
2576
3505
|
if (policy === "skip") {
|
|
@@ -2675,7 +3604,7 @@ function temporalScheduleOptions(task, taskQueue) {
|
|
|
2675
3604
|
}
|
|
2676
3605
|
function scheduledTaskEffectivelyPaused(task) {
|
|
2677
3606
|
if (task.status === "paused") return true;
|
|
2678
|
-
if (task
|
|
3607
|
+
if (!scheduledTaskKnowledgeSource(task)) return false;
|
|
2679
3608
|
const control = task.metadata.knowledgeSourceSync;
|
|
2680
3609
|
if (!control || typeof control !== "object") return false;
|
|
2681
3610
|
const record = control;
|