@k2b/cloud 0.6.0-rc.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/README.md +98 -0
- package/package.json +128 -0
- package/public/logo.svg +1 -0
- package/scripts/app-favicon.test.ts +49 -0
- package/scripts/app-favicon.ts +51 -0
- package/scripts/build.ts +205 -0
- package/scripts/preload.ts +164 -0
- package/scripts/sync-recovery-smoke.ts +200 -0
- package/src/_internal/app-favicon.ts +2 -0
- package/src/_internal/app-presentation.ts +61 -0
- package/src/_internal/app-workspace-state.ts +24 -0
- package/src/_internal/bounded-json.ts +47 -0
- package/src/_internal/build-metadata.ts +9 -0
- package/src/_internal/capabilities.ts +1118 -0
- package/src/_internal/define-app.ts +801 -0
- package/src/_internal/heartbeat.ts +167 -0
- package/src/_internal/help-catalog.ts +241 -0
- package/src/_internal/help.ts +95 -0
- package/src/_internal/nats-connection.ts +34 -0
- package/src/_internal/page-responses.ts +40 -0
- package/src/_internal/process-sync.ts +117 -0
- package/src/_internal/readiness.ts +3 -0
- package/src/_internal/registry-validation.ts +136 -0
- package/src/_internal/registry-watch.ts +52 -0
- package/src/_internal/registry.ts +338 -0
- package/src/_internal/runtime-compatibility.ts +64 -0
- package/src/_internal/runtime-context.ts +84 -0
- package/src/_internal/runtime-watcher.ts +78 -0
- package/src/_internal/static-assets.ts +86 -0
- package/src/_internal/status-preserving-ssr.ts +49 -0
- package/src/access/PermissionEditor.tsx +486 -0
- package/src/access/ResourceApiKeys.tsx +271 -0
- package/src/access/messages.ts +121 -0
- package/src/access/ui.ts +16 -0
- package/src/account/Avatar.tsx +28 -0
- package/src/account/EntitySearch.tsx +363 -0
- package/src/account/avatar-upload-dialog.tsx +197 -0
- package/src/account/avatar-upload.ts +149 -0
- package/src/account/ui.ts +3 -0
- package/src/ai/admin.ts +47 -0
- package/src/ai/approval-routes.ts +130 -0
- package/src/ai/approvals.ts +139 -0
- package/src/ai/assistant-models.ts +61 -0
- package/src/ai/attachments.ts +40 -0
- package/src/ai/browser.ts +92 -0
- package/src/ai/capabilities.ts +714 -0
- package/src/ai/capability-execution.ts +151 -0
- package/src/ai/chat/blocks.render.test.tsx +1108 -0
- package/src/ai/chat/blocks.tsx +730 -0
- package/src/ai/chat/builtin-tools.tsx +428 -0
- package/src/ai/chat/capability-table.tsx +87 -0
- package/src/ai/chat/composer-adapter.ts +169 -0
- package/src/ai/chat/file-tools.tsx +81 -0
- package/src/ai/chat/message-actions.tsx +370 -0
- package/src/ai/chat/message-utils.ts +479 -0
- package/src/ai/chat/messages.ts +43 -0
- package/src/ai/chat/presentation.tsx +395 -0
- package/src/ai/chat/primitives.tsx +3 -0
- package/src/ai/chat/tool-disclosure.tsx +41 -0
- package/src/ai/chat/turn-navigator-utils.ts +28 -0
- package/src/ai/chat/turn-navigator.tsx +164 -0
- package/src/ai/chat/user-message.tsx +149 -0
- package/src/ai/chat/visual-tools.tsx +435 -0
- package/src/ai/chat/web-tools.tsx +202 -0
- package/src/ai/chat-task-contracts.ts +113 -0
- package/src/ai/chat-tasks.ts +952 -0
- package/src/ai/client/controller.ts +1211 -0
- package/src/ai/client/file-source.ts +95 -0
- package/src/ai/client/live-connection.ts +140 -0
- package/src/ai/client/projection.ts +178 -0
- package/src/ai/client/transport.ts +111 -0
- package/src/ai/compaction.ts +182 -0
- package/src/ai/credentials.ts +124 -0
- package/src/ai/default-tool-contracts.ts +108 -0
- package/src/ai/default-tools.ts +127 -0
- package/src/ai/enrich.ts +280 -0
- package/src/ai/executor.ts +1624 -0
- package/src/ai/fetch-file-tool.ts +313 -0
- package/src/ai/file-context.ts +92 -0
- package/src/ai/file-media-type.ts +28 -0
- package/src/ai/file-mount.ts +16 -0
- package/src/ai/file-tools.ts +566 -0
- package/src/ai/files-store.ts +390 -0
- package/src/ai/firecrawl-tools.ts +305 -0
- package/src/ai/grids-skill.ts +126 -0
- package/src/ai/http.ts +265 -0
- package/src/ai/index.ts +337 -0
- package/src/ai/kit-skill.ts +22 -0
- package/src/ai/limits.ts +5 -0
- package/src/ai/live-events.ts +289 -0
- package/src/ai/live-messages.ts +45 -0
- package/src/ai/live-outbox.ts +102 -0
- package/src/ai/live-routes.ts +412 -0
- package/src/ai/live.ts +2 -0
- package/src/ai/maintenance.ts +302 -0
- package/src/ai/markdown-pdf-tool.ts +95 -0
- package/src/ai/memories.ts +605 -0
- package/src/ai/memory-learning-runs.ts +222 -0
- package/src/ai/memory-learning.ts +619 -0
- package/src/ai/memory-tool.ts +120 -0
- package/src/ai/memory-workflow-evidence.ts +158 -0
- package/src/ai/migrate.ts +2082 -0
- package/src/ai/model-access-migrate.ts +30 -0
- package/src/ai/model-access.ts +191 -0
- package/src/ai/personal-agent.ts +6 -0
- package/src/ai/prefs.ts +72 -0
- package/src/ai/project-tool.ts +94 -0
- package/src/ai/projects-routes.ts +277 -0
- package/src/ai/projects.ts +902 -0
- package/src/ai/protocol.ts +279 -0
- package/src/ai/provider.ts +73 -0
- package/src/ai/public-projection.ts +25 -0
- package/src/ai/resource-markers.ts +31 -0
- package/src/ai/resource-refs.ts +70 -0
- package/src/ai/routes.ts +1131 -0
- package/src/ai/runtime-tools.ts +39 -0
- package/src/ai/runtime.ts +544 -0
- package/src/ai/settings.ts +529 -0
- package/src/ai/short-id.ts +45 -0
- package/src/ai/skill-catalog.ts +73 -0
- package/src/ai/skill-format.ts +145 -0
- package/src/ai/skill-seeds.ts +430 -0
- package/src/ai/skill-tool.ts +81 -0
- package/src/ai/skills-routes.ts +143 -0
- package/src/ai/skills.ts +934 -0
- package/src/ai/solid.ts +27 -0
- package/src/ai/store.ts +3113 -0
- package/src/ai/stream.ts +308 -0
- package/src/ai/structured-runs.ts +66 -0
- package/src/ai/structured.ts +186 -0
- package/src/ai/system-prompt.ts +170 -0
- package/src/ai/task-prompt.ts +22 -0
- package/src/ai/timeline.ts +148 -0
- package/src/ai/tool-audit.ts +177 -0
- package/src/ai/tool-result-budget.ts +16 -0
- package/src/ai/tools.ts +155 -0
- package/src/ai/types.ts +967 -0
- package/src/ai/ui.tsx +39 -0
- package/src/ai/usage-migrate.ts +81 -0
- package/src/ai/usage.ts +315 -0
- package/src/ai/validate.ts +54 -0
- package/src/ai/vision-tool.ts +79 -0
- package/src/api/accounts-entities.ts +174 -0
- package/src/api/admin-ai-projects.ts +74 -0
- package/src/api/admin-ai-skills.ts +76 -0
- package/src/api/admin-ai-usage.ts +42 -0
- package/src/api/admin-core-settings.ts +508 -0
- package/src/api/admin-identity.ts +148 -0
- package/src/api/admin-lifecycle.ts +206 -0
- package/src/api/admin-linux-identities.ts +66 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/app-approval.ts +129 -0
- package/src/api/apps.ts +51 -0
- package/src/api/auth/schemas.ts +54 -0
- package/src/api/auth.ts +370 -0
- package/src/api/capabilities.ts +658 -0
- package/src/api/help-messages.ts +19 -0
- package/src/api/help.ts +78 -0
- package/src/api/index.ts +108 -0
- package/src/api/mcp.ts +744 -0
- package/src/api/me-rail.ts +45 -0
- package/src/api/me.ts +753 -0
- package/src/api/search/schemas.ts +79 -0
- package/src/api/search.bench.ts +185 -0
- package/src/api/search.ts +407 -0
- package/src/api/sync-ops.ts +79 -0
- package/src/api/widgets.ts +143 -0
- package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
- package/src/browser/CloudResourceSearch.tsx +791 -0
- package/src/browser/app-approval-vault.ts +305 -0
- package/src/browser/app-approval.ts +234 -0
- package/src/browser/live-websocket.ts +268 -0
- package/src/browser/locale-preference.ts +25 -0
- package/src/browser/notification-target.ts +27 -0
- package/src/browser/notification-timeout.ts +14 -0
- package/src/browser/notifications.ts +181 -0
- package/src/browser/resource-clipboard.ts +70 -0
- package/src/browser/resource-picker.tsx +39 -0
- package/src/browser/resource-search-messages.ts +55 -0
- package/src/browser/resource-search.ts +27 -0
- package/src/capabilities/client.ts +149 -0
- package/src/capabilities/response.ts +39 -0
- package/src/capabilities/server.ts +215 -0
- package/src/capabilities/signals.ts +4 -0
- package/src/capabilities/testing.ts +12 -0
- package/src/capabilities/types.ts +32 -0
- package/src/cli/access.ts +499 -0
- package/src/cli/account.ts +382 -0
- package/src/cli/admin/account-administration.ts +45 -0
- package/src/cli/admin/account-categories.ts +49 -0
- package/src/cli/admin/ai-usage.ts +118 -0
- package/src/cli/admin/app-sign-in.ts +49 -0
- package/src/cli/admin/data.ts +343 -0
- package/src/cli/admin/documentation.ts +34 -0
- package/src/cli/admin/gateway.ts +155 -0
- package/src/cli/admin/index.ts +61 -0
- package/src/cli/admin/instance.ts +250 -0
- package/src/cli/admin/jobs.ts +223 -0
- package/src/cli/admin/legal.ts +122 -0
- package/src/cli/admin/linux.ts +53 -0
- package/src/cli/admin/logs.ts +324 -0
- package/src/cli/admin/metrics.ts +142 -0
- package/src/cli/admin/nats.ts +167 -0
- package/src/cli/admin/notifications.ts +516 -0
- package/src/cli/admin/shared.ts +113 -0
- package/src/cli/admin/sync.ts +188 -0
- package/src/cli/admin/telemetry.ts +314 -0
- package/src/cli/admin/webhooks.ts +222 -0
- package/src/cli/admin/workflows.ts +367 -0
- package/src/cli/apps.ts +40 -0
- package/src/cli/capabilities.ts +239 -0
- package/src/cli/commands.ts +604 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/locale.ts +31 -0
- package/src/cli/output.ts +58 -0
- package/src/clients/core.ts +20 -0
- package/src/config/env.ts +54 -0
- package/src/config/index.ts +6 -0
- package/src/contracts/account-categories.ts +62 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app-approval.ts +197 -0
- package/src/contracts/app.ts +162 -0
- package/src/contracts/capabilities.ts +484 -0
- package/src/contracts/index.ts +14 -0
- package/src/contracts/notification-live.ts +66 -0
- package/src/contracts/notification-types.ts +235 -0
- package/src/contracts/posix.ts +69 -0
- package/src/contracts/profile.ts +85 -0
- package/src/contracts/rail-preferences.ts +53 -0
- package/src/contracts/registry.ts +110 -0
- package/src/contracts/resource-clipboard.ts +33 -0
- package/src/contracts/settings-types.ts +98 -0
- package/src/contracts/shared.ts +408 -0
- package/src/contracts/user-notifications.ts +101 -0
- package/src/contracts/widgets.ts +183 -0
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/index.ts +58 -0
- package/src/server/actor.ts +41 -0
- package/src/server/api/index.ts +2 -0
- package/src/server/api/respond.ts +96 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/help.ts +187 -0
- package/src/server/index.ts +89 -0
- package/src/server/locale.ts +55 -0
- package/src/server/middleware/auth.ts +386 -0
- package/src/server/middleware/index.ts +28 -0
- package/src/server/middleware/invocation.ts +55 -0
- package/src/server/middleware/middleware.ts +65 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +125 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/route-template.ts +46 -0
- package/src/server/middleware/runtime.ts +22 -0
- package/src/server/middleware/settings.ts +55 -0
- package/src/server/middleware/validator.ts +49 -0
- package/src/server/middleware/workload.ts +22 -0
- package/src/server/ratelimit.ts +151 -0
- package/src/server/request-cookies.ts +18 -0
- package/src/server/services/access.ts +644 -0
- package/src/server/services/freeipa/client.ts +137 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +123 -0
- package/src/server/services/freeipa/test-certificates.ts +44 -0
- package/src/server/services/freeipa/tls.ts +44 -0
- package/src/server/services/freeipa/transport.ts +116 -0
- package/src/server/services/freeipa/util.ts +59 -0
- package/src/server/services/geo.ts +198 -0
- package/src/server/services/index.ts +48 -0
- package/src/server/services/pagination.ts +22 -0
- package/src/server/services/services.ts +13 -0
- package/src/server/time.ts +33 -0
- package/src/services/account-category-policy.ts +51 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +940 -0
- package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
- package/src/services/account-lifecycle/notification-sender.ts +13 -0
- package/src/services/account-lifecycle/scheduler.ts +506 -0
- package/src/services/account-model.ts +22 -0
- package/src/services/accounts/app.ts +1671 -0
- package/src/services/accounts/authz.ts +44 -0
- package/src/services/accounts/avatar.ts +120 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +46 -0
- package/src/services/accounts/duplicate-emails.ts +82 -0
- package/src/services/accounts/entities.ts +622 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +272 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +66 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +499 -0
- package/src/services/accounts/model.ts +129 -0
- package/src/services/accounts/notification-sender.ts +31 -0
- package/src/services/accounts/posix.ts +386 -0
- package/src/services/accounts/request-policy.ts +11 -0
- package/src/services/accounts/switching.ts +126 -0
- package/src/services/accounts/users.ts +745 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/app-approval.ts +498 -0
- package/src/services/audit/index.ts +438 -0
- package/src/services/auth-flows/index.ts +15 -0
- package/src/services/auth-flows/ipa.ts +197 -0
- package/src/services/auth-flows/magic-link.ts +185 -0
- package/src/services/auth-flows/notification-sender.ts +10 -0
- package/src/services/auth-flows/password-reset.ts +268 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/document-extraction.ts +148 -0
- package/src/services/freeipa-config.ts +179 -0
- package/src/services/gateway.ts +186 -0
- package/src/services/identity/constants.ts +18 -0
- package/src/services/identity/index.ts +52 -0
- package/src/services/identity/invocation-actor.ts +120 -0
- package/src/services/identity/invocation-authority.ts +53 -0
- package/src/services/identity/invocation-operations.ts +15 -0
- package/src/services/identity/invocation-token.ts +276 -0
- package/src/services/identity/key-config.ts +41 -0
- package/src/services/identity/key-ring.ts +530 -0
- package/src/services/identity/metrics.ts +29 -0
- package/src/services/identity/routes.ts +18 -0
- package/src/services/identity/runtime-config.ts +88 -0
- package/src/services/identity/session-token.ts +152 -0
- package/src/services/identity/signer-refresh.ts +44 -0
- package/src/services/identity/workload-auth.ts +64 -0
- package/src/services/index.ts +187 -0
- package/src/services/ipa/auth.ts +216 -0
- package/src/services/ipa/connection.ts +57 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/groups.ts +722 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/posix.ts +19 -0
- package/src/services/ipa/profile.ts +132 -0
- package/src/services/ipa/runtime.ts +28 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.ts +100 -0
- package/src/services/ipa/sync.ts +862 -0
- package/src/services/ipa/users.ts +806 -0
- package/src/services/legal-consent.ts +76 -0
- package/src/services/logging/index.ts +477 -0
- package/src/services/logging/redaction.ts +20 -0
- package/src/services/logging/trace.ts +1231 -0
- package/src/services/mandates/index.ts +855 -0
- package/src/services/mandates/policy.ts +85 -0
- package/src/services/network-security.ts +31 -0
- package/src/services/notifications/batches.ts +722 -0
- package/src/services/notifications/browser.ts +252 -0
- package/src/services/notifications/catalog.ts +113 -0
- package/src/services/notifications/channels.ts +104 -0
- package/src/services/notifications/dispatcher.ts +139 -0
- package/src/services/notifications/email.ts +118 -0
- package/src/services/notifications/index.ts +633 -0
- package/src/services/notifications/live.ts +50 -0
- package/src/services/notifications/observability.ts +407 -0
- package/src/services/notifications/platform.ts +421 -0
- package/src/services/notifications/runtime.ts +120 -0
- package/src/services/notifications/user.ts +248 -0
- package/src/services/notifications/web-push-transport.ts +135 -0
- package/src/services/oauth-tokens.ts +209 -0
- package/src/services/outbox.ts +187 -0
- package/src/services/pdf/gotenberg.ts +290 -0
- package/src/services/pdf/index.ts +43 -0
- package/src/services/pdf/markdown.ts +209 -0
- package/src/services/pdf/template-preview.ts +122 -0
- package/src/services/postgres.ts +76 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +51 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +263 -0
- package/src/services/rail-preferences.ts +27 -0
- package/src/services/runtime-lifecycle.ts +191 -0
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.ts +891 -0
- package/src/services/service-accounts.ts +184 -0
- package/src/services/session/index.ts +236 -0
- package/src/services/session/test-fixture.ts +18 -0
- package/src/services/session/user.ts +144 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +86 -0
- package/src/services/settings/core-settings.ts +796 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +374 -0
- package/src/services/settings/index.ts +215 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +241 -0
- package/src/services/settings/templates.ts +4 -0
- package/src/services/sync-ops.ts +403 -0
- package/src/services/topic-cursor.ts +22 -0
- package/src/services/weather/forecast.ts +323 -0
- package/src/services/weather/geo.ts +119 -0
- package/src/services/weather/index.ts +67 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +133 -0
- package/src/services/weather/migrate.ts +45 -0
- package/src/services/weather/settings.ts +66 -0
- package/src/services/weather/short-id.ts +57 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/services/webauthn.ts +385 -0
- package/src/shared/account-action-notice.ts +70 -0
- package/src/shared/account-display.ts +15 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/ai-platform-prompt.ts +134 -0
- package/src/shared/ai-usage.ts +49 -0
- package/src/shared/app-presentation.ts +48 -0
- package/src/shared/app-url.ts +12 -0
- package/src/shared/branding.ts +14 -0
- package/src/shared/capability-messages.ts +70 -0
- package/src/shared/email-html.ts +54 -0
- package/src/shared/format.ts +91 -0
- package/src/shared/help.ts +68 -0
- package/src/shared/icons.ts +400 -0
- package/src/shared/index.ts +54 -0
- package/src/shared/locale.ts +48 -0
- package/src/shared/login-method.ts +44 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +105 -0
- package/src/shared/markdown/extensions/guided-help.ts +100 -0
- package/src/shared/markdown/extensions/images.ts +79 -0
- package/src/shared/markdown/extensions/info-blocks.ts +97 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +43 -0
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +109 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +193 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/network-address.ts +106 -0
- package/src/shared/redirect.ts +56 -0
- package/src/shared/template-rendering.ts +140 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +53 -0
- package/src/ssr/AdminSidebar.tsx +50 -0
- package/src/ssr/AppLaunchpad.island.tsx +236 -0
- package/src/ssr/AppWorkspaceController.island.tsx +21 -0
- package/src/ssr/BrowserNotifications.island.tsx +135 -0
- package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
- package/src/ssr/GlobalSearchDialog.tsx +35 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
- package/src/ssr/Layout.render.test.tsx +148 -0
- package/src/ssr/Layout.tsx +292 -0
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutFooter.tsx +22 -0
- package/src/ssr/LayoutHeader.tsx +81 -0
- package/src/ssr/LayoutHelp.tsx +869 -0
- package/src/ssr/LayoutPreferences.island.tsx +36 -0
- package/src/ssr/LayoutRail.tsx +58 -0
- package/src/ssr/MinimalLayout.tsx +46 -0
- package/src/ssr/PageError.tsx +29 -0
- package/src/ssr/ProfilePreferences.island.tsx +92 -0
- package/src/ssr/RailApps.island.tsx +61 -0
- package/src/ssr/RailEditor.tsx +293 -0
- package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/admin-active-link.ts +47 -0
- package/src/ssr/admin-navigation.ts +78 -0
- package/src/ssr/app-appearance.ts +23 -0
- package/src/ssr/app-navigation.ts +41 -0
- package/src/ssr/help-messages.ts +104 -0
- package/src/ssr/index.ts +14 -0
- package/src/ssr/islands/SearchBar.island.tsx +80 -0
- package/src/ssr/islands/index.ts +16 -0
- package/src/ssr/layout-context.ts +17 -0
- package/src/ssr/layout-help-markdown.ts +9 -0
- package/src/ssr/layout-help-navigation.ts +24 -0
- package/src/ssr/layout-help-url.ts +5 -0
- package/src/ssr/layout-runtime.ts +22 -0
- package/src/ssr/page-error-messages.ts +25 -0
- package/src/ssr/platform-messages.ts +135 -0
- package/src/ssr/preference-controller.ts +44 -0
- package/src/ssr/profile-preferences-messages.ts +29 -0
- package/src/ssr/rail-context.ts +21 -0
- package/src/ssr/rail-messages.ts +66 -0
- package/src/ssr/rail-navigation.ts +60 -0
- package/src/ssr/runtime.ts +31 -0
- package/src/ssr/url-filter.ts +127 -0
- package/src/styles/base-popover.css +36 -0
- package/src/styles/effects.css +851 -0
- package/src/styles/global.css +268 -0
- package/src/styles/input.css +58 -0
- package/src/styles/tokens.css +230 -0
- package/src/styles/utilities-buttons.css +405 -0
- package/src/styles/utilities-data.css +33 -0
- package/src/styles/utilities-feedback.css +232 -0
- package/src/styles/utilities-layout.css +603 -0
- package/src/styles/utilities-markdown-editor.css +525 -0
- package/src/styles/utilities-markdown-table.css +234 -0
- package/src/styles/utilities-navigation.css +1054 -0
- package/src/types/ambient.d.ts +24 -0
- package/src/workflows/ai/index.ts +17 -0
- package/src/workflows/ai/runtime.ts +286 -0
- package/src/workflows/ai/store.ts +314 -0
- package/src/workflows/ai/types.ts +111 -0
- package/src/workflows/ai-actions.ts +269 -0
- package/src/workflows/authoring.ts +88 -0
- package/src/workflows/builtins.ts +193 -0
- package/src/workflows/contracts.ts +280 -0
- package/src/workflows/definition.ts +334 -0
- package/src/workflows/editor.ts +113 -0
- package/src/workflows/index.ts +71 -0
- package/src/workflows/language/binder.ts +45 -0
- package/src/workflows/language/canonical.ts +29 -0
- package/src/workflows/language/compiler.ts +396 -0
- package/src/workflows/language/expressions.ts +34 -0
- package/src/workflows/language/index.ts +20 -0
- package/src/workflows/language/references.ts +55 -0
- package/src/workflows/language/schema.ts +134 -0
- package/src/workflows/language/strict-yaml.ts +131 -0
- package/src/workflows/manifest.ts +24 -0
- package/src/workflows/module.ts +12 -0
- package/src/workflows/runtime/coordinator.ts +115 -0
- package/src/workflows/runtime/executor.ts +1003 -0
- package/src/workflows/runtime/index.ts +60 -0
- package/src/workflows/runtime/instant.ts +8 -0
- package/src/workflows/runtime/ports.ts +213 -0
- package/src/workflows/runtime/schedule.ts +203 -0
- package/src/workflows/runtime/trigger.ts +37 -0
- package/src/workflows/runtime/wake.ts +13 -0
- package/src/workflows/store/actions.ts +438 -0
- package/src/workflows/store/budget.ts +145 -0
- package/src/workflows/store/definitions.ts +345 -0
- package/src/workflows/store/events.ts +383 -0
- package/src/workflows/store/index.ts +107 -0
- package/src/workflows/store/observability.ts +594 -0
- package/src/workflows/store/runs.ts +975 -0
- package/src/workflows/store/transaction.ts +23 -0
- package/src/workflows/store/worker.ts +439 -0
- package/src/workflows/testing/conformance.ts +72 -0
- package/src/workflows/testing/index.ts +11 -0
- package/src/workflows/testing/process-fixtures.ts +275 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composing kernel writes into an app's own transaction.
|
|
3
|
+
*
|
|
4
|
+
* Every store function takes an optional `db`, and the whole point of that
|
|
5
|
+
* option is that an app can run a kernel write inside a transaction it already
|
|
6
|
+
* owns — creating a workflow and its app-side profile together, say, so a crash
|
|
7
|
+
* cannot leave one without the other.
|
|
8
|
+
*
|
|
9
|
+
* Bun rejects `begin` inside a transaction; a nested unit is a savepoint. A
|
|
10
|
+
* handle only has `savepoint` when it *is* a transaction, which is what makes
|
|
11
|
+
* this reliable rather than a convention callers have to remember.
|
|
12
|
+
*/
|
|
13
|
+
import { type SQL, sql } from "bun";
|
|
14
|
+
|
|
15
|
+
type Savepointed = SQL & { savepoint: <T>(fn: (tx: SQL) => Promise<T>) => Promise<T> };
|
|
16
|
+
|
|
17
|
+
const isTransaction = (db: SQL): db is Savepointed => typeof (db as Partial<Savepointed>).savepoint === "function";
|
|
18
|
+
|
|
19
|
+
/** Runs `fn` atomically, nesting as a savepoint when the caller already has a transaction. */
|
|
20
|
+
export const withTransaction = <T>(db: SQL | undefined, fn: (tx: SQL) => Promise<T>): Promise<T> => {
|
|
21
|
+
const handle = db ?? sql;
|
|
22
|
+
return isTransaction(handle) ? handle.savepoint(fn) : handle.begin(fn);
|
|
23
|
+
};
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loop that turns queued runs into finished ones.
|
|
3
|
+
*
|
|
4
|
+
* Grids and Mail each wrote this: claim a run, load its plan, drive the
|
|
5
|
+
* executor, record the outcome, renew the lease while it works. The bodies
|
|
6
|
+
* differed enough that a fix in one never reached the other — Grids leased the
|
|
7
|
+
* run, Mail leased a target, and only one of them woke parked runs on a
|
|
8
|
+
* deadline.
|
|
9
|
+
*
|
|
10
|
+
* An app now brings its ID, current module, and runtime ports. Everything
|
|
11
|
+
* about *when* something runs, *which* plan it runs, and what happens if the
|
|
12
|
+
* process dies belongs here.
|
|
13
|
+
*/
|
|
14
|
+
import { type SQL, sql } from "bun";
|
|
15
|
+
import type { WorkflowActor, WorkflowInvocationMode, WorkflowJsonValue } from "../contracts";
|
|
16
|
+
import { hashWorkflowJson } from "../language/canonical";
|
|
17
|
+
import type { DefinedWorkflowModule } from "../module";
|
|
18
|
+
import { coordinateWorkflowExecution } from "../runtime/coordinator";
|
|
19
|
+
import { dryRunWorkflowPlan, executeWorkflowPlan } from "../runtime/executor";
|
|
20
|
+
import type {
|
|
21
|
+
WorkflowDryRunActionPort,
|
|
22
|
+
WorkflowDryRunResult,
|
|
23
|
+
WorkflowExecuteActionPort,
|
|
24
|
+
WorkflowRuntimeRunIdentity,
|
|
25
|
+
WorkflowTracePort,
|
|
26
|
+
WorkflowValueResolverPort,
|
|
27
|
+
} from "../runtime/ports";
|
|
28
|
+
import { dispatchPendingWorkflowEvents } from "./events";
|
|
29
|
+
import {
|
|
30
|
+
claimWorkflowRun,
|
|
31
|
+
createWorkflowCoordinatorPort,
|
|
32
|
+
createWorkflowRuntimeRepository,
|
|
33
|
+
finishExpiredWorkflowRunCancels,
|
|
34
|
+
finishWorkflowRunCancel,
|
|
35
|
+
WORKFLOW_RUN_LEASE_MS,
|
|
36
|
+
WORKFLOW_RUN_MAX_CONSECUTIVE_FAILURES,
|
|
37
|
+
type WorkflowRunClaim,
|
|
38
|
+
type WorkflowRunResult,
|
|
39
|
+
wakeExpiredWorkflowRuns,
|
|
40
|
+
} from "./runs";
|
|
41
|
+
|
|
42
|
+
/** Renew well inside the lease, so one slow query does not cost the claim. */
|
|
43
|
+
const HEARTBEAT_MS = Math.floor(WORKFLOW_RUN_LEASE_MS / 3);
|
|
44
|
+
|
|
45
|
+
export type WorkflowWorkerOptions = {
|
|
46
|
+
/** Identifies this process in `lease_owner`. Only ever read by humans. */
|
|
47
|
+
worker: string;
|
|
48
|
+
/** The app's action implementations. */
|
|
49
|
+
actions: WorkflowExecuteActionPort;
|
|
50
|
+
/** The app whose runs this worker may claim. */
|
|
51
|
+
appId: string;
|
|
52
|
+
/** The current language declaration. Older alpha manifests are not executed. */
|
|
53
|
+
module: DefinedWorkflowModule;
|
|
54
|
+
trace?: WorkflowTracePort;
|
|
55
|
+
/**
|
|
56
|
+
* Builds the value resolver for one claimed run.
|
|
57
|
+
*
|
|
58
|
+
* A function of the claim rather than a port, because a worker serves every
|
|
59
|
+
* run in the app while a resolver answers under one run's scope and actor.
|
|
60
|
+
* One shared instance would resolve a reference through whichever run
|
|
61
|
+
* happened to warm its caches.
|
|
62
|
+
*/
|
|
63
|
+
values?: (claim: WorkflowRunClaim) => WorkflowValueResolverPort;
|
|
64
|
+
maxLoopItems?: number;
|
|
65
|
+
db?: SQL;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type WorkflowWorkerOutcome =
|
|
69
|
+
| { state: "idle" }
|
|
70
|
+
| { state: "finished"; runId: string; result: WorkflowRunResult }
|
|
71
|
+
| { state: "released"; runId: string; error: unknown }
|
|
72
|
+
| { state: "lost"; runId: string };
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The actor a run executes as.
|
|
76
|
+
*
|
|
77
|
+
* Read from the authorization snapshot frozen when the run was accepted, so an
|
|
78
|
+
* edit to a group membership cannot silently widen what an already-queued run
|
|
79
|
+
* is allowed to do.
|
|
80
|
+
*
|
|
81
|
+
* An app's snapshot holds more than the actor — what it was accepted under,
|
|
82
|
+
* which credential — so the actor is a named key inside it rather than the
|
|
83
|
+
* snapshot itself. Nobody named means nobody: an unrecognised snapshot yields
|
|
84
|
+
* an empty actor, and every permission check an action makes then refuses.
|
|
85
|
+
* Inventing a system identity here is how a run acts with more authority than
|
|
86
|
+
* whoever asked for it had.
|
|
87
|
+
*/
|
|
88
|
+
const actorFromSnapshot = (snapshot: WorkflowJsonValue): WorkflowActor => {
|
|
89
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) return {};
|
|
90
|
+
const actor = snapshot.actor;
|
|
91
|
+
return actor && typeof actor === "object" && !Array.isArray(actor) ? (actor as unknown as WorkflowActor) : {};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Maps what the executor reported onto how the run settles.
|
|
96
|
+
*
|
|
97
|
+
* `waiting` is not an outcome — the step parked itself and the run with it, so
|
|
98
|
+
* this only tells the coordinator the run is where it belongs.
|
|
99
|
+
*/
|
|
100
|
+
const settle = (result: Awaited<ReturnType<typeof executeWorkflowPlan>>): WorkflowRunResult => {
|
|
101
|
+
switch (result.state) {
|
|
102
|
+
case "succeeded":
|
|
103
|
+
return { state: "succeeded", result: result.output ?? null, ...(result.message ? { message: result.message } : {}) };
|
|
104
|
+
case "waiting":
|
|
105
|
+
return { state: "waiting" };
|
|
106
|
+
case "failed":
|
|
107
|
+
return { state: "failed", error: result.error as unknown as WorkflowJsonValue };
|
|
108
|
+
case "needs_attention":
|
|
109
|
+
return { state: "needs_attention", error: result.error as unknown as WorkflowJsonValue };
|
|
110
|
+
case "canceled":
|
|
111
|
+
return { state: "canceled", ...(result.message ? { message: result.message } : {}) };
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** The identity the trace port names a run by, built from what the claim returned. */
|
|
116
|
+
const runIdentity = (claim: WorkflowRunClaim, mode: WorkflowInvocationMode): WorkflowRuntimeRunIdentity => ({
|
|
117
|
+
runId: claim.runId,
|
|
118
|
+
executionGeneration: claim.executionGeneration,
|
|
119
|
+
mode,
|
|
120
|
+
workflowId: claim.workflowId,
|
|
121
|
+
sourceHash: claim.sourceHash,
|
|
122
|
+
idempotencyKey: claim.idempotencyKey,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Announces a run's own transitions, around the step-level ones the executor emits.
|
|
127
|
+
*
|
|
128
|
+
* Best-effort on purpose: an observer that throws — a browser stream, a metric
|
|
129
|
+
* sink — must not be able to fail a run that has otherwise done its work.
|
|
130
|
+
*/
|
|
131
|
+
const traceRun = async (trace: WorkflowTracePort | undefined, event: Parameters<WorkflowTracePort["emit"]>[0]): Promise<void> => {
|
|
132
|
+
if (!trace) return;
|
|
133
|
+
try {
|
|
134
|
+
await trace.emit(event);
|
|
135
|
+
} catch {
|
|
136
|
+
// Observing is not part of the run's contract.
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/** Refuses a plan that was bound against any language other than the app's current module. */
|
|
141
|
+
const validateCurrentModule = async (
|
|
142
|
+
claim: WorkflowRunClaim,
|
|
143
|
+
module: DefinedWorkflowModule,
|
|
144
|
+
): Promise<Extract<WorkflowRunResult, { state: "needs_attention" }> | null> => {
|
|
145
|
+
const manifestHash = await hashWorkflowJson(module.manifest);
|
|
146
|
+
if (
|
|
147
|
+
claim.plan.languageId === module.manifest.id &&
|
|
148
|
+
claim.plan.languageVersion === module.manifest.version &&
|
|
149
|
+
claim.plan.manifestHash === manifestHash
|
|
150
|
+
) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
state: "needs_attention",
|
|
155
|
+
error: {
|
|
156
|
+
code: "WORKFLOW_MODULE_MISMATCH",
|
|
157
|
+
message: "workflow version was bound against a different app module; publish it again with the current module",
|
|
158
|
+
retryable: false,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Claims one run and carries it as far as it goes.
|
|
165
|
+
*
|
|
166
|
+
* Returns `idle` when there was nothing to claim, which is what a polling loop
|
|
167
|
+
* uses to back off. A crash anywhere inside leaves the run claimable again once
|
|
168
|
+
* its lease expires, and the journal makes the resumed attempt skip whatever
|
|
169
|
+
* already completed.
|
|
170
|
+
*/
|
|
171
|
+
export const runOneWorkflow = async (options: WorkflowWorkerOptions & { runId?: string }): Promise<WorkflowWorkerOutcome> => {
|
|
172
|
+
const db = options.db ?? sql;
|
|
173
|
+
const repository = createWorkflowRuntimeRepository({ db });
|
|
174
|
+
// Execute only. A dry run is a question and belongs to the dry-run path; the
|
|
175
|
+
// claim filter is what keeps this loop from answering it by doing the work.
|
|
176
|
+
const port = createWorkflowCoordinatorPort({ worker: options.worker, runId: options.runId, appId: options.appId, mode: "execute" });
|
|
177
|
+
|
|
178
|
+
const outcome = await coordinateWorkflowExecution<void, WorkflowRunClaim, WorkflowRunResult>({
|
|
179
|
+
input: undefined,
|
|
180
|
+
heartbeatMs: HEARTBEAT_MS,
|
|
181
|
+
port,
|
|
182
|
+
execute: async ({ claim, heartbeat }) => {
|
|
183
|
+
await traceRun(options.trace, { type: "run.started", run: runIdentity(claim, "execute") });
|
|
184
|
+
const incompatible = await validateCurrentModule(claim, options.module);
|
|
185
|
+
if (incompatible) return incompatible;
|
|
186
|
+
if (claim.consecutiveFailures >= WORKFLOW_RUN_MAX_CONSECUTIVE_FAILURES) {
|
|
187
|
+
const error = {
|
|
188
|
+
code: "WORKFLOW_RETRY_EXHAUSTED",
|
|
189
|
+
message: `workflow failed ${claim.consecutiveFailures} consecutive attempts`,
|
|
190
|
+
retryable: false,
|
|
191
|
+
};
|
|
192
|
+
await db`
|
|
193
|
+
UPDATE workflows.step_outcome AS s
|
|
194
|
+
SET state = 'failed',
|
|
195
|
+
outcome = ${{ mode: "execute", outcome: { state: "failed", error } }},
|
|
196
|
+
execution_generation = ${claim.executionGeneration},
|
|
197
|
+
finished_at = now(),
|
|
198
|
+
updated_at = now()
|
|
199
|
+
FROM workflows.run AS r
|
|
200
|
+
WHERE s.run_id = r.id
|
|
201
|
+
AND r.id = ${claim.runId}::uuid
|
|
202
|
+
AND r.execution_generation = ${claim.executionGeneration}
|
|
203
|
+
AND r.state = 'running'
|
|
204
|
+
AND s.state = 'running'
|
|
205
|
+
`;
|
|
206
|
+
return {
|
|
207
|
+
state: "failed",
|
|
208
|
+
error,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const result = await executeWorkflowPlan({
|
|
212
|
+
runId: claim.runId,
|
|
213
|
+
executionGeneration: claim.executionGeneration,
|
|
214
|
+
plan: claim.plan,
|
|
215
|
+
invocation: {
|
|
216
|
+
workflowId: claim.workflowId,
|
|
217
|
+
mode: "execute",
|
|
218
|
+
channel: "event",
|
|
219
|
+
actor: actorFromSnapshot(claim.authorization),
|
|
220
|
+
inputs: claim.inputs,
|
|
221
|
+
idempotencyKey: claim.idempotencyKey,
|
|
222
|
+
// Everything the plan reads under context.*, carried from the event.
|
|
223
|
+
context: claim.context,
|
|
224
|
+
// The occurrence's time, not the worker's: a replay after a crash has
|
|
225
|
+
// to see the same "now" the first attempt did, or a step that reads
|
|
226
|
+
// the date stops being a function of its inputs.
|
|
227
|
+
occurredAt: claim.occurredAt.toISOString(),
|
|
228
|
+
},
|
|
229
|
+
repository,
|
|
230
|
+
clock: { now: () => claim.occurredAt.toISOString() },
|
|
231
|
+
actions: options.actions,
|
|
232
|
+
...(options.trace ? { trace: options.trace } : {}),
|
|
233
|
+
...(options.values ? { values: options.values(claim) } : {}),
|
|
234
|
+
...(options.maxLoopItems === undefined ? {} : { maxLoopItems: options.maxLoopItems }),
|
|
235
|
+
});
|
|
236
|
+
const runResult = settle(result);
|
|
237
|
+
// Parking deliberately releases the lease. Heartbeating after that
|
|
238
|
+
// misdiagnoses a successful park as a lost claim.
|
|
239
|
+
if (runResult.state !== "waiting") await heartbeat();
|
|
240
|
+
return runResult;
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
switch (outcome.state) {
|
|
245
|
+
case "idle":
|
|
246
|
+
return { state: "idle" };
|
|
247
|
+
case "finished":
|
|
248
|
+
// After the coordinator persisted it: an observer that re-reads the run
|
|
249
|
+
// has to find the state this announces, not the one before it.
|
|
250
|
+
await traceRun(options.trace, {
|
|
251
|
+
type: "run.finished",
|
|
252
|
+
run: runIdentity(outcome.claim, "execute"),
|
|
253
|
+
state: outcome.result.state,
|
|
254
|
+
});
|
|
255
|
+
return { state: "finished", runId: outcome.claim.runId, result: outcome.result };
|
|
256
|
+
case "released":
|
|
257
|
+
case "retry":
|
|
258
|
+
await traceRun(options.trace, { type: "run.finished", run: runIdentity(outcome.claim, "execute"), state: "released" });
|
|
259
|
+
return { state: "released", runId: outcome.claim.runId, error: outcome.error };
|
|
260
|
+
case "stale":
|
|
261
|
+
return { state: "lost", runId: outcome.claim.runId };
|
|
262
|
+
case "canceled": {
|
|
263
|
+
const finished = await finishWorkflowRunCancel(outcome.claim, { db });
|
|
264
|
+
if (finished.state === "stale") return { state: "lost", runId: outcome.claim.runId };
|
|
265
|
+
await traceRun(options.trace, {
|
|
266
|
+
type: "run.finished",
|
|
267
|
+
run: runIdentity(outcome.claim, "execute"),
|
|
268
|
+
state: finished.result.state,
|
|
269
|
+
});
|
|
270
|
+
return { state: "finished", runId: outcome.claim.runId, result: finished.result };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export type WorkflowDryRunWorkerOptions = Omit<WorkflowWorkerOptions, "actions"> & {
|
|
276
|
+
/** The same declarations, planning instead of acting. */
|
|
277
|
+
actions: WorkflowDryRunActionPort;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export type WorkflowDryRunOutcome =
|
|
281
|
+
| { state: "idle" }
|
|
282
|
+
| { state: "finished"; runId: string; result: WorkflowDryRunResult }
|
|
283
|
+
| { state: "released"; runId: string; error: unknown }
|
|
284
|
+
| { state: "lost"; runId: string };
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* How a dry run reports: a plan is not a run that happened.
|
|
288
|
+
*
|
|
289
|
+
* `planned` and a terminal `succeeded` both mean the plan holds; anything that
|
|
290
|
+
* could not be determined is a failure of the *question*, not of the work, and
|
|
291
|
+
* shows up as the run's error so the caller sees which step could not be
|
|
292
|
+
* planned rather than an empty answer.
|
|
293
|
+
*/
|
|
294
|
+
const settleDryRun = (result: WorkflowDryRunResult): WorkflowRunResult => {
|
|
295
|
+
if (result.state === "planned") return { state: "succeeded", result: { effects: result.effects, output: result.output ?? null } };
|
|
296
|
+
if (result.state === "canceled") return { state: "canceled", ...(result.message ? { message: result.message } : {}) };
|
|
297
|
+
if (result.state === "terminal" && result.status === "succeeded") {
|
|
298
|
+
return { state: "succeeded", result: { effects: result.effects }, ...(result.message ? { message: result.message } : {}) };
|
|
299
|
+
}
|
|
300
|
+
const reason = result.state === "terminal" ? (result.message ?? "the plan would fail") : result.reason;
|
|
301
|
+
return {
|
|
302
|
+
state: "failed",
|
|
303
|
+
error: { code: `WORKFLOW_DRY_RUN_${result.state === "terminal" ? "TERMINAL" : result.state.toUpperCase()}`, message: reason },
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The same loop for the question rather than the work.
|
|
309
|
+
*
|
|
310
|
+
* A dry run is a real run with real bookkeeping — it is leased, journaled and
|
|
311
|
+
* recoverable — because a preflight over ten thousand records takes as long as
|
|
312
|
+
* the work would and must survive the same crashes. Only the claim's mode and
|
|
313
|
+
* the port it drives differ, which is exactly the distinction that keeps a dry
|
|
314
|
+
* run from performing what it was meant to describe.
|
|
315
|
+
*/
|
|
316
|
+
export const dryRunOneWorkflow = async (options: WorkflowDryRunWorkerOptions & { runId?: string }): Promise<WorkflowDryRunOutcome> => {
|
|
317
|
+
const db = options.db ?? sql;
|
|
318
|
+
const repository = createWorkflowRuntimeRepository({ db });
|
|
319
|
+
const port = createWorkflowCoordinatorPort({ worker: options.worker, runId: options.runId, appId: options.appId, mode: "dryRun" });
|
|
320
|
+
|
|
321
|
+
let planned: WorkflowDryRunResult | null = null;
|
|
322
|
+
const outcome = await coordinateWorkflowExecution<void, WorkflowRunClaim, WorkflowRunResult>({
|
|
323
|
+
input: undefined,
|
|
324
|
+
heartbeatMs: HEARTBEAT_MS,
|
|
325
|
+
port,
|
|
326
|
+
execute: async ({ claim, heartbeat }) => {
|
|
327
|
+
await traceRun(options.trace, { type: "run.started", run: runIdentity(claim, "dryRun") });
|
|
328
|
+
const incompatible = await validateCurrentModule(claim, options.module);
|
|
329
|
+
if (incompatible) {
|
|
330
|
+
planned = {
|
|
331
|
+
state: "terminal",
|
|
332
|
+
status: "failed",
|
|
333
|
+
message: String((incompatible.error as { message?: unknown }).message ?? "workflow module mismatch"),
|
|
334
|
+
effects: [],
|
|
335
|
+
};
|
|
336
|
+
return incompatible;
|
|
337
|
+
}
|
|
338
|
+
planned = await dryRunWorkflowPlan({
|
|
339
|
+
runId: claim.runId,
|
|
340
|
+
executionGeneration: claim.executionGeneration,
|
|
341
|
+
plan: claim.plan,
|
|
342
|
+
invocation: {
|
|
343
|
+
workflowId: claim.workflowId,
|
|
344
|
+
mode: "dryRun",
|
|
345
|
+
channel: "event",
|
|
346
|
+
actor: actorFromSnapshot(claim.authorization),
|
|
347
|
+
inputs: claim.inputs,
|
|
348
|
+
idempotencyKey: claim.idempotencyKey,
|
|
349
|
+
context: claim.context,
|
|
350
|
+
occurredAt: claim.occurredAt.toISOString(),
|
|
351
|
+
},
|
|
352
|
+
repository,
|
|
353
|
+
clock: { now: () => claim.occurredAt.toISOString() },
|
|
354
|
+
actions: options.actions,
|
|
355
|
+
...(options.trace ? { trace: options.trace } : {}),
|
|
356
|
+
...(options.values ? { values: options.values(claim) } : {}),
|
|
357
|
+
...(options.maxLoopItems === undefined ? {} : { maxLoopItems: options.maxLoopItems }),
|
|
358
|
+
});
|
|
359
|
+
await heartbeat();
|
|
360
|
+
return settleDryRun(planned);
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
switch (outcome.state) {
|
|
365
|
+
case "idle":
|
|
366
|
+
return { state: "idle" };
|
|
367
|
+
case "finished":
|
|
368
|
+
await traceRun(options.trace, {
|
|
369
|
+
type: "run.finished",
|
|
370
|
+
run: runIdentity(outcome.claim, "dryRun"),
|
|
371
|
+
state: outcome.result.state,
|
|
372
|
+
});
|
|
373
|
+
return { state: "finished", runId: outcome.claim.runId, result: planned as unknown as WorkflowDryRunResult };
|
|
374
|
+
case "released":
|
|
375
|
+
case "retry":
|
|
376
|
+
await traceRun(options.trace, { type: "run.finished", run: runIdentity(outcome.claim, "dryRun"), state: "released" });
|
|
377
|
+
return { state: "released", runId: outcome.claim.runId, error: outcome.error };
|
|
378
|
+
case "stale":
|
|
379
|
+
return { state: "lost", runId: outcome.claim.runId };
|
|
380
|
+
case "canceled": {
|
|
381
|
+
const finished = await finishWorkflowRunCancel(outcome.claim, { db });
|
|
382
|
+
if (finished.state === "stale") return { state: "lost", runId: outcome.claim.runId };
|
|
383
|
+
return { state: "finished", runId: outcome.claim.runId, result: planned as unknown as WorkflowDryRunResult };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export type WorkflowTickResult = {
|
|
389
|
+
/** Events turned into runs this tick. */
|
|
390
|
+
dispatched: number;
|
|
391
|
+
/** Parked runs whose deadline passed. */
|
|
392
|
+
woken: number;
|
|
393
|
+
/** Runs carried to an outcome. */
|
|
394
|
+
executed: number;
|
|
395
|
+
failed: number;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* One pass of everything the kernel owes a workflow: dispatch what happened,
|
|
400
|
+
* wake what was waiting, then run what is ready.
|
|
401
|
+
*
|
|
402
|
+
* In that order deliberately. Dispatching first means an event that arrived a
|
|
403
|
+
* moment ago is executed in the same tick rather than the next one, which is
|
|
404
|
+
* the difference between a workflow that feels immediate and one that always
|
|
405
|
+
* lags a poll interval behind.
|
|
406
|
+
*/
|
|
407
|
+
export const tickWorkflows = async (options: WorkflowWorkerOptions & { maxRuns?: number }): Promise<WorkflowTickResult> => {
|
|
408
|
+
const db = options.db ?? sql;
|
|
409
|
+
const dispatch = await dispatchPendingWorkflowEvents(100, { appId: options.appId, db });
|
|
410
|
+
await finishExpiredWorkflowRunCancels(100, { appId: options.appId, db });
|
|
411
|
+
const woken = await wakeExpiredWorkflowRuns(100, { appId: options.appId, db });
|
|
412
|
+
|
|
413
|
+
let executed = 0;
|
|
414
|
+
let failed = 0;
|
|
415
|
+
const maxRuns = options.maxRuns ?? 25;
|
|
416
|
+
for (let index = 0; index < maxRuns; index += 1) {
|
|
417
|
+
const outcome = await runOneWorkflow(options);
|
|
418
|
+
if (outcome.state === "idle") break;
|
|
419
|
+
executed += 1;
|
|
420
|
+
if (outcome.state === "released") failed += 1;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return { dispatched: dispatch.dispatched, woken: woken.length, executed, failed };
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/** Whether there is anything for a worker to do, without claiming it. */
|
|
427
|
+
export const hasClaimableWorkflowRun = async (options: { appId?: string; db?: SQL } = {}): Promise<boolean> => {
|
|
428
|
+
const db = options.db ?? sql;
|
|
429
|
+
const [row] = await db<{ exists: boolean }[]>`
|
|
430
|
+
SELECT EXISTS (
|
|
431
|
+
SELECT 1 FROM workflows.run
|
|
432
|
+
WHERE state IN ('queued', 'running') AND claimable_at < now() AND cancel_requested_at IS NULL
|
|
433
|
+
AND (${options.appId ?? null}::text IS NULL OR app_id = ${options.appId ?? null})
|
|
434
|
+
) AS exists
|
|
435
|
+
`;
|
|
436
|
+
return Boolean(row?.exists);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
export { claimWorkflowRun };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { WorkflowBoundPlan, WorkflowDependency, WorkflowIr } from "../contracts";
|
|
2
|
+
import { bindWorkflow, compileWorkflow } from "../language";
|
|
3
|
+
import {
|
|
4
|
+
executeWorkflowPlan,
|
|
5
|
+
type WorkflowExecutionResult,
|
|
6
|
+
type WorkflowRuntimeRepositoryPort,
|
|
7
|
+
type WorkflowRuntimeRunIdentity,
|
|
8
|
+
type WorkflowRuntimeStepIdentity,
|
|
9
|
+
type WorkflowRuntimeStepResult,
|
|
10
|
+
} from "../runtime";
|
|
11
|
+
import { type WorkflowProcessFixture, workflowProcessWorkflows } from "./process-fixtures";
|
|
12
|
+
|
|
13
|
+
class FixtureRepository implements WorkflowRuntimeRepositoryPort {
|
|
14
|
+
readonly finished: Array<{ step: WorkflowRuntimeStepIdentity; result: WorkflowRuntimeStepResult }> = [];
|
|
15
|
+
|
|
16
|
+
async heartbeat(_run: WorkflowRuntimeRunIdentity) {
|
|
17
|
+
return { state: "active" } as const;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async restoreStepOutcome(_step: WorkflowRuntimeStepIdentity) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async startStep(_step: WorkflowRuntimeStepIdentity) {}
|
|
25
|
+
|
|
26
|
+
async finishStep(step: WorkflowRuntimeStepIdentity, result: WorkflowRuntimeStepResult) {
|
|
27
|
+
this.finished.push({ step, result });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async parkStep(step: WorkflowRuntimeStepIdentity, dependency: WorkflowDependency) {
|
|
31
|
+
this.finished.push({ step, result: { mode: "execute", outcome: { state: "waiting", dependency } } });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type WorkflowProcessFixtureResult = {
|
|
36
|
+
ir: WorkflowIr;
|
|
37
|
+
plan: WorkflowBoundPlan;
|
|
38
|
+
execution: WorkflowExecutionResult;
|
|
39
|
+
finishedSteps: Array<{ step: WorkflowRuntimeStepIdentity; result: WorkflowRuntimeStepResult }>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const runWorkflowProcessFixture = async (fixture: WorkflowProcessFixture): Promise<WorkflowProcessFixtureResult> => {
|
|
43
|
+
const compiled = await compileWorkflow(fixture.source, workflowProcessWorkflows);
|
|
44
|
+
if (!compiled.ok) {
|
|
45
|
+
throw new Error(compiled.diagnostics.map((diagnostic) => `${diagnostic.path.join(".")}: ${diagnostic.message}`).join("\n"));
|
|
46
|
+
}
|
|
47
|
+
const plan = await bindWorkflow(compiled.ir, workflowProcessWorkflows, () => ({
|
|
48
|
+
catalog: fixture.catalog,
|
|
49
|
+
bindings: fixture.bindings,
|
|
50
|
+
}));
|
|
51
|
+
const repository = new FixtureRepository();
|
|
52
|
+
const execution = await executeWorkflowPlan({
|
|
53
|
+
runId: `fixture:${fixture.id}`,
|
|
54
|
+
executionGeneration: 1,
|
|
55
|
+
clock: { now: () => fixture.invocation.occurredAt },
|
|
56
|
+
plan,
|
|
57
|
+
invocation: fixture.invocation,
|
|
58
|
+
repository,
|
|
59
|
+
actions: {
|
|
60
|
+
get: (action) =>
|
|
61
|
+
action === "capture"
|
|
62
|
+
? {
|
|
63
|
+
execute: async (context, step) => ({
|
|
64
|
+
state: "completed",
|
|
65
|
+
output: await context.evaluate(step.config.value ?? null),
|
|
66
|
+
}),
|
|
67
|
+
}
|
|
68
|
+
: undefined,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return { ir: compiled.ir, plan, execution, finishedSteps: repository.finished };
|
|
72
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { runWorkflowProcessFixture, type WorkflowProcessFixtureResult } from "./conformance";
|
|
2
|
+
export {
|
|
3
|
+
bulkLauncherProcessFixture,
|
|
4
|
+
directOnlyProcessFixture,
|
|
5
|
+
recordEventProcessFixture,
|
|
6
|
+
scannerLauncherProcessFixture,
|
|
7
|
+
scheduleProcessFixture,
|
|
8
|
+
type WorkflowProcessFixture,
|
|
9
|
+
workflowProcessFixtures,
|
|
10
|
+
workflowProcessManifest,
|
|
11
|
+
} from "./process-fixtures";
|