@opengeni/api-router 0.30.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-grant-rls.d.ts +3 -0
- package/dist/admission-trace.d.ts +8 -0
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.d.ts +50 -2
- package/dist/app.js +12 -1
- package/dist/artifact-catalog-cursor.d.ts +27 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
- package/dist/auth/managed-auth.d.ts +102 -3
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/organization-user-setup.d.ts +80 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-6TZ5LG3O.js +123948 -0
- package/dist/chunk-6TZ5LG3O.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-subscription-account-conflicts.d.ts +1 -0
- package/dist/claude-subscription-account-usage.d.ts +39 -0
- package/dist/claude-subscription-oauth.d.ts +25 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +44 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +32 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-ownership.d.ts +54 -0
- package/dist/controller-data-plane.d.ts +32 -0
- package/dist/editable-artifact-office-import.d.ts +7 -4
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +10 -18
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/github-access.d.ts +31 -2
- package/dist/http/acting-person.d.ts +24 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +27 -1
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +52 -0
- package/dist/http/sse.d.ts +17 -6
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1129 -166
- package/dist/index.js.map +1 -1
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +29 -3
- package/dist/integrations/connect-authority.d.ts +1 -0
- package/dist/integrations/connect-callback-return.d.ts +6 -0
- package/dist/integrations/fiken.d.ts +16 -2
- package/dist/integrations/github-app-connect.d.ts +47 -0
- package/dist/integrations/github-installation-proof.d.ts +3 -0
- package/dist/integrations/github-lens-connect.d.ts +116 -0
- package/dist/integrations/github-skill-source.d.ts +1 -5
- package/dist/integrations/google-drive.d.ts +25 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +2691 -0
- package/dist/integrations/oauth-client.d.ts +178 -18
- package/dist/integrations/oauth-profiles.d.ts +222 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github-repositories.d.ts +84 -0
- package/dist/integrations/personal-github.d.ts +42 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +19 -1
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +367 -12
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/slack-interactions.d.ts +113 -10
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/social-oauth.d.ts +7 -0
- package/dist/interaction-agent-access.d.ts +15 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/action-catalog-types.d.ts +9 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/command-read.d.ts +24 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/contract-input.d.ts +11 -0
- package/dist/mcp/documents.d.ts +7 -5
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-input.d.ts +1189 -0
- package/dist/mcp/scheduled-task-view.d.ts +22 -4
- package/dist/mcp/server.d.ts +255 -3
- package/dist/mcp/session-event-view.d.ts +94 -0
- package/dist/mcp/session-view.d.ts +66 -2
- package/dist/mcp/session-wait.d.ts +174 -0
- package/dist/mcp-oauth.d.ts +52 -0
- package/dist/model-catalog.d.ts +11 -31
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +52 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +25 -0
- package/dist/routes/api-keys.d.ts +17 -0
- package/dist/routes/artifact-catalog.d.ts +6 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/billing.d.ts +49 -0
- package/dist/routes/browser-sessions.d.ts +71 -3
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +146 -0
- package/dist/routes/codex.d.ts +76 -2
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/computer-sessions.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/editable-artifacts.d.ts +2 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/feedback.d.ts +3 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/github-browser-pages.d.ts +12 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +30 -1
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/managed-auth-session-sets.d.ts +26 -0
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-connection-access.d.ts +3 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/organization-sessions.d.ts +26 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +9 -0
- package/dist/routes/sessions.d.ts +44 -6
- package/dist/routes/skill-content.d.ts +4 -0
- package/dist/routes/skill-install-authority.d.ts +6 -0
- package/dist/routes/subscription-account-pools.d.ts +34 -0
- package/dist/routes/subscription-pool-access.d.ts +16 -0
- package/dist/routes/supergrok.d.ts +6 -1
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +29 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +19 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +9 -1
- package/dist/sandbox/machines.d.ts +5 -2
- package/dist/sandbox/metrics-ingestion.d.ts +28 -20
- package/dist/sandbox/rematerialize.d.ts +2 -0
- package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
- package/dist/sandbox/viewer.d.ts +26 -1
- package/dist/sandbox-file-artifacts.d.ts +50 -0
- package/dist/scheduled-task-deletion.d.ts +13 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-MPS2OZD3.js +292 -0
- package/dist/session-event-view-MPS2OZD3.js.map +1 -0
- package/dist/site-session-origin.d.ts +13 -0
- package/dist/site-uploads.d.ts +41 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +1 -0
- package/dist/transcription/service.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +123 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/package.json +23 -19
- package/src/access-grant-rls.ts +75 -0
- package/src/admission-trace.ts +14 -0
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +1974 -256
- package/src/artifact-catalog-cursor.ts +60 -0
- package/src/auth/managed-auth-attempt-context.ts +70 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +222 -0
- package/src/auth/managed-auth.ts +536 -60
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +307 -0
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-subscription-account-conflicts.ts +12 -0
- package/src/claude-subscription-account-usage.ts +48 -0
- package/src/claude-subscription-oauth.ts +362 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +97 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +194 -30
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-ownership.ts +199 -0
- package/src/controller-data-plane.ts +81 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +77 -9
- package/src/editable-artifact-production.ts +23 -7
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +27 -21
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/fatal-process-boundary.ts +246 -0
- package/src/gateway-realtime.ts +1 -1
- package/src/github-access.ts +147 -5
- package/src/http/acting-person.ts +71 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +115 -1
- package/src/http/auth.ts +48 -0
- package/src/http/cors.ts +67 -0
- package/src/http/deprecation.ts +118 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +178 -0
- package/src/http/sse.ts +486 -132
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +157 -52
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +98 -11
- package/src/integrations/atlassian.ts +474 -136
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +354 -64
- package/src/integrations/github-app-connect.ts +435 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +76 -0
- package/src/integrations/github-skill-source.ts +2 -142
- package/src/integrations/google-drive.ts +820 -222
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +48 -0
- package/src/integrations/oauth-client.ts +1356 -459
- package/src/integrations/oauth-profiles.ts +537 -0
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github-repositories.ts +674 -0
- package/src/integrations/personal-github.ts +825 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +250 -22
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +963 -239
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/slack-interactions.ts +3170 -474
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/social-oauth.ts +213 -15
- package/src/interaction-agent-access.ts +48 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/action-catalog-types.ts +9 -0
- package/src/mcp/action-catalog.gen.ts +6696 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/command-read.ts +109 -0
- package/src/mcp/company-profile-agent-admin.ts +198 -0
- package/src/mcp/contract-input.ts +141 -0
- package/src/mcp/documents.ts +65 -377
- package/src/mcp/editable-artifacts.ts +12 -6
- package/src/mcp/files.ts +80 -38
- package/src/mcp/first-party-tool-permissions.ts +305 -0
- package/src/mcp/knowledge-entries.ts +322 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-input.ts +76 -0
- package/src/mcp/scheduled-task-view.ts +44 -3
- package/src/mcp/server.ts +3030 -1267
- package/src/mcp/session-event-view.ts +383 -0
- package/src/mcp/session-view.ts +84 -3
- package/src/mcp/session-wait.ts +665 -0
- package/src/mcp-oauth.ts +1123 -0
- package/src/model-catalog.ts +91 -333
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +397 -0
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +364 -74
- package/src/routes/api-keys.ts +336 -10
- package/src/routes/artifact-catalog.ts +279 -0
- package/src/routes/automations.ts +768 -0
- package/src/routes/billing.ts +461 -46
- package/src/routes/browser-sessions.ts +839 -131
- package/src/routes/canonical-human-identities.ts +90 -14
- package/src/routes/capabilities.ts +107 -6
- package/src/routes/channels.ts +20 -3
- package/src/routes/claude-subscription-accounts.ts +334 -0
- package/src/routes/claude-subscription-oauth.ts +142 -0
- package/src/routes/client-errors.ts +362 -0
- package/src/routes/codex.ts +833 -116
- package/src/routes/company-brain.ts +322 -0
- package/src/routes/company-profile.ts +65 -1
- package/src/routes/computer-sessions.ts +557 -106
- package/src/routes/connect.ts +1938 -0
- package/src/routes/connections.ts +1040 -349
- package/src/routes/documents.ts +488 -287
- package/src/routes/editable-artifacts.ts +79 -9
- package/src/routes/enrollments.ts +135 -39
- package/src/routes/environments.ts +154 -58
- package/src/routes/external-identity-links.ts +201 -0
- package/src/routes/feedback.ts +113 -0
- package/src/routes/files.ts +337 -39
- package/src/routes/github-browser-pages.ts +162 -0
- package/src/routes/github.ts +446 -144
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +153 -19
- package/src/routes/install.ts +133 -38
- package/src/routes/integration-facets.ts +107 -33
- package/src/routes/interaction-resources.ts +30 -3
- package/src/routes/knowledge.ts +488 -0
- package/src/routes/machines.ts +313 -15
- package/src/routes/managed-auth-session-sets.ts +1218 -0
- package/src/routes/managed-onboarding.ts +337 -0
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-connection-access.ts +208 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +178 -0
- package/src/routes/organization-memberships.ts +1019 -27
- package/src/routes/organization-model-providers.ts +293 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +350 -0
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +370 -0
- package/src/routes/plugins.ts +251 -22
- package/src/routes/pr-review-github.ts +881 -0
- package/src/routes/pr-review.ts +622 -0
- package/src/routes/preference-registry.ts +12 -93
- package/src/routes/rigs.ts +167 -50
- package/src/routes/scheduled-tasks.ts +346 -41
- package/src/routes/session-artifact-associations.ts +87 -0
- package/src/routes/session-history-imports.ts +280 -0
- package/src/routes/sessions.ts +2306 -264
- package/src/routes/skill-content.ts +297 -0
- package/src/routes/skill-install-authority.ts +64 -0
- package/src/routes/skills.ts +62 -9
- package/src/routes/social.ts +46 -18
- package/src/routes/subscription-account-pools.ts +436 -0
- package/src/routes/subscription-pool-access.ts +126 -0
- package/src/routes/supergrok.ts +219 -303
- package/src/routes/transcription-recordings.ts +38 -3
- package/src/routes/transcriptions.ts +4 -0
- package/src/routes/usage-allowances.ts +378 -0
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/video-generation.ts +14 -10
- package/src/routes/workspace-artifacts.ts +275 -69
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-learning.ts +159 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +38 -25
- package/src/routes/workspaces.ts +1004 -127
- package/src/sandbox/auth-callout.ts +96 -19
- package/src/sandbox/channel-a.ts +323 -24
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +81 -6
- package/src/sandbox/machines.ts +218 -76
- package/src/sandbox/metrics-ingestion.ts +411 -62
- package/src/sandbox/rematerialize.ts +121 -19
- package/src/sandbox/session-attach-variable-sets.ts +131 -0
- package/src/sandbox/viewer.ts +280 -25
- package/src/sandbox-file-artifacts.ts +413 -0
- package/src/scheduled-task-deletion.ts +138 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +6 -3
- package/src/site-session-origin.ts +48 -0
- package/src/site-uploads.ts +224 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/providers/codex-subscription.ts +2 -0
- package/src/transcription/providers/openai.ts +1 -0
- package/src/transcription/providers/xai-subscription.ts +49 -3
- package/src/transcription/segmenter.ts +8 -2
- package/src/transcription/service.ts +43 -6
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-artifact-content.ts +144 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/src/workspace-deletion.ts +73 -0
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +870 -0
- package/src/workspace-webhook-dispatch.ts +190 -0
- package/src/xai-subscription-auth.ts +9 -2
- package/dist/chunk-2PQMSRCB.js +0 -49759
- package/dist/chunk-2PQMSRCB.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
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
|
|
@@ -1578,20 +2230,34 @@ import {
|
|
|
1578
2230
|
editableArtifactStateHash as editableArtifactStateHash2,
|
|
1579
2231
|
EditableArtifactOfficeImportError
|
|
1580
2232
|
} from "@opengeni/core/editable-artifacts";
|
|
1581
|
-
import {
|
|
2233
|
+
import { requireLiveAgentAttemptAuthorization } from "@opengeni/core";
|
|
2234
|
+
import { getFilesForSubject } from "@opengeni/db";
|
|
1582
2235
|
import {
|
|
1583
|
-
MAX_BOUNDED_OBJECT_CHUNK_BYTES
|
|
2236
|
+
MAX_BOUNDED_OBJECT_CHUNK_BYTES,
|
|
2237
|
+
retryWhileMissing
|
|
1584
2238
|
} from "@opengeni/storage";
|
|
1585
2239
|
var SNAPSHOT_MIME = "application/vnd.opengeni.editable-artifact-snapshot";
|
|
1586
2240
|
var EditableArtifactOfficeImportAdapter = class {
|
|
1587
2241
|
constructor(dependencies) {
|
|
1588
2242
|
this.dependencies = dependencies;
|
|
1589
|
-
this.
|
|
2243
|
+
this.readFiles = dependencies.readFiles ?? getFilesForSubject;
|
|
2244
|
+
this.resolveFileAuthoritySubjectId = dependencies.resolveFileAuthoritySubjectId ?? editableArtifactFileAuthoritySubjectId;
|
|
1590
2245
|
}
|
|
1591
|
-
|
|
2246
|
+
readFiles;
|
|
2247
|
+
resolveFileAuthoritySubjectId;
|
|
1592
2248
|
async prepare(input) {
|
|
1593
2249
|
throwIfAborted2(input.signal);
|
|
1594
|
-
const
|
|
2250
|
+
const subjectId = await this.resolveFileAuthoritySubjectId(
|
|
2251
|
+
this.dependencies.db,
|
|
2252
|
+
input.scope,
|
|
2253
|
+
input.actor
|
|
2254
|
+
);
|
|
2255
|
+
const [file] = await this.readFiles(this.dependencies.db, {
|
|
2256
|
+
accountId: input.scope.accountId,
|
|
2257
|
+
workspaceId: input.scope.workspaceId,
|
|
2258
|
+
subjectId,
|
|
2259
|
+
fileIds: [input.fileId]
|
|
2260
|
+
});
|
|
1595
2261
|
if (!file || file.status !== "ready") {
|
|
1596
2262
|
throw new EditableArtifactOfficeImportError("invalid_source");
|
|
1597
2263
|
}
|
|
@@ -1699,29 +2365,74 @@ function officeFilenameMatches(filename, modality) {
|
|
|
1699
2365
|
}
|
|
1700
2366
|
async function readVerifiedWorkspaceFile(storage, file, signal) {
|
|
1701
2367
|
throwIfAborted2(signal);
|
|
1702
|
-
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
|
+
}
|
|
1703
2381
|
if (before.ContentLength !== file.sizeBytes || before.ContentType !== file.contentType || before.Metadata?.sha256 !== file.sha256 || typeof before.VersionToken !== "string" || before.VersionToken.length < 1) {
|
|
1704
2382
|
throw new EditableArtifactOfficeImportError("source_changed");
|
|
1705
2383
|
}
|
|
1706
2384
|
const bytes = new Uint8Array(file.sizeBytes);
|
|
1707
|
-
const digest =
|
|
2385
|
+
const digest = createHash3("sha256");
|
|
1708
2386
|
for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
|
|
1709
2387
|
throwIfAborted2(signal);
|
|
1710
2388
|
const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
|
|
1711
|
-
const chunk = await storage.getFileRange(file, { start, end });
|
|
2389
|
+
const chunk = await retryWhileMissing(async () => storage.getFileRange(file, { start, end }));
|
|
1712
2390
|
if (!chunk || chunk.byteLength !== end - start + 1) {
|
|
1713
2391
|
throw new EditableArtifactOfficeImportError("source_changed");
|
|
1714
2392
|
}
|
|
1715
2393
|
bytes.set(chunk, start);
|
|
1716
2394
|
digest.update(chunk);
|
|
1717
2395
|
}
|
|
1718
|
-
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
|
+
}
|
|
1719
2409
|
const sha256 = digest.digest("hex");
|
|
1720
2410
|
if (after.ContentLength !== before.ContentLength || after.ContentType !== before.ContentType || after.Metadata?.sha256 !== before.Metadata?.sha256 || after.VersionToken !== before.VersionToken || sha256 !== file.sha256) {
|
|
1721
2411
|
throw new EditableArtifactOfficeImportError("source_changed");
|
|
1722
2412
|
}
|
|
1723
2413
|
return bytes;
|
|
1724
2414
|
}
|
|
2415
|
+
async function editableArtifactFileAuthoritySubjectId(db, scope, actor) {
|
|
2416
|
+
if (actor.kind !== "agent") return actor.subjectId;
|
|
2417
|
+
const authorization = await requireLiveAgentAttemptAuthorization(
|
|
2418
|
+
db,
|
|
2419
|
+
{
|
|
2420
|
+
accountId: scope.accountId,
|
|
2421
|
+
workspaceId: scope.workspaceId,
|
|
2422
|
+
subjectId: actor.subjectId,
|
|
2423
|
+
permissions: [],
|
|
2424
|
+
principalKind: "agent_attempt",
|
|
2425
|
+
metadata: {
|
|
2426
|
+
sessionId: actor.sessionId,
|
|
2427
|
+
turnId: actor.turnId,
|
|
2428
|
+
attemptId: actor.attemptId,
|
|
2429
|
+
executionGeneration: actor.generation
|
|
2430
|
+
}
|
|
2431
|
+
},
|
|
2432
|
+
actor.sessionId
|
|
2433
|
+
);
|
|
2434
|
+
return authorization.initiatingHumanSubjectId;
|
|
2435
|
+
}
|
|
1725
2436
|
async function* byteChunks(bytes) {
|
|
1726
2437
|
for (let offset = 0; offset < bytes.byteLength; offset += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
|
|
1727
2438
|
yield bytes.subarray(offset, offset + MAX_BOUNDED_OBJECT_CHUNK_BYTES);
|
|
@@ -1732,8 +2443,9 @@ function throwIfAborted2(signal) {
|
|
|
1732
2443
|
}
|
|
1733
2444
|
|
|
1734
2445
|
// src/editable-artifact-workspace-files.ts
|
|
1735
|
-
import { createHash as
|
|
2446
|
+
import { createHash as createHash4 } from "crypto";
|
|
1736
2447
|
import { completeFileUpload, prepareGeneratedWorkspaceFile } from "@opengeni/db";
|
|
2448
|
+
import { retryWhileMissing as retryWhileMissing2 } from "@opengeni/storage";
|
|
1737
2449
|
var UPLOAD_INTENT_TTL_MS = 60 * 6e4;
|
|
1738
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;
|
|
1739
2451
|
var EditableArtifactWorkspaceFileAdapter = class {
|
|
@@ -1790,7 +2502,7 @@ var EditableArtifactWorkspaceFileAdapter = class {
|
|
|
1790
2502
|
if (existing) {
|
|
1791
2503
|
assertStoredExport(existing, download.byteSize, download.mimeType, sha256);
|
|
1792
2504
|
} else {
|
|
1793
|
-
await storage.putObjectStreamIfAbsent({
|
|
2505
|
+
const created = await storage.putObjectStreamIfAbsent({
|
|
1794
2506
|
key: objectKey,
|
|
1795
2507
|
contentType: download.mimeType,
|
|
1796
2508
|
chunks: download.chunks(input.signal ? { signal: input.signal } : {}),
|
|
@@ -1798,18 +2510,28 @@ var EditableArtifactWorkspaceFileAdapter = class {
|
|
|
1798
2510
|
sha256,
|
|
1799
2511
|
...input.signal ? { signal: input.signal } : {}
|
|
1800
2512
|
});
|
|
1801
|
-
|
|
1802
|
-
|
|
2513
|
+
if (!created) {
|
|
2514
|
+
await assertVisibleExport(
|
|
2515
|
+
storage,
|
|
2516
|
+
objectKey,
|
|
2517
|
+
download.byteSize,
|
|
2518
|
+
download.mimeType,
|
|
2519
|
+
sha256,
|
|
2520
|
+
input.signal
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
1803
2523
|
}
|
|
1804
2524
|
await download.assertUnchanged(input.signal);
|
|
1805
2525
|
throwIfAborted3(input.signal);
|
|
1806
2526
|
file = await this.completeFile(this.dependencies.db, input.scope.workspaceId, uploadId);
|
|
1807
2527
|
} else {
|
|
1808
|
-
|
|
1809
|
-
|
|
2528
|
+
await assertVisibleExport(
|
|
2529
|
+
storage,
|
|
2530
|
+
objectKey,
|
|
1810
2531
|
download.byteSize,
|
|
1811
2532
|
download.mimeType,
|
|
1812
|
-
sha256
|
|
2533
|
+
sha256,
|
|
2534
|
+
input.signal
|
|
1813
2535
|
);
|
|
1814
2536
|
await download.assertUnchanged(input.signal);
|
|
1815
2537
|
}
|
|
@@ -1833,6 +2555,13 @@ var EditableArtifactWorkspaceFileAdapter = class {
|
|
|
1833
2555
|
}
|
|
1834
2556
|
}
|
|
1835
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
|
+
}
|
|
1836
2565
|
function assertStoredExport(head, byteSize, contentType, sha256) {
|
|
1837
2566
|
if (!head || head.ContentLength !== byteSize || head.ContentType !== contentType || head.Metadata?.sha256 !== sha256 || typeof head.VersionToken !== "string" || head.VersionToken.length < 1) {
|
|
1838
2567
|
throw new Error("Workspace export object differs from its durable materialization");
|
|
@@ -1850,7 +2579,7 @@ function plainSha256(value) {
|
|
|
1850
2579
|
return match[1];
|
|
1851
2580
|
}
|
|
1852
2581
|
function deterministicUuid(seed) {
|
|
1853
|
-
const bytes =
|
|
2582
|
+
const bytes = createHash4("sha256").update(seed, "utf8").digest().subarray(0, 16);
|
|
1854
2583
|
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
1855
2584
|
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
1856
2585
|
const hex = bytes.toString("hex");
|
|
@@ -1939,6 +2668,11 @@ async function createStandaloneEditableArtifactApplication(input) {
|
|
|
1939
2668
|
touch: async ({ scope, sessionId, artifactId }) => await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId)
|
|
1940
2669
|
}),
|
|
1941
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
|
+
},
|
|
1942
2676
|
officeImports,
|
|
1943
2677
|
workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
|
|
1944
2678
|
db: input.db,
|
|
@@ -1964,7 +2698,8 @@ async function createStandaloneEditableArtifactApplication(input) {
|
|
|
1964
2698
|
});
|
|
1965
2699
|
const compatibility = Object.freeze({
|
|
1966
2700
|
assertCompatible(request) {
|
|
1967
|
-
|
|
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) {
|
|
1968
2703
|
throw new EditableArtifactCompatibilityError();
|
|
1969
2704
|
}
|
|
1970
2705
|
}
|
|
@@ -1996,7 +2731,7 @@ var PostgresEditableArtifactAuthorization = class {
|
|
|
1996
2731
|
}
|
|
1997
2732
|
async authorize(request) {
|
|
1998
2733
|
const actor = request.actor;
|
|
1999
|
-
const rows = await
|
|
2734
|
+
const rows = await withRlsContext2(
|
|
2000
2735
|
this.db,
|
|
2001
2736
|
request.scope,
|
|
2002
2737
|
async (tx) => rawRows(
|
|
@@ -2074,22 +2809,24 @@ var PollingEditableArtifactAuthorizationInvalidations = class {
|
|
|
2074
2809
|
};
|
|
2075
2810
|
var RandomEditableArtifactStableIds = class {
|
|
2076
2811
|
next(_kind) {
|
|
2077
|
-
return
|
|
2812
|
+
return randomUUID2().replaceAll("-", "");
|
|
2078
2813
|
}
|
|
2079
2814
|
};
|
|
2080
2815
|
var RandomEditableArtifactDurableExportIds = class {
|
|
2081
2816
|
next(_kind) {
|
|
2082
|
-
return
|
|
2817
|
+
return randomUUID2().replaceAll("-", "");
|
|
2083
2818
|
}
|
|
2084
2819
|
};
|
|
2085
2820
|
function materializationProfiles(capabilities) {
|
|
2086
|
-
|
|
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) {
|
|
2087
2823
|
throw new Error("Verified artifact materializer profile is incompatible");
|
|
2088
2824
|
}
|
|
2089
2825
|
const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
|
|
2090
2826
|
return Object.freeze({
|
|
2827
|
+
supportedFormats: EDITABLE_ARTIFACT_EXPORT_FORMATS,
|
|
2091
2828
|
async resolve(input) {
|
|
2092
|
-
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) {
|
|
2093
2830
|
return null;
|
|
2094
2831
|
}
|
|
2095
2832
|
return Object.freeze({
|
|
@@ -2118,7 +2855,7 @@ function snapshotBytesPort(objects) {
|
|
|
2118
2855
|
throw new Error("Editable artifact snapshot content type mismatch");
|
|
2119
2856
|
}
|
|
2120
2857
|
const bytes = new Uint8Array(snapshot.byteSize);
|
|
2121
|
-
const digest =
|
|
2858
|
+
const digest = createHash5("sha256");
|
|
2122
2859
|
let offset = 0;
|
|
2123
2860
|
for await (const chunk of object.chunks({
|
|
2124
2861
|
chunkBytes: MAX_BOUNDED_OBJECT_CHUNK_BYTES2
|
|
@@ -2148,6 +2885,169 @@ function rawRows(result) {
|
|
|
2148
2885
|
throw new Error("Unsupported database execute result shape");
|
|
2149
2886
|
}
|
|
2150
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
|
+
|
|
2151
3051
|
// src/index.ts
|
|
2152
3052
|
function isWorkflowAlreadyStarted(error) {
|
|
2153
3053
|
return error instanceof WorkflowExecutionAlreadyStartedError;
|
|
@@ -2172,7 +3072,40 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2172
3072
|
connection,
|
|
2173
3073
|
namespace: settings.temporalNamespace
|
|
2174
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
|
+
});
|
|
2175
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
|
+
},
|
|
2176
3109
|
signalUserMessage: async ({ eventId, workflowId }) => {
|
|
2177
3110
|
await temporal.workflow.getHandle(workflowId).signal("userMessage", eventId);
|
|
2178
3111
|
},
|
|
@@ -2182,7 +3115,8 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2182
3115
|
sessionId,
|
|
2183
3116
|
workflowId,
|
|
2184
3117
|
wakeRevision,
|
|
2185
|
-
interruptionRequested
|
|
3118
|
+
interruptionRequested,
|
|
3119
|
+
onSignalAccepted
|
|
2186
3120
|
}) => {
|
|
2187
3121
|
await temporal.workflow.signalWithStart("sessionWorkflow", {
|
|
2188
3122
|
taskQueue: settings.temporalTaskQueue,
|
|
@@ -2191,7 +3125,8 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2191
3125
|
args: [{ accountId, workspaceId, sessionId }],
|
|
2192
3126
|
signal: interruptionRequested ? "sessionControl" : "queueChanged"
|
|
2193
3127
|
});
|
|
2194
|
-
|
|
3128
|
+
onSignalAccepted?.();
|
|
3129
|
+
return await markSessionWorkflowWakeDelivered(db, {
|
|
2195
3130
|
accountId,
|
|
2196
3131
|
workspaceId,
|
|
2197
3132
|
sessionId,
|
|
@@ -2250,41 +3185,14 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2250
3185
|
wakeRevision: workflowWakeRevision
|
|
2251
3186
|
});
|
|
2252
3187
|
},
|
|
2253
|
-
syncScheduledTask: async ({ task }) =>
|
|
2254
|
-
|
|
2255
|
-
if (task.schedule.type === "manual") {
|
|
2256
|
-
try {
|
|
2257
|
-
await schedule.delete();
|
|
2258
|
-
} catch (error) {
|
|
2259
|
-
if (!(error instanceof ScheduleNotFoundError)) throw error;
|
|
2260
|
-
}
|
|
2261
|
-
return;
|
|
2262
|
-
}
|
|
2263
|
-
const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
|
|
2264
|
-
try {
|
|
2265
|
-
await schedule.update(() => temporalScheduleUpdateOptions(options));
|
|
2266
|
-
} catch (error) {
|
|
2267
|
-
if (!shouldCreateScheduleAfterUpdateError(error)) {
|
|
2268
|
-
throw error;
|
|
2269
|
-
}
|
|
2270
|
-
await temporal.schedule.create(options);
|
|
2271
|
-
}
|
|
2272
|
-
},
|
|
2273
|
-
deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
|
|
2274
|
-
try {
|
|
2275
|
-
await temporal.withDeadline(Date.now() + 5e3, async () => {
|
|
2276
|
-
await temporal.schedule.getHandle(temporalScheduleId).delete();
|
|
2277
|
-
});
|
|
2278
|
-
} catch (error) {
|
|
2279
|
-
if (error instanceof ScheduleNotFoundError) return;
|
|
2280
|
-
throw error;
|
|
2281
|
-
}
|
|
2282
|
-
},
|
|
3188
|
+
syncScheduledTask: async ({ task, onFailure }) => scheduledTasks.sync(task, onFailure),
|
|
3189
|
+
deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => scheduledTasks.remove(temporalScheduleId),
|
|
2283
3190
|
triggerScheduledTask: async ({
|
|
2284
3191
|
task,
|
|
2285
3192
|
agentRunUsageIdempotencyKey,
|
|
2286
3193
|
triggerWorkflowId,
|
|
2287
3194
|
initiator,
|
|
3195
|
+
credentialRestriction,
|
|
2288
3196
|
triggerType = "manual"
|
|
2289
3197
|
}) => {
|
|
2290
3198
|
const workflowId = triggerWorkflowId;
|
|
@@ -2300,7 +3208,8 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2300
3208
|
taskId: task.id,
|
|
2301
3209
|
triggerType,
|
|
2302
3210
|
agentRunUsageIdempotencyKey,
|
|
2303
|
-
initiator
|
|
3211
|
+
initiator,
|
|
3212
|
+
...credentialRestriction ? { credentialRestriction } : {}
|
|
2304
3213
|
}
|
|
2305
3214
|
]
|
|
2306
3215
|
});
|
|
@@ -2314,7 +3223,7 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2314
3223
|
startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {
|
|
2315
3224
|
const targetId = changeId ?? versionId;
|
|
2316
3225
|
if (!targetId) {
|
|
2317
|
-
throw new Error("
|
|
3226
|
+
throw new Error("sandbox environment verification requires changeId or versionId");
|
|
2318
3227
|
}
|
|
2319
3228
|
try {
|
|
2320
3229
|
await temporal.workflow.start("rigVerificationWorkflow", {
|
|
@@ -2357,11 +3266,12 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
2357
3266
|
}
|
|
2358
3267
|
};
|
|
2359
3268
|
}
|
|
2360
|
-
async function startApi() {
|
|
2361
|
-
const settings = getSettings();
|
|
2362
|
-
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" });
|
|
2363
3272
|
const searchPath = dbSearchPath(settings);
|
|
2364
3273
|
const dbClient = createDb(settings.databaseUrl, {
|
|
3274
|
+
max: settings.apiDatabasePoolMax,
|
|
2365
3275
|
...searchPath ? { searchPath } : {},
|
|
2366
3276
|
rlsStrategy: settings.rlsStrategy
|
|
2367
3277
|
});
|
|
@@ -2372,21 +3282,37 @@ async function startApi() {
|
|
|
2372
3282
|
const databasePosture = {
|
|
2373
3283
|
rlsStrategy: settings.rlsStrategy,
|
|
2374
3284
|
expectedRole: settings.runtimeDatabaseRole,
|
|
2375
|
-
targetSchema: settings.dbSchema.trim() || "public"
|
|
3285
|
+
targetSchema: settings.dbSchema.trim() || "public",
|
|
3286
|
+
organizationTenancyCanonicalActivationEnabled: settings.organizationTenancyCanonicalActivationEnabled
|
|
2376
3287
|
};
|
|
2377
3288
|
const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);
|
|
2378
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
|
+
);
|
|
2379
3296
|
await retryStartupDependency(
|
|
2380
3297
|
"PostgreSQL runtime posture",
|
|
2381
3298
|
() => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
|
|
3299
|
+
{ ...retryOptions, onRetry, shouldRetry: isRetryableRuntimeDatabaseStartupError }
|
|
3300
|
+
);
|
|
3301
|
+
const resolvedCatalog = await retryStartupDependency(
|
|
3302
|
+
"model catalog",
|
|
3303
|
+
() => resolveCatalogSettings(dbClient.db, settings),
|
|
2382
3304
|
{ ...retryOptions, onRetry }
|
|
2383
3305
|
);
|
|
3306
|
+
observability.info("OpenGeni model catalog resolved", {
|
|
3307
|
+
catalogSource: resolvedCatalog.source,
|
|
3308
|
+
catalogVersion: resolvedCatalog.version
|
|
3309
|
+
});
|
|
2384
3310
|
bus = await retryStartupDependency(
|
|
2385
3311
|
"NATS",
|
|
2386
3312
|
() => createNatsEventBus(
|
|
2387
3313
|
settings.natsUrl,
|
|
2388
3314
|
controlPlaneAuth ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password } : void 0,
|
|
2389
|
-
|
|
3315
|
+
observabilityEventBusOptions(observability)
|
|
2390
3316
|
),
|
|
2391
3317
|
{
|
|
2392
3318
|
...retryOptions,
|
|
@@ -2447,30 +3373,54 @@ async function startApi() {
|
|
|
2447
3373
|
behavior: "http_and_websocket_fail_closed"
|
|
2448
3374
|
});
|
|
2449
3375
|
}
|
|
2450
|
-
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];
|
|
2451
3387
|
const server = Bun.serve({
|
|
2452
3388
|
hostname: settings.apiHost,
|
|
2453
3389
|
port: settings.apiPort,
|
|
2454
3390
|
idleTimeout: 255,
|
|
2455
3391
|
fetch: (request, bunServer) => {
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
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
|
+
);
|
|
2460
3403
|
},
|
|
2461
3404
|
websocket: {
|
|
2462
3405
|
maxPayloadLength: EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES,
|
|
2463
3406
|
backpressureLimit: 16 * 1024 * 1024,
|
|
2464
3407
|
closeOnBackpressureLimit: true,
|
|
2465
|
-
open: (socket) =>
|
|
2466
|
-
message: (socket, message) =>
|
|
2467
|
-
close: (socket) =>
|
|
3408
|
+
open: (socket) => socket.data.attach(socket),
|
|
3409
|
+
message: (socket, message) => socket.data.receive(message),
|
|
3410
|
+
close: (socket) => socket.data.transportClosed()
|
|
2468
3411
|
}
|
|
2469
3412
|
});
|
|
3413
|
+
const metricsServer = startApiMetricsListener(settings, observability);
|
|
2470
3414
|
const stopSlackInteractionPump = settings.slackSigningSecret ? startSlackInteractionPump(routeDeps) : void 0;
|
|
2471
3415
|
const stopMemorySlackPublicationPump = startMemorySlackPublicationPump(routeDeps);
|
|
3416
|
+
const stopWorkspaceWebhookDispatchPump = startWorkspaceWebhookDispatchPump({
|
|
3417
|
+
db: dbClient.db,
|
|
3418
|
+
settings,
|
|
3419
|
+
observability
|
|
3420
|
+
});
|
|
2472
3421
|
const stopTemporalScheduleCleanupPump = startTemporalScheduleCleanupPump({
|
|
2473
3422
|
db: dbClient.db,
|
|
3423
|
+
cleanupConnectorAuthorization: async (claim) => await cleanupScheduledTaskConnectorAuthorization(routeDeps, claim),
|
|
2474
3424
|
deleteSchedule: async (temporalScheduleId) => {
|
|
2475
3425
|
await workflowClient.client.deleteScheduledTaskSchedule({ temporalScheduleId });
|
|
2476
3426
|
},
|
|
@@ -2514,17 +3464,21 @@ async function startApi() {
|
|
|
2514
3464
|
}
|
|
2515
3465
|
observability.info("OpenGeni API listening", {
|
|
2516
3466
|
host: settings.apiHost,
|
|
2517
|
-
port: settings.apiPort
|
|
3467
|
+
port: settings.apiPort,
|
|
3468
|
+
...metricsServer ? { metricsPort: settings.apiMetricsPort } : {}
|
|
2518
3469
|
});
|
|
2519
3470
|
return {
|
|
2520
3471
|
server,
|
|
2521
3472
|
close: async () => {
|
|
2522
3473
|
server.stop(true);
|
|
3474
|
+
metricsServer?.stop(true);
|
|
2523
3475
|
stopSlackInteractionPump?.();
|
|
2524
3476
|
await stopMemorySlackPublicationPump();
|
|
2525
3477
|
stopMetricsIngestion?.();
|
|
2526
3478
|
stopHelloIngestion?.();
|
|
2527
3479
|
await stopTemporalScheduleCleanupPump();
|
|
3480
|
+
await stopWorkspaceWebhookDispatchPump();
|
|
3481
|
+
await routeDeps.userPresence?.close().catch(() => void 0);
|
|
2528
3482
|
await Promise.allSettled([
|
|
2529
3483
|
Promise.resolve(editableArtifactComposition?.close()),
|
|
2530
3484
|
authCalloutResponder?.close(),
|
|
@@ -2536,7 +3490,16 @@ async function startApi() {
|
|
|
2536
3490
|
};
|
|
2537
3491
|
}
|
|
2538
3492
|
if (import.meta.main) {
|
|
2539
|
-
|
|
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
|
+
}
|
|
2540
3503
|
}
|
|
2541
3504
|
function temporalOverlapPolicy(policy) {
|
|
2542
3505
|
if (policy === "skip") {
|
|
@@ -2641,7 +3604,7 @@ function temporalScheduleOptions(task, taskQueue) {
|
|
|
2641
3604
|
}
|
|
2642
3605
|
function scheduledTaskEffectivelyPaused(task) {
|
|
2643
3606
|
if (task.status === "paused") return true;
|
|
2644
|
-
if (task
|
|
3607
|
+
if (!scheduledTaskKnowledgeSource(task)) return false;
|
|
2645
3608
|
const control = task.metadata.knowledgeSourceSync;
|
|
2646
3609
|
if (!control || typeof control !== "object") return false;
|
|
2647
3610
|
const record = control;
|