@pikku/core 0.12.72 → 0.12.77
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/CHANGELOG.md +1171 -0
- package/dist/column-form.d.ts +32 -0
- package/dist/column-form.js +42 -0
- package/dist/crypto-utils.d.ts +43 -7
- package/dist/crypto-utils.js +163 -42
- package/dist/data-classification.d.ts +44 -0
- package/dist/dev/hot-reload.js +11 -30
- package/dist/dev/module-runner.d.ts +3 -7
- package/dist/dev/module-runner.js +4 -10
- package/dist/dev/reload-meta.d.ts +8 -20
- package/dist/dev/reload-meta.js +9 -29
- package/dist/errors/error-handler.d.ts +5 -30
- package/dist/errors/error-handler.js +16 -32
- package/dist/errors/errors.d.ts +32 -151
- package/dist/errors/errors.js +55 -157
- package/dist/function/abort-scope.d.ts +47 -0
- package/dist/function/abort-scope.js +63 -0
- package/dist/function/function-runner.js +37 -32
- package/dist/function/functions.types.d.ts +57 -136
- package/dist/function/functions.types.js +0 -58
- package/dist/function/index.d.ts +1 -1
- package/dist/function/list.types.d.ts +12 -62
- package/dist/function/list.types.js +4 -25
- package/dist/handle-error.d.ts +0 -13
- package/dist/handle-error.js +0 -18
- package/dist/index.d.ts +11 -5
- package/dist/index.js +7 -2
- package/dist/middleware/auth-apikey.d.ts +3 -18
- package/dist/middleware/auth-apikey.js +0 -17
- package/dist/middleware/auth-bearer.d.ts +6 -41
- package/dist/middleware/auth-bearer.js +5 -41
- package/dist/middleware/auth-cookie.d.ts +5 -27
- package/dist/middleware/auth-cookie.js +2 -26
- package/dist/middleware/cors.d.ts +7 -34
- package/dist/middleware/cors.js +7 -34
- package/dist/middleware/remote-auth.d.ts +3 -1
- package/dist/middleware/remote-auth.js +4 -3
- package/dist/middleware/telemetry.d.ts +8 -33
- package/dist/middleware/telemetry.js +2 -31
- package/dist/middleware-runner.d.ts +4 -55
- package/dist/middleware-runner.js +5 -74
- package/dist/permissions.d.ts +3 -44
- package/dist/permissions.js +19 -71
- package/dist/pikku-request.d.ts +0 -6
- package/dist/pikku-request.js +0 -6
- package/dist/pikku-state.d.ts +0 -26
- package/dist/pikku-state.js +2 -30
- package/dist/remote.d.ts +3 -5
- package/dist/remote.js +9 -8
- package/dist/schema.d.ts +5 -39
- package/dist/schema.js +5 -39
- package/dist/scopes.d.ts +4 -23
- package/dist/scopes.js +7 -48
- package/dist/secret-value.d.ts +56 -0
- package/dist/secret-value.js +46 -0
- package/dist/services/ai-agent-runner-service.d.ts +20 -0
- package/dist/services/ai-embedding-service.d.ts +2 -25
- package/dist/services/audit-service.d.ts +74 -4
- package/dist/services/audit-service.js +8 -7
- package/dist/services/content-service.d.ts +1 -46
- package/dist/services/credential-service.d.ts +3 -40
- package/dist/services/credential-wire-service.d.ts +5 -0
- package/dist/services/credential-wire-service.js +9 -1
- package/dist/services/deployment-service.d.ts +3 -9
- package/dist/services/email-service.d.ts +2 -1
- package/dist/services/gateway-service.d.ts +0 -15
- package/dist/services/http-personas.d.ts +80 -0
- package/dist/services/http-personas.js +233 -0
- package/dist/services/in-memory-queue-service.d.ts +0 -14
- package/dist/services/in-memory-queue-service.js +1 -15
- package/dist/services/in-memory-trigger-service.d.ts +0 -18
- package/dist/services/in-memory-trigger-service.js +1 -18
- package/dist/services/in-memory-workflow-service.d.ts +0 -16
- package/dist/services/in-memory-workflow-service.js +4 -33
- package/dist/services/index.d.ts +8 -9
- package/dist/services/index.js +3 -6
- package/dist/services/istanbul-coverage-service.d.ts +1 -5
- package/dist/services/istanbul-coverage-service.js +2 -8
- package/dist/services/jwt-service.d.ts +1 -16
- package/dist/services/local-content-request-handler.d.ts +29 -0
- package/dist/services/local-content-request-handler.js +176 -0
- package/dist/services/local-content.d.ts +13 -2
- package/dist/services/local-content.js +40 -13
- package/dist/services/local-gateway-service.d.ts +0 -16
- package/dist/services/local-gateway-service.js +2 -17
- package/dist/services/local-secrets.d.ts +4 -7
- package/dist/services/local-secrets.js +7 -7
- package/dist/services/logger-console.d.ts +3 -7
- package/dist/services/logger-console.js +3 -7
- package/dist/services/logger.d.ts +22 -40
- package/dist/services/meta-service.d.ts +23 -26
- package/dist/services/meta-service.js +22 -36
- package/dist/services/personas-service.d.ts +134 -0
- package/dist/services/personas-service.js +40 -0
- package/dist/services/pikku-user-id.js +0 -4
- package/dist/services/queue-webhook-service.d.ts +2 -36
- package/dist/services/queue-webhook-service.js +10 -42
- package/dist/services/scheduler-service.d.ts +1 -50
- package/dist/services/scheduler-service.js +0 -10
- package/dist/services/schema-service.d.ts +1 -24
- package/dist/services/scope-service.d.ts +49 -34
- package/dist/services/scoped-secret-service.d.ts +4 -7
- package/dist/services/scoped-secret-service.js +0 -4
- package/dist/services/secret-host-binding.d.ts +8 -0
- package/dist/services/secret-host-binding.js +36 -0
- package/dist/services/secret-service.d.ts +12 -35
- package/dist/services/secretless.d.ts +6 -0
- package/dist/services/secretless.js +21 -0
- package/dist/services/stub-tracker.d.ts +7 -18
- package/dist/services/stub-tracker.js +8 -18
- package/dist/services/system-role-guard.d.ts +33 -0
- package/dist/services/system-role-guard.js +38 -0
- package/dist/services/trigger-service.d.ts +0 -12
- package/dist/services/typed-secret-service.d.ts +5 -11
- package/dist/services/typed-secret-service.js +1 -7
- package/dist/services/v8-coverage-service.d.ts +2 -3
- package/dist/services/v8-coverage-service.js +1 -2
- package/dist/services/variables-service.d.ts +1 -8
- package/dist/services/webhook-service.d.ts +21 -64
- package/dist/services/webhook-service.js +6 -20
- package/dist/services/workflow-service.d.ts +3 -15
- package/dist/testing/service-tests.js +6 -23
- package/dist/time-utils.d.ts +0 -16
- package/dist/time-utils.js +1 -19
- package/dist/types/core.types.d.ts +120 -219
- package/dist/types/core.types.js +0 -42
- package/dist/types/state.types.d.ts +4 -9
- package/dist/utils/hmac.d.ts +4 -10
- package/dist/utils/hmac.js +4 -10
- package/dist/utils/safe-fetch.d.ts +7 -35
- package/dist/utils/safe-fetch.js +13 -53
- package/dist/utils.d.ts +1 -6
- package/dist/utils.js +6 -15
- package/dist/wirings/actor-flow/actor-flow.types.d.ts +1 -34
- package/dist/wirings/actor-flow/index.d.ts +0 -9
- package/dist/wirings/actor-flow/run-conversation.d.ts +5 -5
- package/dist/wirings/actor-flow/run-conversation.js +14 -7
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +0 -5
- package/dist/wirings/ai-agent/ai-agent-agui.js +46 -12
- package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-helpers.js +7 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.d.ts +153 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +256 -0
- package/dist/wirings/ai-agent/ai-agent-memory.js +0 -2
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +0 -9
- package/dist/wirings/ai-agent/ai-agent-model-config.js +1 -9
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -99
- package/dist/wirings/ai-agent/ai-agent-prepare.js +65 -132
- package/dist/wirings/ai-agent/ai-agent-registry.d.ts +2 -1
- package/dist/wirings/ai-agent/ai-agent-registry.js +5 -1
- package/dist/wirings/ai-agent/ai-agent-runner.js +63 -22
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +25 -2
- package/dist/wirings/ai-agent/ai-agent-stream.js +180 -64
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +124 -4
- package/dist/wirings/ai-agent/index.d.ts +6 -4
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-agent/voice-input.d.ts +59 -1
- package/dist/wirings/ai-agent/voice-input.js +90 -12
- package/dist/wirings/ai-agent/voice-output.d.ts +69 -1
- package/dist/wirings/ai-agent/voice-output.js +162 -50
- package/dist/wirings/channel/channel-common.d.ts +7 -20
- package/dist/wirings/channel/channel-common.js +7 -21
- package/dist/wirings/channel/channel-handler.js +25 -6
- package/dist/wirings/channel/channel-host-rpc.d.ts +25 -0
- package/dist/wirings/channel/channel-host-rpc.js +38 -0
- package/dist/wirings/channel/channel-middleware-runner.d.ts +0 -12
- package/dist/wirings/channel/channel-middleware-runner.js +0 -12
- package/dist/wirings/channel/channel-rpc-registry.d.ts +31 -0
- package/dist/wirings/channel/channel-rpc-registry.js +89 -0
- package/dist/wirings/channel/channel-rpc-responder.d.ts +15 -0
- package/dist/wirings/channel/channel-rpc-responder.js +71 -0
- package/dist/wirings/channel/channel-rpc-service.d.ts +40 -0
- package/dist/wirings/channel/channel-rpc-service.js +106 -0
- package/dist/wirings/channel/channel-rpc-validators.d.ts +14 -0
- package/dist/wirings/channel/channel-rpc-validators.js +30 -0
- package/dist/wirings/channel/channel-rpc.d.ts +5 -0
- package/dist/wirings/channel/channel-rpc.js +5 -0
- package/dist/wirings/channel/channel-rpc.types.d.ts +90 -0
- package/dist/wirings/channel/channel-rpc.types.js +50 -0
- package/dist/wirings/channel/channel-runner.d.ts +0 -4
- package/dist/wirings/channel/channel-runner.js +0 -14
- package/dist/wirings/channel/channel-store.d.ts +0 -10
- package/dist/wirings/channel/channel.types.d.ts +12 -1
- package/dist/wirings/channel/define-channel-routes.d.ts +0 -20
- package/dist/wirings/channel/define-channel-routes.js +0 -20
- package/dist/wirings/channel/eventhub-service.d.ts +0 -18
- package/dist/wirings/channel/index.d.ts +4 -1
- package/dist/wirings/channel/index.js +2 -0
- package/dist/wirings/channel/local/local-channel-runner.js +3 -1
- package/dist/wirings/channel/local/local-eventhub-service.d.ts +0 -33
- package/dist/wirings/channel/local/local-eventhub-service.js +2 -36
- package/dist/wirings/channel/log-channels.d.ts +0 -4
- package/dist/wirings/channel/log-channels.js +0 -4
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +6 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -5
- package/dist/wirings/cli/channel/cli-approval.d.ts +41 -0
- package/dist/wirings/cli/channel/cli-approval.js +81 -0
- package/dist/wirings/cli/channel/cli-channel-runner.d.ts +0 -4
- package/dist/wirings/cli/channel/cli-channel-runner.js +3 -25
- package/dist/wirings/cli/channel/cli-raw-channel-runner.d.ts +47 -9
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +24 -16
- package/dist/wirings/cli/channel/cli-raw-client-runner.d.ts +38 -0
- package/dist/wirings/cli/channel/cli-raw-client-runner.js +129 -0
- package/dist/wirings/cli/channel/index.d.ts +5 -0
- package/dist/wirings/cli/channel/index.js +2 -0
- package/dist/wirings/cli/cli-runner.d.ts +20 -20
- package/dist/wirings/cli/cli-runner.js +28 -89
- package/dist/wirings/cli/cli.types.d.ts +14 -3
- package/dist/wirings/cli/command-parser.d.ts +1 -10
- package/dist/wirings/cli/command-parser.js +10 -87
- package/dist/wirings/cli/define-cli-commands.d.ts +1 -17
- package/dist/wirings/cli/define-cli-commands.js +1 -17
- package/dist/wirings/credential/credential.types.d.ts +0 -12
- package/dist/wirings/credential/define-credential.d.ts +48 -0
- package/dist/wirings/credential/define-credential.js +47 -0
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/credential/index.js +1 -1
- package/dist/wirings/credential/validate-credential-definitions.d.ts +2 -4
- package/dist/wirings/gateway/gateway-runner.d.ts +1 -20
- package/dist/wirings/gateway/gateway-runner.js +8 -105
- package/dist/wirings/gateway/gateway.types.d.ts +7 -80
- package/dist/wirings/http/http-routes.d.ts +0 -63
- package/dist/wirings/http/http-routes.js +0 -63
- package/dist/wirings/http/http-runner.d.ts +0 -99
- package/dist/wirings/http/http-runner.js +9 -165
- package/dist/wirings/http/http.types.d.ts +14 -55
- package/dist/wirings/http/log-http-routes.d.ts +0 -4
- package/dist/wirings/http/log-http-routes.js +0 -4
- package/dist/wirings/http/pikku-fetch-http-request.d.ts +0 -40
- package/dist/wirings/http/pikku-fetch-http-request.js +0 -58
- package/dist/wirings/http/pikku-fetch-http-response.js +0 -3
- package/dist/wirings/http/routers/path-to-regex.js +2 -13
- package/dist/wirings/http/web-request.d.ts +0 -8
- package/dist/wirings/http/web-request.js +25 -17
- package/dist/wirings/mcp/mcp-runner.d.ts +1 -4
- package/dist/wirings/mcp/mcp-runner.js +1 -14
- package/dist/wirings/mcp/mcp.types.d.ts +2 -35
- package/dist/wirings/oauth2/oauth2.types.d.ts +0 -28
- package/dist/wirings/oauth2/oauth2.types.js +0 -3
- package/dist/wirings/persona/define-personas.d.ts +32 -0
- package/dist/wirings/persona/define-personas.js +31 -0
- package/dist/wirings/persona/index.d.ts +21 -0
- package/dist/wirings/persona/index.js +17 -0
- package/dist/wirings/persona/persona-email.d.ts +37 -0
- package/dist/wirings/persona/persona-email.js +69 -0
- package/dist/wirings/persona/persona-environments.d.ts +45 -0
- package/dist/wirings/persona/persona-environments.js +81 -0
- package/dist/wirings/persona/persona-mailbox.d.ts +101 -0
- package/dist/wirings/persona/persona-mailbox.js +53 -0
- package/dist/wirings/persona/persona.types.d.ts +136 -0
- package/dist/wirings/persona/persona.types.js +1 -0
- package/dist/wirings/persona/validate-personas.d.ts +53 -0
- package/dist/wirings/persona/validate-personas.js +94 -0
- package/dist/wirings/queue/index.d.ts +3 -0
- package/dist/wirings/queue/index.js +2 -3
- package/dist/wirings/queue/queue-identity.d.ts +28 -0
- package/dist/wirings/queue/queue-identity.js +103 -0
- package/dist/wirings/queue/queue-runner.d.ts +0 -19
- package/dist/wirings/queue/queue-runner.js +9 -30
- package/dist/wirings/queue/queue.types.d.ts +20 -90
- package/dist/wirings/queue/register-queue-helper.d.ts +0 -12
- package/dist/wirings/queue/register-queue-helper.js +0 -11
- package/dist/wirings/queue/signed-queue-service.d.ts +17 -0
- package/dist/wirings/queue/signed-queue-service.js +42 -0
- package/dist/wirings/queue/validate-worker-config.d.ts +2 -23
- package/dist/wirings/queue/validate-worker-config.js +0 -14
- package/dist/wirings/role/define-system-role.d.ts +32 -0
- package/dist/wirings/role/define-system-role.js +31 -0
- package/dist/wirings/role/index.d.ts +3 -0
- package/dist/wirings/role/index.js +2 -0
- package/dist/wirings/role/role.types.d.ts +43 -0
- package/dist/wirings/role/role.types.js +1 -0
- package/dist/wirings/role/validate-role-definitions.d.ts +21 -0
- package/dist/wirings/role/validate-role-definitions.js +71 -0
- package/dist/wirings/rpc/addon-runner.d.ts +0 -19
- package/dist/wirings/rpc/addon-runner.js +0 -51
- package/dist/wirings/rpc/remote-addon-auth.d.ts +3 -13
- package/dist/wirings/rpc/remote-addon-auth.js +7 -11
- package/dist/wirings/rpc/rpc-runner.d.ts +11 -18
- package/dist/wirings/rpc/rpc-runner.js +88 -105
- package/dist/wirings/rpc/rpc-types.d.ts +7 -6
- package/dist/wirings/rpc/wire-addon.d.ts +25 -0
- package/dist/wirings/rpc/wire-addon.js +62 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +3 -28
- package/dist/wirings/rpc/wire-remote-addon.js +0 -8
- package/dist/wirings/scheduler/log-schedulers.d.ts +0 -4
- package/dist/wirings/scheduler/log-schedulers.js +0 -4
- package/dist/wirings/scheduler/scheduler-runner.d.ts +0 -1
- package/dist/wirings/scheduler/scheduler-runner.js +0 -1
- package/dist/wirings/scheduler/scheduler.types.d.ts +1 -14
- package/dist/wirings/scope/define-scope.d.ts +32 -0
- package/dist/wirings/scope/define-scope.js +31 -0
- package/dist/wirings/scope/index.d.ts +1 -1
- package/dist/wirings/scope/index.js +1 -1
- package/dist/wirings/scope/scope.types.d.ts +7 -9
- package/dist/wirings/scope/validate-scope-definitions.d.ts +2 -21
- package/dist/wirings/scope/validate-scope-definitions.js +3 -21
- package/dist/wirings/secret/index.d.ts +1 -1
- package/dist/wirings/secret/index.js +1 -1
- package/dist/wirings/secret/secret.types.d.ts +19 -15
- package/dist/wirings/secret/secret.types.js +1 -1
- package/dist/wirings/secret/validate-secret-definitions.d.ts +2 -4
- package/dist/wirings/trigger/trigger-runner.d.ts +0 -27
- package/dist/wirings/trigger/trigger-runner.js +1 -24
- package/dist/wirings/trigger/trigger.types.d.ts +1 -82
- package/dist/wirings/trigger/trigger.types.js +0 -34
- package/dist/wirings/variable/index.d.ts +1 -1
- package/dist/wirings/variable/index.js +1 -1
- package/dist/wirings/variable/validate-variable-definitions.d.ts +2 -4
- package/dist/wirings/variable/variable.types.d.ts +1 -13
- package/dist/wirings/variable/variable.types.js +1 -1
- package/dist/wirings/virtual-user/index.d.ts +30 -0
- package/dist/wirings/virtual-user/index.js +10 -0
- package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +54 -0
- package/dist/wirings/virtual-user/prepare-virtual-user-run.js +49 -0
- package/dist/wirings/virtual-user/run-virtual-user.d.ts +92 -0
- package/dist/wirings/virtual-user/run-virtual-user.js +478 -0
- package/dist/wirings/virtual-user/virtual-user-agents.d.ts +38 -0
- package/dist/wirings/virtual-user/virtual-user-agents.js +24 -0
- package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +92 -0
- package/dist/wirings/virtual-user/virtual-user-catalogue.js +134 -0
- package/dist/wirings/virtual-user/virtual-user-derive.d.ts +26 -0
- package/dist/wirings/virtual-user/virtual-user-derive.js +137 -0
- package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +79 -0
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +128 -0
- package/dist/wirings/virtual-user/virtual-user-intents.d.ts +78 -0
- package/dist/wirings/virtual-user/virtual-user-intents.js +142 -0
- package/dist/wirings/virtual-user/virtual-user-rng.d.ts +24 -0
- package/dist/wirings/virtual-user/virtual-user-rng.js +44 -0
- package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +90 -0
- package/dist/wirings/virtual-user/virtual-user-run-store.js +1 -0
- package/dist/wirings/virtual-user/virtual-user-target.d.ts +21 -0
- package/dist/wirings/virtual-user/virtual-user-target.js +34 -0
- package/dist/wirings/virtual-user/virtual-user.types.d.ts +199 -0
- package/dist/wirings/virtual-user/virtual-user.types.js +8 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +19 -15
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +0 -4
- package/dist/wirings/workflow/dsl/workflow-runner.js +0 -4
- package/dist/wirings/workflow/feature.d.ts +0 -19
- package/dist/wirings/workflow/feature.js +0 -19
- package/dist/wirings/workflow/graph/graph-node.d.ts +0 -98
- package/dist/wirings/workflow/graph/graph-node.js +0 -34
- package/dist/wirings/workflow/graph/graph-runner.js +6 -41
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +0 -4
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -58
- package/dist/wirings/workflow/graph/workflow-graph.types.js +0 -6
- package/dist/wirings/workflow/index.d.ts +5 -7
- package/dist/wirings/workflow/index.js +3 -17
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +87 -5
- package/dist/wirings/workflow/pikku-scenario-service.js +204 -43
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +7 -459
- package/dist/wirings/workflow/pikku-workflow-service.js +58 -551
- package/dist/wirings/workflow/run-timeline.d.ts +0 -47
- package/dist/wirings/workflow/run-timeline.js +0 -22
- package/dist/wirings/workflow/scenario-cookie-jar.d.ts +0 -23
- package/dist/wirings/workflow/scenario-cookie-jar.js +0 -16
- package/dist/wirings/workflow/scenario-poll.d.ts +0 -15
- package/dist/wirings/workflow/scenario-poll.js +0 -12
- package/dist/wirings/workflow/scenario-prose.d.ts +0 -28
- package/dist/wirings/workflow/scenario-prose.js +1 -19
- package/dist/wirings/workflow/scenario-step-guards.d.ts +0 -13
- package/dist/wirings/workflow/scenario-step-guards.js +1 -14
- package/dist/wirings/workflow/scenario-step.types.d.ts +84 -12
- package/dist/wirings/workflow/scenario-step.types.js +5 -1
- package/dist/wirings/workflow/scenario-surface.d.ts +16 -0
- package/dist/wirings/workflow/scenario-surface.js +56 -0
- package/dist/wirings/workflow/workflow-invocation-id.d.ts +0 -18
- package/dist/wirings/workflow/workflow-invocation-id.js +2 -22
- package/dist/wirings/workflow/workflow-queue-workers.d.ts +0 -20
- package/dist/wirings/workflow/workflow-queue-workers.js +0 -19
- package/dist/wirings/workflow/workflow.types.d.ts +5 -195
- package/knowledge/decisions/index.md +19 -0
- package/knowledge/decisions/internals/a-secret-that-fails-to-decrypt-fails-the-whole-read.md +49 -0
- package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-and-not-a-queued-job.md +48 -0
- package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +23 -0
- package/knowledge/decisions/internals/actor-flow-drives-the-target-through-a-transport-seam.md +24 -0
- package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +25 -0
- package/knowledge/decisions/internals/addon-package-roots-resolve-by-walking-node-module-search-paths.md +26 -0
- package/knowledge/decisions/internals/addon-singleton-services-are-cached-per-namespace-not-per-package.md +33 -0
- package/knowledge/decisions/internals/addon-workflow-names-are-prefixed-with-the-consumer-namespace.md +28 -0
- package/knowledge/decisions/internals/ai-agent-agui-bridge-obeys-the-client-ordering-contract.md +29 -0
- package/knowledge/decisions/internals/ai-agent-audio-chunks-carry-the-format-the-provider-returned.md +20 -0
- package/knowledge/decisions/internals/ai-agent-credential-suspensions-hide-the-tool-result.md +26 -0
- package/knowledge/decisions/internals/ai-agent-delegate-and-supervise-hide-different-text.md +26 -0
- package/knowledge/decisions/internals/ai-agent-llm-tool-arguments-have-nulls-stripped.md +23 -0
- package/knowledge/decisions/internals/ai-agent-model-config-stays-a-single-resolution-seam.md +25 -0
- package/knowledge/decisions/internals/ai-agent-onerror-hooks-cannot-change-the-failure.md +22 -0
- package/knowledge/decisions/internals/ai-agent-runner-methods-must-keep-their-receiver.md +22 -0
- package/knowledge/decisions/internals/ai-agent-stream-persistence-is-best-effort.md +27 -0
- package/knowledge/decisions/internals/ai-agent-sub-agents-inherit-the-parent-context-block.md +26 -0
- package/knowledge/decisions/internals/ai-agent-tool-execute-failures-are-logged-unconditionally.md +25 -0
- package/knowledge/decisions/internals/ai-agent-voice-input-transcribes-audio-parts-in-place.md +22 -0
- package/knowledge/decisions/internals/ai-agent-working-memory-is-persisted-only-when-valid.md +25 -0
- package/knowledge/decisions/internals/channel-message-handlers-accept-three-config-shapes.md +30 -0
- package/knowledge/decisions/internals/channel-middleware-caches-only-statically-resolved-middleware.md +31 -0
- package/knowledge/decisions/internals/channel-state-is-per-socket-session-state-is-per-user.md +29 -0
- package/knowledge/decisions/internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md +28 -0
- package/knowledge/decisions/internals/cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md +27 -0
- package/knowledge/decisions/internals/cli-parse-errors-are-routed-by-message-prefix.md +28 -0
- package/knowledge/decisions/internals/cli-stdout-is-reserved-for-machine-readable-output.md +34 -0
- package/knowledge/decisions/internals/cli-unknown-long-options-warn-instead-of-failing.md +29 -0
- package/knowledge/decisions/internals/core-column-form-is-an-axis-of-its-own.md +84 -0
- package/knowledge/decisions/internals/core-data-classification-brand-is-an-optional-property.md +41 -0
- package/knowledge/decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md +44 -0
- package/knowledge/decisions/internals/core-hot-reload-merges-generated-meta-never-replaces-it.md +39 -0
- package/knowledge/decisions/internals/core-hot-reload-owns-its-module-registry.md +42 -0
- package/knowledge/decisions/internals/core-middleware-order-is-scope-then-priority.md +39 -0
- package/knowledge/decisions/internals/core-schema-defaults-apply-on-every-transport.md +43 -0
- package/knowledge/decisions/internals/core-scopes-are-an-and-gate-separate-from-permissions.md +38 -0
- package/knowledge/decisions/internals/core-state-is-a-global-map-written-only-at-registration-time.md +44 -0
- package/knowledge/decisions/internals/email-meta-is-read-uncached-because-codegen-rewrites-it-mid-session.md +27 -0
- package/knowledge/decisions/internals/gateway-adapters-resolve-lazily-and-are-promise-cached.md +32 -0
- package/knowledge/decisions/internals/gateway-webhook-challenges-echo-bytes-not-json.md +28 -0
- package/knowledge/decisions/internals/gateway-wiring-is-a-meta-wiring-over-http-and-channels.md +31 -0
- package/knowledge/decisions/internals/generated-src-paths-in-pikku-meta-are-absolute.md +26 -0
- package/knowledge/decisions/internals/http-request-bodies-are-read-once-and-shared.md +32 -0
- package/knowledge/decisions/internals/http-route-groups-cascade-config-in-a-fixed-order.md +28 -0
- package/knowledge/decisions/internals/http-router-matches-normalized-paths-but-returns-registered-ones.md +30 -0
- package/knowledge/decisions/internals/http-runner-logs-through-a-trace-scoped-logger-functions-do-not.md +26 -0
- package/knowledge/decisions/internals/http-set-cookie-headers-are-appended-never-joined.md +28 -0
- package/knowledge/decisions/internals/http-sse-streams-flush-headers-only-after-middleware.md +32 -0
- package/knowledge/decisions/internals/http-wiring-without-metadata-is-skipped-not-fatal.md +26 -0
- package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +25 -0
- package/knowledge/decisions/internals/in-memory-workflow-history-aliases-the-live-step-object.md +26 -0
- package/knowledge/decisions/internals/index.md +117 -0
- package/knowledge/decisions/internals/istanbul-statement-counts-attach-to-the-start-line-only.md +25 -0
- package/knowledge/decisions/internals/local-trigger-and-gateway-services-assume-a-single-process.md +26 -0
- package/knowledge/decisions/internals/node-only-builtins-are-imported-dynamically.md +24 -0
- package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +53 -0
- package/knowledge/decisions/internals/queue-group-concurrency-keeps-one-shared-queue-fair.md +28 -0
- package/knowledge/decisions/internals/queue-jobs-always-carry-an-explicit-attempts-count.md +27 -0
- package/knowledge/decisions/internals/remote-addons-dispatch-over-http-instead-of-local-meta.md +31 -0
- package/knowledge/decisions/internals/rpc-names-resolve-through-package-scope-before-root.md +32 -0
- package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +27 -0
- package/knowledge/decisions/internals/scenario-meta-lives-apart-from-app-meta-but-merges-when-read-off-disk.md +26 -0
- package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +27 -0
- package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +48 -0
- package/knowledge/decisions/internals/scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md +30 -0
- package/knowledge/decisions/internals/serverless-channel-disconnect-must-tolerate-a-missing-channel.md +28 -0
- package/knowledge/decisions/internals/the-dev-queue-copies-prod-timing-and-serialization-semantics.md +30 -0
- package/knowledge/decisions/internals/the-embedding-model-is-pinned-per-service-and-doc-query-embedding-is-split.md +29 -0
- package/knowledge/decisions/internals/the-in-memory-workflow-service-is-inline-only-and-single-process.md +27 -0
- package/knowledge/decisions/internals/the-kek-salt-is-scoped-to-the-key-version.md +40 -0
- package/knowledge/decisions/internals/the-schema-service-is-never-stubbed.md +26 -0
- package/knowledge/decisions/internals/trigger-declaration-is-split-from-trigger-source.md +33 -0
- package/knowledge/decisions/internals/typed-secret-service-caches-for-the-process-lifetime.md +26 -0
- package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +43 -0
- package/knowledge/decisions/internals/webhook-delivery-history-records-every-attempt-best-effort.md +26 -0
- package/knowledge/decisions/internals/webhook-service-collaborators-are-constructor-args-not-locator-lookups.md +25 -0
- package/knowledge/decisions/internals/whether-a-run-is-inline-is-read-from-the-run-record.md +58 -0
- package/knowledge/decisions/internals/workflow-approval-expiry-is-decided-from-a-recorded-deadline.md +34 -0
- package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +42 -0
- package/knowledge/decisions/internals/workflow-dsl-meta-separates-runtime-expressions-from-literals.md +38 -0
- package/knowledge/decisions/internals/workflow-features-resolve-scenarios-by-object-identity.md +29 -0
- package/knowledge/decisions/internals/workflow-graph-inline-and-queued-runs-share-one-planner.md +42 -0
- package/knowledge/decisions/internals/workflow-graph-node-notes-are-excluded-from-the-graph-hash.md +25 -0
- package/knowledge/decisions/internals/workflow-inline-runs-report-their-run-id-before-they-can-fail.md +29 -0
- package/knowledge/decisions/internals/workflow-invocation-id-is-the-dedupe-key-not-step-id.md +43 -0
- package/knowledge/decisions/internals/workflow-queued-step-dispatch-requires-an-explicit-opt-in.md +29 -0
- package/knowledge/decisions/internals/workflow-queues-are-per-workflow-by-default.md +42 -0
- package/knowledge/decisions/internals/workflow-repeated-step-names-get-an-ordinal-suffix.md +33 -0
- package/knowledge/decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md +32 -0
- package/knowledge/decisions/internals/workflow-retries-are-owned-by-the-workflow-not-the-queue.md +31 -0
- package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +39 -0
- package/knowledge/decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md +29 -0
- package/knowledge/decisions/internals/workflow-run-polling-backs-off-to-the-callers-ceiling.md +33 -0
- package/knowledge/decisions/internals/workflow-run-timeline-is-a-pure-fold-over-durable-history.md +37 -0
- package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +50 -0
- package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +43 -0
- package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +32 -0
- package/knowledge/decisions/internals/workflow-scenario-quarantine-reason-lives-in-code.md +18 -0
- package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +34 -0
- package/knowledge/decisions/internals/workflow-step-compensation-runs-as-its-own-durable-step.md +26 -0
- package/knowledge/decisions/internals/workflow-step-dispatch-failure-is-transient-not-a-run-failure.md +33 -0
- package/knowledge/decisions/internals/workflow-step-lock-is-held-only-to-claim-the-step.md +27 -0
- package/knowledge/decisions/internals/workflow-step-rpc-name-is-provenance-only.md +34 -0
- package/knowledge/decisions/internals/workflow-suspend-and-approval-reasons-are-durable-step-identities.md +38 -0
- package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +30 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +26 -0
- package/knowledge/decisions/security/actor-flow-missing-approval-decisions-default-to-denied.md +22 -0
- package/knowledge/decisions/security/actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md +27 -0
- package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +27 -0
- package/knowledge/decisions/security/addon-auth-and-tags-only-tighten.md +43 -0
- package/knowledge/decisions/security/addon-config-gates-apply-only-at-the-namespaced-rpc-boundary.md +52 -0
- package/knowledge/decisions/security/addon-scopes-are-resolved-where-the-function-runs.md +46 -0
- package/knowledge/decisions/security/ai-agent-approval-forwarding-requires-a-symbol-brand.md +27 -0
- package/knowledge/decisions/security/ai-agent-credential-requests-are-symbol-branded.md +37 -0
- package/knowledge/decisions/security/ai-agent-gate-requires-a-session-only-when-auth-is-true.md +31 -0
- package/knowledge/decisions/security/ai-agent-ownership-failures-never-echo-the-resource.md +23 -0
- package/knowledge/decisions/security/ai-agent-resume-re-runs-the-authorization-gate.md +22 -0
- package/knowledge/decisions/security/ai-agent-sessionless-deployments-have-no-thread-ownership.md +39 -0
- package/knowledge/decisions/security/ai-agent-thread-ownership-composes-the-session-principal.md +30 -0
- package/knowledge/decisions/security/ai-agent-tool-filtering-reads-the-live-function-config.md +24 -0
- package/knowledge/decisions/security/an-empty-owners-constraint-matches-nothing.md +30 -0
- package/knowledge/decisions/security/an-exposed-ungated-function-is-a-codegen-warning.md +49 -0
- package/knowledge/decisions/security/console-addon-privileged-functions-gate-themselves.md +76 -0
- package/knowledge/decisions/security/core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md +39 -0
- package/knowledge/decisions/security/core-secrets-use-a-per-secret-dek-wrapped-by-a-kek.md +37 -0
- package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +31 -0
- package/knowledge/decisions/security/gateway-middleware-sessions-must-be-bridged-onto-the-wire.md +30 -0
- package/knowledge/decisions/security/global-permissions-and-function-permissions-are-independent-gates.md +40 -0
- package/knowledge/decisions/security/http-error-detail-is-withheld-from-clients-in-production.md +33 -0
- package/knowledge/decisions/security/http-request-bodies-are-bounded-before-they-are-buffered.md +46 -0
- package/knowledge/decisions/security/index.md +55 -0
- package/knowledge/decisions/security/mcp-internal-error-details-are-double-gated-on-production.md +27 -0
- package/knowledge/decisions/security/passphrases-are-stretched-key-material-is-expanded.md +40 -0
- package/knowledge/decisions/security/permission-auth-filtering-requires-live-permission-functions.md +31 -0
- package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +26 -0
- package/knowledge/decisions/security/queue-job-identities-are-signed-at-enqueue.md +69 -0
- package/knowledge/decisions/security/queue-jobs-carry-the-producers-pikku-user-id.md +39 -0
- package/knowledge/decisions/security/remote-addon-tokens-are-client-credentials-not-mesh-trust.md +34 -0
- package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +49 -0
- package/knowledge/decisions/security/scenario-step-functions-are-never-externally-invocable.md +30 -0
- package/knowledge/decisions/security/scope-resolution-happens-at-the-session-boundary-and-sync-never-deletes.md +28 -0
- package/knowledge/decisions/security/self-authentication-is-declared-not-detected.md +34 -0
- package/knowledge/decisions/security/signed-content-urls-bind-the-request-path.md +37 -0
- package/knowledge/decisions/security/webhook-bodies-are-signed-before-they-are-enqueued.md +25 -0
- package/knowledge/decisions/security/workflow-actor-steps-always-use-the-real-transport.md +34 -0
- package/knowledge/decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md +40 -0
- package/knowledge/decisions/security/workflow-queued-steps-rehydrate-their-session-from-the-run-wire.md +32 -0
- package/knowledge/decisions/security/workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md +32 -0
- package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +31 -0
- package/knowledge/index.md +24 -0
- package/knowledge/questions/index.md +15 -0
- package/package.json +6 -2
- package/run-tests.sh +0 -0
- package/src/column-form.test.ts +97 -0
- package/src/column-form.ts +58 -0
- package/src/crypto-utils.test.ts +460 -19
- package/src/crypto-utils.ts +306 -59
- package/src/data-classification.ts +45 -7
- package/src/dev/hot-reload.test.ts +0 -4
- package/src/dev/hot-reload.ts +11 -30
- package/src/dev/module-runner.ts +7 -32
- package/src/dev/reload-meta.ts +9 -29
- package/src/errors/error-handler.ts +20 -35
- package/src/errors/error.test.ts +30 -1
- package/src/errors/errors.ts +73 -157
- package/src/function/abort-scope.test.ts +97 -0
- package/src/function/abort-scope.ts +80 -0
- package/src/function/function-runner.test.ts +0 -7
- package/src/function/function-runner.ts +62 -32
- package/src/function/functions.types.ts +95 -138
- package/src/function/index.ts +1 -0
- package/src/function/list.types.test.ts +3 -25
- package/src/function/list.types.ts +12 -62
- package/src/handle-error.ts +0 -18
- package/src/index.ts +84 -3
- package/src/middleware/auth-apikey.test.ts +0 -1
- package/src/middleware/auth-apikey.ts +0 -17
- package/src/middleware/auth-bearer.test.ts +3 -5
- package/src/middleware/auth-bearer.ts +5 -41
- package/src/middleware/auth-cookie.test.ts +0 -6
- package/src/middleware/auth-cookie.ts +2 -26
- package/src/middleware/cors.test.ts +34 -0
- package/src/middleware/cors.ts +12 -33
- package/src/middleware/remote-auth.test.ts +26 -10
- package/src/middleware/remote-auth.ts +11 -3
- package/src/middleware/telemetry.ts +2 -31
- package/src/middleware-runner.test.ts +0 -2
- package/src/middleware-runner.ts +5 -74
- package/src/permissions.test.ts +30 -0
- package/src/permissions.ts +24 -74
- package/src/pikku-request.ts +0 -6
- package/src/pikku-state.ts +2 -30
- package/src/production-barrels-stay-lean.test.ts +110 -0
- package/src/remote.test.ts +173 -0
- package/src/remote.ts +18 -8
- package/src/schema.ts +5 -39
- package/src/scopes.ts +7 -48
- package/src/secret-value.test.ts +204 -0
- package/src/secret-value.ts +111 -0
- package/src/services/ai-agent-runner-service.ts +20 -0
- package/src/services/ai-embedding-service.ts +3 -25
- package/src/services/audit-service.ts +88 -11
- package/src/services/content-service.ts +1 -46
- package/src/services/credential-service.ts +3 -40
- package/src/services/credential-wire-service.test.ts +0 -2
- package/src/services/credential-wire-service.ts +9 -1
- package/src/services/deployment-service.ts +3 -9
- package/src/services/email-service.ts +3 -1
- package/src/services/gateway-service.ts +0 -15
- package/src/services/{http-scenario-actors-converse.test.ts → http-personas-converse.test.ts} +38 -9
- package/src/services/{http-scenario-actors.test.ts → http-personas.test.ts} +39 -22
- package/src/services/{http-scenario-actors.ts → http-personas.ts} +85 -45
- package/src/services/in-memory-queue-service.ts +1 -15
- package/src/services/in-memory-trigger-service.ts +1 -18
- package/src/services/in-memory-workflow-service.test.ts +0 -13
- package/src/services/in-memory-workflow-service.ts +4 -38
- package/src/services/index.ts +23 -18
- package/src/services/istanbul-coverage-service.ts +2 -8
- package/src/services/jwt-service.ts +1 -16
- package/src/services/local-content-request-handler.test.ts +202 -0
- package/src/services/local-content-request-handler.ts +267 -0
- package/src/services/local-content.test.ts +159 -27
- package/src/services/local-content.ts +55 -23
- package/src/services/local-gateway-service.ts +2 -17
- package/src/services/local-secrets.test.ts +20 -5
- package/src/services/local-secrets.ts +15 -11
- package/src/services/logger-console.test.ts +0 -1
- package/src/services/logger-console.ts +3 -7
- package/src/services/logger.ts +31 -46
- package/src/services/meta-service.test.ts +1 -5
- package/src/services/meta-service.ts +41 -61
- package/src/services/{scenario-actors-service.ts → personas-service.ts} +48 -43
- package/src/services/pikku-user-id.ts +0 -4
- package/src/services/queue-webhook-service.test.ts +2 -1
- package/src/services/queue-webhook-service.ts +10 -42
- package/src/services/scheduler-service.ts +1 -50
- package/src/services/schema-service.ts +1 -24
- package/src/services/scope-service.ts +50 -34
- package/src/services/scoped-secret-service.ts +4 -7
- package/src/services/secret-host-binding.test.ts +138 -0
- package/src/services/secret-host-binding.ts +51 -0
- package/src/services/secret-service.ts +12 -35
- package/src/services/secretless.test.ts +54 -0
- package/src/services/secretless.ts +29 -0
- package/src/services/stub-tracker.ts +8 -18
- package/src/services/system-role-guard.test.ts +93 -0
- package/src/services/system-role-guard.ts +71 -0
- package/src/services/trigger-service.ts +0 -12
- package/src/services/typed-secret-service.ts +12 -14
- package/src/services/v8-coverage-service.ts +3 -6
- package/src/services/variables-service.ts +1 -8
- package/src/services/webhook-service.ts +23 -64
- package/src/services/workflow-service.ts +3 -20
- package/src/testing/service-tests.ts +6 -32
- package/src/time-utils.ts +1 -19
- package/src/types/core.types.ts +137 -229
- package/src/types/state.types.ts +7 -9
- package/src/utils/hmac.ts +4 -10
- package/src/utils/safe-fetch.ts +13 -54
- package/src/utils.test.ts +11 -2
- package/src/utils.ts +6 -15
- package/src/wirings/actor-flow/actor-flow.types.ts +1 -34
- package/src/wirings/actor-flow/index.ts +0 -9
- package/src/wirings/actor-flow/run-conversation.test.ts +11 -6
- package/src/wirings/actor-flow/run-conversation.ts +19 -12
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +91 -10
- package/src/wirings/ai-agent/ai-agent-agui.ts +49 -17
- package/src/wirings/ai-agent/ai-agent-helpers.ts +20 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +842 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +399 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +0 -2
- package/src/wirings/ai-agent/ai-agent-model-config.ts +1 -9
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +202 -31
- package/src/wirings/ai-agent/ai-agent-prepare.ts +89 -139
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +191 -6
- package/src/wirings/ai-agent/ai-agent-registry.ts +18 -1
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +0 -2
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +11 -9
- package/src/wirings/ai-agent/ai-agent-runner.ts +85 -35
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +205 -103
- package/src/wirings/ai-agent/ai-agent-stream.ts +224 -76
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +301 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +130 -5
- package/src/wirings/ai-agent/index.ts +37 -3
- package/src/wirings/ai-agent/voice-input.test.ts +137 -7
- package/src/wirings/ai-agent/voice-input.ts +96 -12
- package/src/wirings/ai-agent/voice-output.test.ts +512 -0
- package/src/wirings/ai-agent/voice-output.ts +243 -56
- package/src/wirings/channel/channel-common.ts +15 -20
- package/src/wirings/channel/channel-handler.test.ts +50 -0
- package/src/wirings/channel/channel-handler.ts +32 -11
- package/src/wirings/channel/channel-host-rpc.test.ts +150 -0
- package/src/wirings/channel/channel-host-rpc.ts +69 -0
- package/src/wirings/channel/channel-middleware-runner.test.ts +0 -1
- package/src/wirings/channel/channel-middleware-runner.ts +0 -12
- package/src/wirings/channel/channel-rpc-registry.ts +116 -0
- package/src/wirings/channel/channel-rpc-responder.ts +117 -0
- package/src/wirings/channel/channel-rpc-service.ts +146 -0
- package/src/wirings/channel/channel-rpc-validators.ts +65 -0
- package/src/wirings/channel/channel-rpc.test.ts +820 -0
- package/src/wirings/channel/channel-rpc.ts +5 -0
- package/src/wirings/channel/channel-rpc.types.ts +150 -0
- package/src/wirings/channel/channel-runner.ts +0 -14
- package/src/wirings/channel/channel-store.ts +0 -10
- package/src/wirings/channel/channel.types.ts +19 -8
- package/src/wirings/channel/define-channel-routes.ts +0 -20
- package/src/wirings/channel/eventhub-service.ts +0 -18
- package/src/wirings/channel/index.ts +35 -0
- package/src/wirings/channel/local/local-channel-handler.ts +3 -1
- package/src/wirings/channel/local/local-channel-runner.test.ts +0 -10
- package/src/wirings/channel/local/local-channel-runner.ts +3 -1
- package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -13
- package/src/wirings/channel/local/local-eventhub-service.ts +2 -37
- package/src/wirings/channel/log-channels.ts +0 -4
- package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +83 -2
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +7 -0
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -5
- package/src/wirings/cli/channel/cli-approval.test.ts +177 -0
- package/src/wirings/cli/channel/cli-approval.ts +135 -0
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -26
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +169 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +59 -16
- package/src/wirings/cli/channel/cli-raw-client-runner.test.ts +480 -0
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +185 -0
- package/src/wirings/cli/channel/index.ts +13 -0
- package/src/wirings/cli/cli-runner.test.ts +0 -1
- package/src/wirings/cli/cli-runner.ts +46 -88
- package/src/wirings/cli/cli.types.ts +14 -3
- package/src/wirings/cli/command-parser.test.ts +0 -4
- package/src/wirings/cli/command-parser.ts +11 -91
- package/src/wirings/cli/define-cli-commands.ts +1 -17
- package/src/wirings/credential/credential.types.ts +0 -12
- package/src/wirings/credential/{wire-credential.ts → define-credential.ts} +7 -7
- package/src/wirings/credential/index.ts +1 -1
- package/src/wirings/credential/validate-credential-definitions.ts +2 -4
- package/src/wirings/gateway/gateway-runner.test.ts +1 -21
- package/src/wirings/gateway/gateway-runner.ts +8 -110
- package/src/wirings/gateway/gateway.types.ts +7 -80
- package/src/wirings/http/http-routes.test.ts +0 -3
- package/src/wirings/http/http-routes.ts +0 -86
- package/src/wirings/http/http-runner.test.ts +0 -1
- package/src/wirings/http/http-runner.ts +8 -167
- package/src/wirings/http/http.types.ts +15 -62
- package/src/wirings/http/log-http-routes.ts +0 -4
- package/src/wirings/http/pikku-fetch-http-request.test.ts +2 -10
- package/src/wirings/http/pikku-fetch-http-request.ts +0 -58
- package/src/wirings/http/pikku-fetch-http-response.test.ts +1 -1
- package/src/wirings/http/pikku-fetch-http-response.ts +0 -3
- package/src/wirings/http/routers/path-to-regex.test.ts +4 -17
- package/src/wirings/http/routers/path-to-regex.ts +2 -13
- package/src/wirings/http/web-request.test.ts +33 -2
- package/src/wirings/http/web-request.ts +30 -17
- package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -1
- package/src/wirings/mcp/mcp-runner.ts +2 -17
- package/src/wirings/mcp/mcp.types.ts +7 -42
- package/src/wirings/oauth2/oauth2.types.ts +0 -30
- package/src/wirings/persona/define-personas.ts +33 -0
- package/src/wirings/persona/index.ts +62 -0
- package/src/wirings/persona/persona-email.ts +87 -0
- package/src/wirings/persona/persona-environments.test.ts +183 -0
- package/src/wirings/persona/persona-environments.ts +138 -0
- package/src/wirings/persona/persona-mailbox.ts +156 -0
- package/src/wirings/persona/persona.test.ts +220 -0
- package/src/wirings/persona/persona.types.ts +142 -0
- package/src/wirings/persona/validate-personas.ts +133 -0
- package/src/wirings/queue/index.ts +13 -3
- package/src/wirings/queue/queue-identity.test.ts +454 -0
- package/src/wirings/queue/queue-identity.ts +176 -0
- package/src/wirings/queue/queue-runner.ts +12 -31
- package/src/wirings/queue/queue.types.ts +25 -90
- package/src/wirings/queue/register-queue-helper.ts +0 -14
- package/src/wirings/queue/signed-queue-service.ts +60 -0
- package/src/wirings/queue/validate-worker-config.ts +2 -28
- package/src/wirings/role/define-system-role.ts +33 -0
- package/src/wirings/role/index.ts +13 -0
- package/src/wirings/role/role.test.ts +104 -0
- package/src/wirings/role/role.types.ts +47 -0
- package/src/wirings/role/validate-role-definitions.ts +93 -0
- package/src/wirings/rpc/addon-auth-tags.test.ts +223 -0
- package/src/wirings/rpc/addon-runner.ts +0 -56
- package/src/wirings/rpc/addon-scopes.test.ts +225 -0
- package/src/wirings/rpc/remote-addon-auth.ts +9 -16
- package/src/wirings/rpc/rpc-runner.test.ts +192 -6
- package/src/wirings/rpc/rpc-runner.ts +145 -127
- package/src/wirings/rpc/rpc-types.ts +11 -6
- package/src/wirings/rpc/wire-addon.test.ts +43 -1
- package/src/wirings/rpc/wire-addon.ts +99 -0
- package/src/wirings/rpc/wire-remote-addon.ts +9 -29
- package/src/wirings/scheduler/log-schedulers.ts +0 -4
- package/src/wirings/scheduler/scheduler-runner.test.ts +1 -8
- package/src/wirings/scheduler/scheduler-runner.ts +0 -2
- package/src/wirings/scheduler/scheduler.types.ts +1 -14
- package/src/wirings/scope/{wire-scope.ts → define-scope.ts} +5 -6
- package/src/wirings/scope/index.ts +1 -1
- package/src/wirings/scope/scope.test.ts +1 -2
- package/src/wirings/scope/scope.types.ts +7 -9
- package/src/wirings/scope/validate-scope-definitions.ts +3 -21
- package/src/wirings/secret/index.ts +1 -1
- package/src/wirings/secret/secret.types.ts +19 -15
- package/src/wirings/secret/validate-secret-definitions.ts +2 -4
- package/src/wirings/trigger/trigger-runner.ts +1 -27
- package/src/wirings/trigger/trigger.types.ts +1 -82
- package/src/wirings/variable/index.ts +1 -1
- package/src/wirings/variable/validate-variable-definitions.ts +2 -4
- package/src/wirings/variable/variable.types.ts +1 -13
- package/src/wirings/virtual-user/index.ts +88 -0
- package/src/wirings/virtual-user/prepare-virtual-user-run.test.ts +115 -0
- package/src/wirings/virtual-user/prepare-virtual-user-run.ts +95 -0
- package/src/wirings/virtual-user/run-virtual-user.test.ts +765 -0
- package/src/wirings/virtual-user/run-virtual-user.ts +671 -0
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +65 -0
- package/src/wirings/virtual-user/virtual-user-agents.ts +57 -0
- package/src/wirings/virtual-user/virtual-user-catalogue.test.ts +215 -0
- package/src/wirings/virtual-user/virtual-user-catalogue.ts +184 -0
- package/src/wirings/virtual-user/virtual-user-derive.test.ts +398 -0
- package/src/wirings/virtual-user/virtual-user-derive.ts +173 -0
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +63 -0
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +213 -0
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +208 -0
- package/src/wirings/virtual-user/virtual-user-intents.ts +185 -0
- package/src/wirings/virtual-user/virtual-user-rng.test.ts +72 -0
- package/src/wirings/virtual-user/virtual-user-rng.ts +50 -0
- package/src/wirings/virtual-user/virtual-user-run-store.ts +98 -0
- package/src/wirings/virtual-user/virtual-user-target.ts +47 -0
- package/src/wirings/virtual-user/virtual-user.types.ts +219 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +19 -20
- package/src/wirings/workflow/dsl/workflow-runner.ts +0 -4
- package/src/wirings/workflow/feature.ts +0 -19
- package/src/wirings/workflow/graph/graph-node.ts +0 -136
- package/src/wirings/workflow/graph/graph-runner.test.ts +20 -19
- package/src/wirings/workflow/graph/graph-runner.ts +6 -41
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -4
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -58
- package/src/wirings/workflow/index.ts +10 -44
- package/src/wirings/workflow/pikku-scenario-service.ts +235 -61
- package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -39
- package/src/wirings/workflow/pikku-workflow-service.ts +77 -674
- package/src/wirings/workflow/run-timeline.test.ts +7 -19
- package/src/wirings/workflow/run-timeline.ts +0 -56
- package/src/wirings/workflow/scenario-cookie-jar.test.ts +0 -1
- package/src/wirings/workflow/scenario-cookie-jar.ts +0 -25
- package/src/wirings/workflow/scenario-expectations.test.ts +2 -7
- package/src/wirings/workflow/scenario-hooks.test.ts +2 -7
- package/src/wirings/workflow/scenario-poll.test.ts +0 -2
- package/src/wirings/workflow/scenario-poll.ts +0 -15
- package/src/wirings/workflow/scenario-prose.test.ts +5 -7
- package/src/wirings/workflow/scenario-prose.ts +1 -29
- package/src/wirings/workflow/scenario-service.test.ts +2 -10
- package/src/wirings/workflow/scenario-step-guards.ts +1 -14
- package/src/wirings/workflow/scenario-step.test.ts +163 -19
- package/src/wirings/workflow/scenario-step.types.ts +94 -12
- package/src/wirings/workflow/scenario-surface.test.ts +146 -0
- package/src/wirings/workflow/scenario-surface.ts +71 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +14 -15
- package/src/wirings/workflow/workflow-dispatch-payload.test.ts +0 -4
- package/src/wirings/workflow/workflow-inline-authority.test.ts +169 -0
- package/src/wirings/workflow/workflow-invocation-id.test.ts +0 -2
- package/src/wirings/workflow/workflow-invocation-id.ts +2 -22
- package/src/wirings/workflow/workflow-mirror.test.ts +0 -7
- package/src/wirings/workflow/workflow-on-error.test.ts +0 -9
- package/src/wirings/workflow/workflow-queue-workers.ts +0 -21
- package/src/wirings/workflow/workflow-replay-snapshot.test.ts +8 -7
- package/src/wirings/workflow/workflow-retry-policy.test.ts +0 -5
- package/src/wirings/workflow/workflow-run-context.test.ts +5 -10
- package/src/wirings/workflow/workflow-run-polling.test.ts +0 -5
- package/src/wirings/workflow/workflow-step-ordinal.test.ts +19 -4
- package/src/wirings/workflow/workflow-step-session.test.ts +0 -7
- package/src/wirings/workflow/workflow.types.ts +5 -201
- package/tsconfig.tsbuildinfo +1 -1
- package/tsconfig.type-tests.json +12 -0
- package/src/middleware/timeout.ts +0 -22
- package/src/pikku-response.ts +0 -5
- package/src/wirings/mcp/mcp-endpoint-registry.test.d.ts +0 -1
- package/src/wirings/workflow/dsl/index.ts +0 -36
- package/src/wirings/workflow/graph/index.ts +0 -15
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
runFromMeta,
|
|
71
71
|
} from './graph/graph-runner.js'
|
|
72
72
|
import type { WorkflowService } from '../../services/workflow-service.js'
|
|
73
|
-
import type {
|
|
73
|
+
import type { ScenarioPersonas } from '../../services/personas-service.js'
|
|
74
74
|
import {
|
|
75
75
|
PikkuError,
|
|
76
76
|
addError,
|
|
@@ -92,18 +92,8 @@ import type {
|
|
|
92
92
|
PikkuWorkerConfig,
|
|
93
93
|
} from '../queue/queue.types.js'
|
|
94
94
|
|
|
95
|
-
/**
|
|
96
|
-
* Default number of retries for a workflow step when none is specified. The
|
|
97
|
-
* workflow — not the queue — owns retry policy; a step inherits this unless it
|
|
98
|
-
* sets its own `retries` (including `retries: 0` to opt out entirely). Picked >0
|
|
99
|
-
* so a transient failure (a DB blip, a downstream restart, a deploy) is ridden
|
|
100
|
-
* out by default; safe because every step gets a stable `invocationId` to dedupe on.
|
|
101
|
-
*/
|
|
102
95
|
export const DEFAULT_STEP_RETRIES = 5
|
|
103
96
|
|
|
104
|
-
/**
|
|
105
|
-
* Exception thrown when workflow needs to pause for async step
|
|
106
|
-
*/
|
|
107
97
|
export class WorkflowAsyncException extends Error {
|
|
108
98
|
constructor(
|
|
109
99
|
public readonly runId: string,
|
|
@@ -114,9 +104,6 @@ export class WorkflowAsyncException extends Error {
|
|
|
114
104
|
}
|
|
115
105
|
}
|
|
116
106
|
|
|
117
|
-
/**
|
|
118
|
-
* Exception thrown when workflow is cancelled
|
|
119
|
-
*/
|
|
120
107
|
export class WorkflowCancelledException extends Error {
|
|
121
108
|
constructor(
|
|
122
109
|
public readonly runId: string,
|
|
@@ -127,9 +114,6 @@ export class WorkflowCancelledException extends Error {
|
|
|
127
114
|
}
|
|
128
115
|
}
|
|
129
116
|
|
|
130
|
-
/**
|
|
131
|
-
* Exception thrown when workflow is suspended
|
|
132
|
-
*/
|
|
133
117
|
export class WorkflowSuspendedException extends Error {
|
|
134
118
|
constructor(
|
|
135
119
|
public readonly runId: string,
|
|
@@ -140,14 +124,6 @@ export class WorkflowSuspendedException extends Error {
|
|
|
140
124
|
}
|
|
141
125
|
}
|
|
142
126
|
|
|
143
|
-
/**
|
|
144
|
-
* Thrown when a step (or the orchestrator) could not be enqueued — the queue
|
|
145
|
-
* itself failed (e.g. pg-boss is momentarily down), NOT the step's own logic.
|
|
146
|
-
* This is transient infrastructure failure: the run is left untouched (the step
|
|
147
|
-
* stays `pending`, the run stays running) and the orchestrator job is rethrown
|
|
148
|
-
* so the queue redelivers it and the workflow replays from its snapshot. Treat
|
|
149
|
-
* it as non-terminal — never mark the run `failed` for it.
|
|
150
|
-
*/
|
|
151
127
|
export class WorkflowDispatchException extends Error {
|
|
152
128
|
constructor(
|
|
153
129
|
public readonly runId: string,
|
|
@@ -162,9 +138,6 @@ export class WorkflowDispatchException extends Error {
|
|
|
162
138
|
}
|
|
163
139
|
}
|
|
164
140
|
|
|
165
|
-
/**
|
|
166
|
-
* Error class for workflow not found
|
|
167
|
-
*/
|
|
168
141
|
export class WorkflowNotFoundError extends PikkuError {
|
|
169
142
|
constructor(name: string) {
|
|
170
143
|
super(`Workflow not found: ${name}`)
|
|
@@ -207,12 +180,6 @@ addError(WorkflowRunCancelledError, {
|
|
|
207
180
|
message: 'Workflow was cancelled.',
|
|
208
181
|
})
|
|
209
182
|
|
|
210
|
-
/**
|
|
211
|
-
* A decision arrived for an approval gate that has already resolved. The gate
|
|
212
|
-
* caches its outcome as the step result and never re-reads run state, so the
|
|
213
|
-
* decision could not take effect — it is rejected rather than accepted and
|
|
214
|
-
* dropped.
|
|
215
|
-
*/
|
|
216
183
|
export class WorkflowApprovalResolvedError extends PikkuError {
|
|
217
184
|
public payload: {
|
|
218
185
|
reason: string
|
|
@@ -242,11 +209,6 @@ const WORKFLOW_END_STATES: ReadonlySet<string> = new Set([
|
|
|
242
209
|
'suspended',
|
|
243
210
|
])
|
|
244
211
|
|
|
245
|
-
/**
|
|
246
|
-
* Everything an extension needs to run something of its own around a run's
|
|
247
|
-
* function: the registration it was resolved from, the run itself (so `input`
|
|
248
|
-
* is the same object the function is called with), and the wire it is given.
|
|
249
|
-
*/
|
|
250
212
|
export interface RunLifecycleContext {
|
|
251
213
|
runId: string
|
|
252
214
|
run: WorkflowRun
|
|
@@ -256,12 +218,6 @@ export interface RunLifecycleContext {
|
|
|
256
218
|
packageName: string | null
|
|
257
219
|
}
|
|
258
220
|
|
|
259
|
-
/**
|
|
260
|
-
* The slice of the run engine an extension is allowed to drive, handed to it at
|
|
261
|
-
* construction. It exists so that recording a durable step stays available to
|
|
262
|
-
* an extension without `inlineStep` and friends becoming public API on every
|
|
263
|
-
* workflow service a production app instantiates.
|
|
264
|
-
*/
|
|
265
221
|
export interface WorkflowRunEngine {
|
|
266
222
|
inlineStep(
|
|
267
223
|
runId: string,
|
|
@@ -281,25 +237,13 @@ export interface WorkflowRunEngine {
|
|
|
281
237
|
verifyStepName(stepName: unknown): void
|
|
282
238
|
}
|
|
283
239
|
|
|
284
|
-
/**
|
|
285
|
-
* A capability layered onto a run without being a workflow service itself.
|
|
286
|
-
*
|
|
287
|
-
* The engine names nothing about what an extension is for: a bundler drops an
|
|
288
|
-
* unused *module* but never an unused class member, so the alternative — a
|
|
289
|
-
* subclass carrying the capability — puts it in every server built on Pikku
|
|
290
|
-
* whether or not the app ever uses it. Scenarios are the one implementation
|
|
291
|
-
* today (`PikkuScenarioService` in `@pikku/core/scenario`).
|
|
292
|
-
*/
|
|
293
240
|
export interface WorkflowRunExtension {
|
|
294
|
-
/** Per-run state resolved once the run has an id. */
|
|
295
241
|
attachRunContext(
|
|
296
242
|
runId: string,
|
|
297
243
|
workflowMeta: any,
|
|
298
244
|
options?: Record<string, any>
|
|
299
245
|
): Promise<void>
|
|
300
|
-
/** Release whatever `attachRunContext` stored. */
|
|
301
246
|
detachRunContext(runId: string): void
|
|
302
|
-
/** Contribute extra members to the wire a run's function is invoked with. */
|
|
303
247
|
decorateRunWire(
|
|
304
248
|
wire: PikkuWire,
|
|
305
249
|
context: {
|
|
@@ -308,7 +252,6 @@ export interface WorkflowRunExtension {
|
|
|
308
252
|
workflowWire: PikkuWorkflowWire
|
|
309
253
|
}
|
|
310
254
|
): void
|
|
311
|
-
/** Contribute extra members to the workflow wire itself, in place. */
|
|
312
255
|
decorateWorkflowWire(
|
|
313
256
|
workflowWire: PikkuWorkflowWire,
|
|
314
257
|
context: {
|
|
@@ -318,18 +261,7 @@ export interface WorkflowRunExtension {
|
|
|
318
261
|
addonNamespace?: string | null
|
|
319
262
|
}
|
|
320
263
|
): void
|
|
321
|
-
/**
|
|
322
|
-
* Called immediately before the run's function, inside the run lock. Throwing
|
|
323
|
-
* skips the function and fails the run.
|
|
324
|
-
*/
|
|
325
264
|
onBeforeRunFunc(context: RunLifecycleContext): Promise<void>
|
|
326
|
-
/**
|
|
327
|
-
* Called once the run reaches a terminal state, in a `finally`, so it runs
|
|
328
|
-
* whether the function passed or threw. `interrupted` means the run suspended
|
|
329
|
-
* or went async and is still mid-flight. Throwing here propagates, replacing
|
|
330
|
-
* a pending failure, so an extension that must not mask one handles that
|
|
331
|
-
* itself.
|
|
332
|
-
*/
|
|
333
265
|
onAfterRunFunc(
|
|
334
266
|
context: RunLifecycleContext,
|
|
335
267
|
outcome: 'completed' | 'failed' | 'interrupted',
|
|
@@ -337,54 +269,25 @@ export interface WorkflowRunExtension {
|
|
|
337
269
|
): Promise<void>
|
|
338
270
|
}
|
|
339
271
|
|
|
340
|
-
/**
|
|
341
|
-
* States a run never leaves. `suspended` is deliberately absent: a suspended
|
|
342
|
-
* run stops a poll loop but can still be resumed, so anything the process holds
|
|
343
|
-
* for it has to survive.
|
|
344
|
-
*/
|
|
345
272
|
const WORKFLOW_TERMINAL_STATES: ReadonlySet<string> = new Set([
|
|
346
273
|
'completed',
|
|
347
274
|
'failed',
|
|
348
275
|
'cancelled',
|
|
349
276
|
])
|
|
350
277
|
|
|
351
|
-
/** First wait when polling a run, before the backoff starts widening it. */
|
|
352
278
|
const WORKFLOW_POLL_MIN_MS = 10
|
|
353
279
|
|
|
354
|
-
/** How much each successive wait grows, up to the caller's ceiling. */
|
|
355
280
|
const WORKFLOW_POLL_FACTOR = 1.6
|
|
356
281
|
|
|
357
|
-
/**
|
|
358
|
-
* Ceiling for the wait on an inline sub-workflow. Lower than a top-level run's
|
|
359
|
-
* default, because the parent step is blocked on it and every wait here is
|
|
360
|
-
* added latency in the middle of a workflow rather than at its edge.
|
|
361
|
-
*/
|
|
362
282
|
const WORKFLOW_CHILD_POLL_MAX_MS = 500
|
|
363
283
|
|
|
364
|
-
/**
|
|
365
|
-
* Abstract workflow state service
|
|
366
|
-
* Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
|
|
367
|
-
* Combines orchestration and step execution
|
|
368
|
-
*/
|
|
369
|
-
/**
|
|
370
|
-
* Everything the engine holds in memory for a run that is executing in this
|
|
371
|
-
* process. One entry, one lifetime: created when the run starts executing here
|
|
372
|
-
* and dropped when nothing is holding it open any more.
|
|
373
|
-
*
|
|
374
|
-
* The `replay` half is rebuilt from scratch on every orchestrator tick; the
|
|
375
|
-
* rest outlives individual ticks and belongs to whoever started the run.
|
|
376
|
-
*/
|
|
377
284
|
type RunContext = {
|
|
378
|
-
|
|
379
|
-
inline
|
|
285
|
+
activeExecutions: number
|
|
286
|
+
inline?: boolean
|
|
287
|
+
ordinals: Map<string, number>
|
|
288
|
+
lastStep?: string
|
|
380
289
|
replay?: {
|
|
381
|
-
/** How many times this walk has reached each logical step name. */
|
|
382
|
-
ordinals: Map<string, number>
|
|
383
|
-
/** The step key the walk last reached — the next step's predecessor. */
|
|
384
|
-
lastStep?: string
|
|
385
|
-
/** Every step of the run as this replay found it, keyed by step name. */
|
|
386
290
|
steps?: Map<string, StepState>
|
|
387
|
-
/** The run as this replay found it. Only its immutable half is reused. */
|
|
388
291
|
run?: WorkflowRun
|
|
389
292
|
}
|
|
390
293
|
}
|
|
@@ -397,20 +300,39 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
397
300
|
private contextFor(runId: string): RunContext {
|
|
398
301
|
let context = this.runContexts.get(runId)
|
|
399
302
|
if (!context) {
|
|
400
|
-
context = {
|
|
303
|
+
context = { activeExecutions: 0, ordinals: new Map() }
|
|
401
304
|
this.runContexts.set(runId, context)
|
|
402
305
|
}
|
|
403
306
|
return context
|
|
404
307
|
}
|
|
405
308
|
|
|
406
|
-
/** Drop a run's context once nothing is holding it open. */
|
|
407
309
|
private releaseContext(runId: string): void {
|
|
408
310
|
const context = this.runContexts.get(runId)
|
|
409
311
|
if (!context) return
|
|
410
|
-
if (context.
|
|
312
|
+
if (context.activeExecutions > 0) return
|
|
411
313
|
this.runContexts.delete(runId)
|
|
412
314
|
}
|
|
413
315
|
|
|
316
|
+
private enterExecution(runId: string): RunContext {
|
|
317
|
+
const context = this.contextFor(runId)
|
|
318
|
+
context.activeExecutions++
|
|
319
|
+
return context
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private exitExecution(runId: string): void {
|
|
323
|
+
const context = this.runContexts.get(runId)
|
|
324
|
+
if (!context) return
|
|
325
|
+
if (context.activeExecutions > 0) {
|
|
326
|
+
context.activeExecutions--
|
|
327
|
+
}
|
|
328
|
+
if (context.activeExecutions === 0) {
|
|
329
|
+
context.replay = undefined
|
|
330
|
+
context.ordinals = new Map()
|
|
331
|
+
context.lastStep = undefined
|
|
332
|
+
}
|
|
333
|
+
this.releaseContext(runId)
|
|
334
|
+
}
|
|
335
|
+
|
|
414
336
|
protected get logger() {
|
|
415
337
|
return getSingletonServices()?.logger
|
|
416
338
|
}
|
|
@@ -437,17 +359,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
437
359
|
}
|
|
438
360
|
}
|
|
439
361
|
|
|
440
|
-
/**
|
|
441
|
-
* Perform a state write, then shadow it to the mirror.
|
|
442
|
-
*
|
|
443
|
-
* The mirror is an observability sink, never a second source of truth, and
|
|
444
|
-
* both halves of that follow from this one shape: it is only ever told about
|
|
445
|
-
* a write that already landed, and a mirror that is down or throwing cannot
|
|
446
|
-
* fail — or even be seen by — the workflow it is watching.
|
|
447
|
-
*
|
|
448
|
-
* @param write - the authoritative write; its result is what the caller gets
|
|
449
|
-
* @param mirror - shadows the write, given the live mirror and what was written
|
|
450
|
-
*/
|
|
451
362
|
private async mirrored<T>(
|
|
452
363
|
write: () => Promise<T>,
|
|
453
364
|
mirror: (mirror: WorkflowRunMirror, written: T) => Promise<void>
|
|
@@ -462,24 +373,17 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
462
373
|
`[pikku] WorkflowRunMirror write failed: ${err?.message ?? err}`
|
|
463
374
|
)
|
|
464
375
|
} catch {
|
|
465
|
-
//
|
|
376
|
+
// knowledge: decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md
|
|
466
377
|
}
|
|
467
378
|
}
|
|
468
379
|
}
|
|
469
380
|
return written
|
|
470
381
|
}
|
|
471
382
|
|
|
472
|
-
/**
|
|
473
|
-
* Wire the queue-based orchestrator/step/sleeper workers.
|
|
474
|
-
* Subclasses that orchestrate without queues (e.g. Durable Objects) should
|
|
475
|
-
* pass `wireQueues: false` to the base constructor and skip this entirely.
|
|
476
|
-
* Call this explicitly after adding addons dynamically.
|
|
477
|
-
*/
|
|
478
383
|
public wireQueueWorkers(): void {
|
|
479
384
|
const functions = pikkuState(null, 'function', 'functions')
|
|
480
385
|
const functionsMeta = pikkuState(null, 'function', 'meta')
|
|
481
386
|
|
|
482
|
-
// Minimal meta for internal workflow functions (satisfies FunctionMeta)
|
|
483
387
|
const mkMeta = (funcId: string) => ({
|
|
484
388
|
pikkuFuncId: funcId,
|
|
485
389
|
sessionless: true,
|
|
@@ -507,10 +411,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
507
411
|
}
|
|
508
412
|
}
|
|
509
413
|
|
|
510
|
-
// Under 'shared-groups' every workflow runs through these two queues and is
|
|
511
|
-
// kept from hogging them by the per-group cap, so the per-workflow queues
|
|
512
|
-
// below are left unconsumed — one set of pollers for the whole system
|
|
513
|
-
// instead of one per workflow.
|
|
514
414
|
const sharedGroups = this.queueStrategy === 'shared-groups'
|
|
515
415
|
const sharedQueueConfig: PikkuWorkerConfig | undefined = sharedGroups
|
|
516
416
|
? {
|
|
@@ -519,7 +419,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
519
419
|
}
|
|
520
420
|
: undefined
|
|
521
421
|
|
|
522
|
-
// Register shared queue workers for monolith deployments
|
|
523
422
|
registerWorkflowFunc(
|
|
524
423
|
'pikkuWorkflowOrchestrator',
|
|
525
424
|
{ func: pikkuWorkflowOrchestratorFunc },
|
|
@@ -533,7 +432,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
533
432
|
sharedQueueConfig
|
|
534
433
|
)
|
|
535
434
|
|
|
536
|
-
// Register per-workflow queue workers (root + addon packages)
|
|
537
435
|
const registerQueueWorkers = (queueMeta: Record<string, any>) => {
|
|
538
436
|
for (const [queueName, meta] of Object.entries(queueMeta)) {
|
|
539
437
|
if (functions.has(meta.pikkuFuncId)) continue
|
|
@@ -567,13 +465,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
567
465
|
}
|
|
568
466
|
}
|
|
569
467
|
|
|
570
|
-
// Workflows exist but no per-workflow orchestrator queue was registered:
|
|
571
|
-
// the generated queue meta never reached the runtime (most often the
|
|
572
|
-
// bootstrap doesn't import the queue-workers meta, so `queue.meta` is
|
|
573
|
-
// empty). Everything still "works" — dispatch silently falls back to the
|
|
574
|
-
// single shared orchestrator queue — but the isolation is gone: one slow
|
|
575
|
-
// workflow step head-of-line-blocks every other workflow behind it. That
|
|
576
|
-
// is invisible until a queue starves, so say so loudly at wiring time.
|
|
577
468
|
const workflowCount = Object.keys(
|
|
578
469
|
pikkuState(null, 'workflows', 'meta') ?? {}
|
|
579
470
|
).length
|
|
@@ -598,27 +489,26 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
598
489
|
}
|
|
599
490
|
}
|
|
600
491
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
492
|
+
protected async isInline(runId: string): Promise<boolean> {
|
|
493
|
+
const context = this.runContexts.get(runId)
|
|
494
|
+
if (context?.inline !== undefined) {
|
|
495
|
+
return context.inline
|
|
496
|
+
}
|
|
497
|
+
const inline = (await this.getRunIdentity(runId))?.inline === true
|
|
498
|
+
if (context) {
|
|
499
|
+
context.inline = inline
|
|
500
|
+
}
|
|
501
|
+
return inline
|
|
606
502
|
}
|
|
607
503
|
|
|
608
|
-
/**
|
|
609
|
-
* Register a run as inline (for graph-runner to use)
|
|
610
|
-
*/
|
|
611
504
|
public registerInlineRun(runId: string): void {
|
|
612
505
|
this.contextFor(runId).inline = true
|
|
613
506
|
}
|
|
614
507
|
|
|
615
|
-
/**
|
|
616
|
-
* Unregister a run from inline tracking
|
|
617
|
-
*/
|
|
618
508
|
public unregisterInlineRun(runId: string): void {
|
|
619
509
|
const context = this.runContexts.get(runId)
|
|
620
510
|
if (!context) return
|
|
621
|
-
context.inline =
|
|
511
|
+
context.inline = undefined
|
|
622
512
|
this.releaseContext(runId)
|
|
623
513
|
}
|
|
624
514
|
|
|
@@ -676,17 +566,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
676
566
|
}
|
|
677
567
|
): Promise<string>
|
|
678
568
|
|
|
679
|
-
/**
|
|
680
|
-
* Get a workflow run by ID
|
|
681
|
-
* @param id - Run ID
|
|
682
|
-
* @returns Workflow run or null if not found
|
|
683
|
-
*/
|
|
684
569
|
abstract getRun(id: string): Promise<WorkflowRun | null>
|
|
685
570
|
|
|
686
|
-
/**
|
|
687
|
-
* Get minimal workflow run status with step summaries.
|
|
688
|
-
* Used by the public API — the console addon provides the full verbose view.
|
|
689
|
-
*/
|
|
690
571
|
async getRunStatus(id: string): Promise<WorkflowRunStatus | null> {
|
|
691
572
|
const run = await this.getRun(id)
|
|
692
573
|
if (!run) return null
|
|
@@ -694,7 +575,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
694
575
|
const history = await this.getRunHistory(id)
|
|
695
576
|
const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
|
|
696
577
|
|
|
697
|
-
// Build step summaries from history (latest attempt per step)
|
|
698
578
|
const stepMap = new Map<
|
|
699
579
|
string,
|
|
700
580
|
{
|
|
@@ -741,24 +621,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
741
621
|
}
|
|
742
622
|
}
|
|
743
623
|
|
|
744
|
-
/**
|
|
745
|
-
* Build the run's time-travel event stream from durable history.
|
|
746
|
-
* @param id - Run ID
|
|
747
|
-
* @returns Ordered timeline, or null if the run doesn't exist
|
|
748
|
-
*/
|
|
749
624
|
public async getRunTimeline(id: string): Promise<RunTimeline | null> {
|
|
750
625
|
const run = await this.getRun(id)
|
|
751
626
|
if (!run) return null
|
|
752
627
|
return buildRunTimeline(await this.getRunHistory(id))
|
|
753
628
|
}
|
|
754
629
|
|
|
755
|
-
/**
|
|
756
|
-
* Reconstruct the run's state at a point in its timeline.
|
|
757
|
-
* @param id - Run ID
|
|
758
|
-
* @param at - A seq index (inclusive) or a Date (inclusive); omit for the
|
|
759
|
-
* final state.
|
|
760
|
-
* @returns Reconstructed state, or null if the run doesn't exist
|
|
761
|
-
*/
|
|
762
630
|
public async reconstructRunStateAt(
|
|
763
631
|
id: string,
|
|
764
632
|
at?: number | Date
|
|
@@ -768,20 +636,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
768
636
|
return reconstructStateAt(timeline, at ?? timeline.length - 1)
|
|
769
637
|
}
|
|
770
638
|
|
|
771
|
-
/**
|
|
772
|
-
* Get workflow run history (all step attempts in chronological order)
|
|
773
|
-
* @param runId - Run ID
|
|
774
|
-
* @returns Array of step states with step names, ordered oldest to newest
|
|
775
|
-
*/
|
|
776
639
|
abstract getRunHistory(
|
|
777
640
|
runId: string
|
|
778
641
|
): Promise<Array<StepState & { stepName: string }>>
|
|
779
642
|
|
|
780
|
-
/**
|
|
781
|
-
* Update workflow run status
|
|
782
|
-
* @param id - Run ID
|
|
783
|
-
* @param status - New status
|
|
784
|
-
*/
|
|
785
643
|
public async updateRunStatus(
|
|
786
644
|
id: string,
|
|
787
645
|
status: WorkflowStatus,
|
|
@@ -793,9 +651,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
793
651
|
(mirror) => mirror.updateRunStatus(id, status, output, error)
|
|
794
652
|
)
|
|
795
653
|
if (WORKFLOW_TERMINAL_STATES.has(status)) {
|
|
796
|
-
// The run is over: release whatever this process opened for it. Queued
|
|
797
|
-
// runs never pass through the inline path that does this, so their
|
|
798
|
-
// context was held for the life of the process.
|
|
799
654
|
this.runExtension?.detachRunContext(id)
|
|
800
655
|
this.releaseContext(id)
|
|
801
656
|
}
|
|
@@ -808,19 +663,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
808
663
|
error?: SerializedError
|
|
809
664
|
): Promise<void>
|
|
810
665
|
|
|
811
|
-
/**
|
|
812
|
-
* Insert initial step state (called by orchestrator)
|
|
813
|
-
* Creates pending step in both workflow_step and workflow_step_history
|
|
814
|
-
* @param runId - Run ID
|
|
815
|
-
* @param stepName - Step cache key
|
|
816
|
-
* @param rpcName - The name this step was dispatched by: an RPC for a
|
|
817
|
-
* `workflow.do` step, a step function for a scenario step, null for a
|
|
818
|
-
* closure. Nothing dispatches off this value — it is recorded so a reader
|
|
819
|
-
* can join a step back to the function that ran it.
|
|
820
|
-
* @param data - Step input data
|
|
821
|
-
* @param stepOptions - Step options (retries, retryDelay)
|
|
822
|
-
* @returns Step state with generated stepId
|
|
823
|
-
*/
|
|
824
666
|
public async insertStepState(
|
|
825
667
|
runId: string,
|
|
826
668
|
stepName: string,
|
|
@@ -853,19 +695,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
853
695
|
fromStepName?: string
|
|
854
696
|
): Promise<StepState>
|
|
855
697
|
|
|
856
|
-
/**
|
|
857
|
-
* Get step state by cache key (read-only)
|
|
858
|
-
* @param runId - Run ID
|
|
859
|
-
* @param stepName - Step cache key (from workflow.do)
|
|
860
|
-
* @returns Step state with attemptCount calculated from history
|
|
861
|
-
*/
|
|
862
698
|
abstract getStepState(runId: string, stepName: string): Promise<StepState>
|
|
863
699
|
|
|
864
|
-
/**
|
|
865
|
-
* Mark step as running
|
|
866
|
-
* Updates both workflow_step and workflow_step_history
|
|
867
|
-
* @param stepId - Step ID
|
|
868
|
-
*/
|
|
869
700
|
public async setStepRunning(stepId: string): Promise<void> {
|
|
870
701
|
await this.mirrored(
|
|
871
702
|
() => this.setStepRunningImpl(stepId),
|
|
@@ -875,11 +706,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
875
706
|
|
|
876
707
|
protected abstract setStepRunningImpl(stepId: string): Promise<void>
|
|
877
708
|
|
|
878
|
-
/**
|
|
879
|
-
* Mark step as scheduled (queued for execution)
|
|
880
|
-
* Updates both workflow_step and workflow_step_history
|
|
881
|
-
* @param stepId - Step ID
|
|
882
|
-
*/
|
|
883
709
|
public async setStepScheduled(stepId: string): Promise<void> {
|
|
884
710
|
await this.mirrored(
|
|
885
711
|
() => this.setStepScheduledImpl(stepId),
|
|
@@ -889,12 +715,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
889
715
|
|
|
890
716
|
protected abstract setStepScheduledImpl(stepId: string): Promise<void>
|
|
891
717
|
|
|
892
|
-
/**
|
|
893
|
-
* Store step result and mark as succeeded
|
|
894
|
-
* Updates both workflow_step and workflow_step_history
|
|
895
|
-
* @param stepId - Step ID
|
|
896
|
-
* @param result - Step result
|
|
897
|
-
*/
|
|
898
718
|
public async setStepResult(stepId: string, result: any): Promise<void> {
|
|
899
719
|
await this.mirrored(
|
|
900
720
|
() => this.setStepResultImpl(stepId, result),
|
|
@@ -907,11 +727,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
907
727
|
result: any
|
|
908
728
|
): Promise<void>
|
|
909
729
|
|
|
910
|
-
/**
|
|
911
|
-
* Set the child workflow run ID on a step
|
|
912
|
-
* @param stepId - Step ID
|
|
913
|
-
* @param childRunId - Child workflow run ID
|
|
914
|
-
*/
|
|
915
730
|
public async setStepChildRunId(
|
|
916
731
|
stepId: string,
|
|
917
732
|
childRunId: string
|
|
@@ -927,12 +742,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
927
742
|
childRunId: string
|
|
928
743
|
): Promise<void>
|
|
929
744
|
|
|
930
|
-
/**
|
|
931
|
-
* Store step error and mark as failed
|
|
932
|
-
* Updates both workflow_step and workflow_step_history
|
|
933
|
-
* @param stepId - Step ID
|
|
934
|
-
* @param error - Error object
|
|
935
|
-
*/
|
|
936
745
|
public async setStepError(stepId: string, error: Error): Promise<void> {
|
|
937
746
|
await this.mirrored(
|
|
938
747
|
() => this.setStepErrorImpl(stepId, error),
|
|
@@ -953,14 +762,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
953
762
|
error: Error
|
|
954
763
|
): Promise<void>
|
|
955
764
|
|
|
956
|
-
/**
|
|
957
|
-
* Create a new retry attempt for a failed step
|
|
958
|
-
* Inserts new pending step in both workflow_step and workflow_step_history
|
|
959
|
-
* Resets status to 'pending' with new stepId
|
|
960
|
-
* Copies metadata (rpcName, data, retries, retryDelay) from failed attempt
|
|
961
|
-
* @param failedStepId - Failed step ID to copy from
|
|
962
|
-
* @returns New step state for the retry attempt
|
|
963
|
-
*/
|
|
964
765
|
public async createRetryAttempt(
|
|
965
766
|
failedStepId: string,
|
|
966
767
|
status: 'pending' | 'running'
|
|
@@ -980,64 +781,22 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
980
781
|
status: 'pending' | 'running'
|
|
981
782
|
): Promise<StepState>
|
|
982
783
|
|
|
983
|
-
/**
|
|
984
|
-
* Execute function within a run lock to prevent concurrent modifications
|
|
985
|
-
* @param id - Run ID
|
|
986
|
-
* @param fn - Function to execute
|
|
987
|
-
* @returns Function result
|
|
988
|
-
*/
|
|
989
784
|
abstract withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>
|
|
990
785
|
|
|
991
|
-
/**
|
|
992
|
-
* Execute function within a step lock to prevent concurrent step execution
|
|
993
|
-
* @param runId - Run ID
|
|
994
|
-
* @param stepName - Step name
|
|
995
|
-
* @param fn - Function to execute
|
|
996
|
-
* @returns Function result
|
|
997
|
-
*/
|
|
998
786
|
abstract withStepLock<T>(
|
|
999
787
|
runId: string,
|
|
1000
788
|
stepName: string,
|
|
1001
789
|
fn: () => Promise<T>
|
|
1002
790
|
): Promise<T>
|
|
1003
791
|
|
|
1004
|
-
/**
|
|
1005
|
-
* Close any open connections
|
|
1006
|
-
*/
|
|
1007
792
|
abstract close(): Promise<void>
|
|
1008
793
|
|
|
1009
|
-
// ============================================================================
|
|
1010
|
-
// Workflow Graph Methods
|
|
1011
|
-
// ============================================================================
|
|
1012
|
-
|
|
1013
|
-
/**
|
|
1014
|
-
* Get completed graph state (lightweight - no results)
|
|
1015
|
-
* @param runId - Run ID
|
|
1016
|
-
* @returns Completed node IDs and their branch keys
|
|
1017
|
-
*/
|
|
1018
794
|
abstract getCompletedGraphState(runId: string): Promise<{
|
|
1019
795
|
completedNodeIds: string[]
|
|
1020
796
|
failedNodeIds: string[]
|
|
1021
797
|
branchKeys: Record<string, string>
|
|
1022
798
|
}>
|
|
1023
799
|
|
|
1024
|
-
/**
|
|
1025
|
-
* Filter candidate nodes to only those without existing steps
|
|
1026
|
-
* @param runId - Run ID
|
|
1027
|
-
* @param nodeIds - Candidate node IDs to check
|
|
1028
|
-
* @returns Node IDs that don't have a step yet
|
|
1029
|
-
*/
|
|
1030
|
-
abstract getNodesWithoutSteps(
|
|
1031
|
-
runId: string,
|
|
1032
|
-
nodeIds: string[]
|
|
1033
|
-
): Promise<string[]>
|
|
1034
|
-
|
|
1035
|
-
/**
|
|
1036
|
-
* List every step instance of a run (any status) with its predecessor.
|
|
1037
|
-
* Drives bounded graph revisits: the runner counts instances per logical node
|
|
1038
|
-
* and treats each `fromStepName → node` as a once-fired transition.
|
|
1039
|
-
* @param runId - Run ID
|
|
1040
|
-
*/
|
|
1041
800
|
abstract getStepInstances(runId: string): Promise<
|
|
1042
801
|
Array<{
|
|
1043
802
|
stepName: string
|
|
@@ -1046,22 +805,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1046
805
|
}>
|
|
1047
806
|
>
|
|
1048
807
|
|
|
1049
|
-
/**
|
|
1050
|
-
* Get results for specific nodes
|
|
1051
|
-
* @param runId - Run ID
|
|
1052
|
-
* @param nodeIds - Node IDs to fetch results for
|
|
1053
|
-
* @returns Map of nodeId to result
|
|
1054
|
-
*/
|
|
1055
808
|
abstract getNodeResults(
|
|
1056
809
|
runId: string,
|
|
1057
810
|
nodeIds: string[]
|
|
1058
811
|
): Promise<Record<string, any>>
|
|
1059
812
|
|
|
1060
|
-
/**
|
|
1061
|
-
* Set the branch key for a graph node step
|
|
1062
|
-
* @param stepId - Step ID
|
|
1063
|
-
* @param branchKey - Branch key selected by graph.branch()
|
|
1064
|
-
*/
|
|
1065
813
|
public async setBranchTaken(
|
|
1066
814
|
stepId: string,
|
|
1067
815
|
branchKey: string
|
|
@@ -1077,12 +825,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1077
825
|
branchKey: string
|
|
1078
826
|
): Promise<void>
|
|
1079
827
|
|
|
1080
|
-
/**
|
|
1081
|
-
* Update a state variable in the workflow run's state
|
|
1082
|
-
* @param runId - Run ID
|
|
1083
|
-
* @param name - Variable name
|
|
1084
|
-
* @param value - Value to store
|
|
1085
|
-
*/
|
|
1086
828
|
public async updateRunState(
|
|
1087
829
|
runId: string,
|
|
1088
830
|
name: string,
|
|
@@ -1100,11 +842,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1100
842
|
value: unknown
|
|
1101
843
|
): Promise<void>
|
|
1102
844
|
|
|
1103
|
-
/**
|
|
1104
|
-
* Get the entire state object for a workflow run
|
|
1105
|
-
* @param runId - Run ID
|
|
1106
|
-
* @returns The state object with all variables
|
|
1107
|
-
*/
|
|
1108
845
|
abstract getRunState(runId: string): Promise<Record<string, unknown>>
|
|
1109
846
|
|
|
1110
847
|
public async upsertWorkflowVersion(
|
|
@@ -1152,14 +889,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1152
889
|
graphHash: string
|
|
1153
890
|
): Promise<{ graph: any; source: string } | null>
|
|
1154
891
|
|
|
1155
|
-
// ============================================================================
|
|
1156
|
-
// Workflow Lifecycle Methods
|
|
1157
|
-
// ============================================================================
|
|
1158
|
-
|
|
1159
|
-
/**
|
|
1160
|
-
* Resume a paused workflow by triggering the orchestrator
|
|
1161
|
-
* @param runId - Run ID
|
|
1162
|
-
*/
|
|
1163
892
|
public async resumeWorkflow(
|
|
1164
893
|
runId: string,
|
|
1165
894
|
workflowName?: string
|
|
@@ -1169,12 +898,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1169
898
|
const run = await this.getRun(runId)
|
|
1170
899
|
workflowName = run?.workflow
|
|
1171
900
|
}
|
|
1172
|
-
// Carry an explicit retry policy on the orchestrator job too. Orchestrator
|
|
1173
|
-
// runs are idempotent (they replay from the snapshot, returning cached step
|
|
1174
|
-
// results), so redelivery is always safe — and it's what lets a transient
|
|
1175
|
-
// dispatch/infra failure recover: the job is rethrown and retried instead of
|
|
1176
|
-
// the run hanging. Passing `attempts` per-job overrides the queue default, so
|
|
1177
|
-
// this holds even when the orchestrator queue is configured `retry_limit 0`.
|
|
1178
901
|
await queueService.add(
|
|
1179
902
|
this.getOrchestratorQueueName(workflowName),
|
|
1180
903
|
{ runId },
|
|
@@ -1185,22 +908,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1185
908
|
)
|
|
1186
909
|
}
|
|
1187
910
|
|
|
1188
|
-
/**
|
|
1189
|
-
* Resolve a step's retry policy into queue job options. The workflow is the
|
|
1190
|
-
* sole source of truth for retries: an explicitly-set `retries` (including 0)
|
|
1191
|
-
* is always honored, an unset one defaults to {@link DEFAULT_STEP_RETRIES},
|
|
1192
|
-
* and we ALWAYS pass `attempts` so the queue can never fall back to its own
|
|
1193
|
-
* default — which would re-run a step the workflow said not to retry. Backoff
|
|
1194
|
-
* defaults to exponential whenever there's at least one retry, so retries ride
|
|
1195
|
-
* out a transient outage instead of firing instantly.
|
|
1196
|
-
*/
|
|
1197
911
|
protected resolveStepJobOptions(
|
|
1198
912
|
stepOptions?: WorkflowStepOptions
|
|
1199
913
|
): JobOptions {
|
|
1200
914
|
const retries = stepOptions?.retries ?? DEFAULT_STEP_RETRIES
|
|
1201
915
|
const retryDelay = stepOptions?.retryDelay
|
|
1202
|
-
// A concrete retryDelay (15000, '15s') is a fixed backoff; only the literal
|
|
1203
|
-
// 'exponential' — or no delay at all — selects exponential.
|
|
1204
916
|
const backoff =
|
|
1205
917
|
retryDelay !== undefined && retryDelay !== 'exponential'
|
|
1206
918
|
? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
|
|
@@ -1224,18 +936,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1224
936
|
{ runId, stepName, rpcName, data, fromStepName },
|
|
1225
937
|
{
|
|
1226
938
|
...this.resolveStepJobOptions(stepOptions),
|
|
1227
|
-
// Group by step function, mirroring how per-step queues split them —
|
|
1228
|
-
// one slow step function can't monopolise the shared step worker.
|
|
1229
939
|
group: this.getJobGroup(rpcName),
|
|
1230
940
|
}
|
|
1231
941
|
)
|
|
1232
942
|
}
|
|
1233
943
|
|
|
1234
|
-
/**
|
|
1235
|
-
* Execute a workflow sleep step completion
|
|
1236
|
-
* Sets the step result to null and resumes the workflow
|
|
1237
|
-
* @param data - Sleeper input data
|
|
1238
|
-
*/
|
|
1239
944
|
public async executeWorkflowSleepCompleted(
|
|
1240
945
|
runId: string,
|
|
1241
946
|
stepId: string
|
|
@@ -1244,11 +949,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1244
949
|
await this.resumeWorkflow(runId)
|
|
1245
950
|
}
|
|
1246
951
|
|
|
1247
|
-
/**
|
|
1248
|
-
* Schedule orchestrator retry with delay
|
|
1249
|
-
* @param runId - Run ID
|
|
1250
|
-
* @param retryDelay - Delay in milliseconds or duration string (optional)
|
|
1251
|
-
*/
|
|
1252
952
|
protected async scheduleOrchestratorRetry(
|
|
1253
953
|
runId: string,
|
|
1254
954
|
retryDelay?: number | string,
|
|
@@ -1271,20 +971,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1271
971
|
)
|
|
1272
972
|
}
|
|
1273
973
|
|
|
1274
|
-
/**
|
|
1275
|
-
* Dispatch a workflow step to be executed asynchronously.
|
|
1276
|
-
*
|
|
1277
|
-
* Default implementation enqueues a step worker job via the queue service.
|
|
1278
|
-
* Subclasses with non-queue transports (e.g. Durable Objects) override this
|
|
1279
|
-
* to dispatch via their own mechanism (RPC to a step worker, etc.).
|
|
1280
|
-
*
|
|
1281
|
-
* On return, the workflow is paused via `WorkflowAsyncException` thrown by
|
|
1282
|
-
* the caller; the step transport is responsible for calling back into the
|
|
1283
|
-
* orchestrator (via `resumeWorkflow` or equivalent) when the step completes.
|
|
1284
|
-
*
|
|
1285
|
-
* @returns true if dispatch was async (caller should pause), false to fall
|
|
1286
|
-
* through to the inline execution path.
|
|
1287
|
-
*/
|
|
1288
974
|
protected async dispatchStep(
|
|
1289
975
|
runId: string,
|
|
1290
976
|
stepName: string,
|
|
@@ -1293,10 +979,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1293
979
|
stepOptions?: WorkflowStepOptions,
|
|
1294
980
|
fromStepName?: string
|
|
1295
981
|
): Promise<boolean> {
|
|
1296
|
-
// Step execution is decided purely by the function's `workflowQueued` flag
|
|
1297
|
-
// (default false). Only a function explicitly marked `workflowQueued: true`
|
|
1298
|
-
// dispatches via the queue. If the queue service is not configured that is
|
|
1299
|
-
// a hard error — there is no inline fallback.
|
|
1300
982
|
const functionsMeta = pikkuState(null, 'function', 'meta')
|
|
1301
983
|
const rpcFuncId = pikkuState(null, 'rpc', 'meta')[rpcName]
|
|
1302
984
|
const rpcMeta =
|
|
@@ -1320,31 +1002,20 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1320
1002
|
}
|
|
1321
1003
|
)
|
|
1322
1004
|
} catch (cause) {
|
|
1323
|
-
// The queue is down/unreachable — NOT a step failure. Surface it as a
|
|
1324
|
-
// transient dispatch error so the caller leaves the step `pending` and the
|
|
1325
|
-
// orchestrator job is retried (replayed from snapshot) rather than the run
|
|
1326
|
-
// being marked failed. `add` already throws on failure in every adapter.
|
|
1327
1005
|
throw new WorkflowDispatchException(runId, stepName, { cause })
|
|
1328
1006
|
}
|
|
1329
1007
|
return true
|
|
1330
1008
|
}
|
|
1331
1009
|
|
|
1332
|
-
/**
|
|
1333
|
-
* Schedule a workflow sleep wakeup at the given duration.
|
|
1334
|
-
*
|
|
1335
|
-
* Default implementation uses the scheduler service to enqueue a delayed
|
|
1336
|
-
* sleeper RPC. Subclasses with native timer primitives (e.g. Durable Object
|
|
1337
|
-
* alarms) override this to schedule directly without going through queues.
|
|
1338
|
-
*
|
|
1339
|
-
* @returns true if the wakeup was scheduled remotely (caller should pause),
|
|
1340
|
-
* false to fall through to inline `setTimeout` behavior.
|
|
1341
|
-
*/
|
|
1342
1010
|
protected async scheduleSleep(
|
|
1343
1011
|
runId: string,
|
|
1344
1012
|
stepId: string,
|
|
1345
1013
|
duration: number | string
|
|
1346
1014
|
): Promise<boolean> {
|
|
1347
|
-
if (
|
|
1015
|
+
if (
|
|
1016
|
+
(await this.isInline(runId)) ||
|
|
1017
|
+
!getSingletonServices()?.schedulerService
|
|
1018
|
+
) {
|
|
1348
1019
|
return false
|
|
1349
1020
|
}
|
|
1350
1021
|
await getSingletonServices()!.schedulerService!.scheduleRPC(
|
|
@@ -1355,12 +1026,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1355
1026
|
return true
|
|
1356
1027
|
}
|
|
1357
1028
|
|
|
1358
|
-
/**
|
|
1359
|
-
* Install the one extension a run may have, built from a handle onto the run
|
|
1360
|
-
* engine so that `inlineStep` and friends stay protected rather than becoming
|
|
1361
|
-
* public API. Returns the extension, so the caller keeps a typed reference to
|
|
1362
|
-
* whatever it just built.
|
|
1363
|
-
*/
|
|
1364
1029
|
public setRunExtension<T extends WorkflowRunExtension>(
|
|
1365
1030
|
create: (engine: WorkflowRunEngine) => T
|
|
1366
1031
|
): T {
|
|
@@ -1379,15 +1044,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1379
1044
|
return this.runExtension
|
|
1380
1045
|
}
|
|
1381
1046
|
|
|
1382
|
-
/**
|
|
1383
|
-
* Start a new workflow run
|
|
1384
|
-
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
1385
|
-
* @param options.inline - If true, execute workflow directly without queue service
|
|
1386
|
-
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
1387
|
-
* @param options.onRunCreated - Called with the run id the moment the run exists.
|
|
1388
|
-
* An inline run that fails throws instead of returning, so this is the only
|
|
1389
|
-
* way a caller can still read that run back — its steps, and which one failed.
|
|
1390
|
-
*/
|
|
1391
1047
|
public async startWorkflow<I>(
|
|
1392
1048
|
name: string,
|
|
1393
1049
|
input: I,
|
|
@@ -1396,11 +1052,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1396
1052
|
options?: {
|
|
1397
1053
|
inline?: boolean
|
|
1398
1054
|
startNode?: string
|
|
1399
|
-
actors?:
|
|
1055
|
+
actors?: ScenarioPersonas
|
|
1400
1056
|
onRunCreated?: (runId: string) => void
|
|
1401
1057
|
}
|
|
1402
1058
|
): Promise<{ runId: string }> {
|
|
1403
|
-
// Resolve workflow from static meta (root or addon namespace)
|
|
1404
1059
|
const resolved = resolveWorkflowMeta(name)
|
|
1405
1060
|
const workflowMeta = resolved?.meta
|
|
1406
1061
|
const packageName = resolved?.packageName ?? null
|
|
@@ -1424,7 +1079,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1424
1079
|
)
|
|
1425
1080
|
}
|
|
1426
1081
|
|
|
1427
|
-
// DSL workflow - check registration exists
|
|
1428
1082
|
const registrations = pikkuState(packageName, 'workflows', 'registrations')
|
|
1429
1083
|
const workflow = registrations.get(resolved?.resolvedName ?? name)
|
|
1430
1084
|
|
|
@@ -1464,7 +1118,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1464
1118
|
error.name !== 'WorkflowAsyncException' &&
|
|
1465
1119
|
error.name !== 'WorkflowCancelledException' &&
|
|
1466
1120
|
error.name !== 'WorkflowSuspendedException' &&
|
|
1467
|
-
// Transient queue failure — leave the run resumable, don't fail it.
|
|
1468
1121
|
error.name !== 'WorkflowDispatchException'
|
|
1469
1122
|
) {
|
|
1470
1123
|
await this.updateRunStatus(runId, 'failed', undefined, {
|
|
@@ -1472,11 +1125,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1472
1125
|
message: error.message,
|
|
1473
1126
|
stack: error.stack,
|
|
1474
1127
|
})
|
|
1475
|
-
// An expected failure (a PikkuError, e.g. a build gate tripping) —
|
|
1476
|
-
// its message is the whole story, so don't dump the stack. The
|
|
1477
|
-
// `expected` flag survives the step-boundary rehydration that strips
|
|
1478
|
-
// the class. Anything else is an uncaught/unexpected error: log it in
|
|
1479
|
-
// full so the trace is there to debug.
|
|
1480
1128
|
getSingletonServices()!.logger.error(
|
|
1481
1129
|
`Workflow ${name} (run ${runId}) failed:`,
|
|
1482
1130
|
isExpectedError(error) ? error.message : error
|
|
@@ -1484,8 +1132,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1484
1132
|
throw error
|
|
1485
1133
|
}
|
|
1486
1134
|
if (error.name === 'WorkflowDispatchException') {
|
|
1487
|
-
// Rethrow so the caller (poll loop / starter) sees the transient
|
|
1488
|
-
// failure; the run stays running and can be resumed.
|
|
1489
1135
|
throw error
|
|
1490
1136
|
}
|
|
1491
1137
|
} finally {
|
|
@@ -1522,15 +1168,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1522
1168
|
return run.output
|
|
1523
1169
|
}
|
|
1524
1170
|
|
|
1525
|
-
/**
|
|
1526
|
-
* Read a run until it reaches an end state, backing off as it drags on.
|
|
1527
|
-
*
|
|
1528
|
-
* A fixed interval is wrong at both ends: it makes a workflow that finished
|
|
1529
|
-
* in milliseconds wait out the whole interval anyway, and it keeps reading a
|
|
1530
|
-
* long-running one at full rate for as long as it lasts. Starting short and
|
|
1531
|
-
* growing to `maxIntervalMs` returns quick runs promptly while a slow run's
|
|
1532
|
-
* read cost grows logarithmically rather than linearly with its duration.
|
|
1533
|
-
*/
|
|
1534
1171
|
protected async awaitRunEnd(
|
|
1535
1172
|
runId: string,
|
|
1536
1173
|
maxIntervalMs: number
|
|
@@ -1549,41 +1186,21 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1549
1186
|
}
|
|
1550
1187
|
}
|
|
1551
1188
|
|
|
1552
|
-
/**
|
|
1553
|
-
* Wait between two reads of a run.
|
|
1554
|
-
*
|
|
1555
|
-
* Its own method so the backoff schedule can be asserted on directly. Timing
|
|
1556
|
-
* a poll loop by the clock measures the host's scheduler as much as the
|
|
1557
|
-
* policy — `setTimeout(40)` routinely returns late on a loaded runner — which
|
|
1558
|
-
* makes the obvious test both slow and flaky.
|
|
1559
|
-
*/
|
|
1560
1189
|
protected async waitBeforeNextRead(ms: number): Promise<void> {
|
|
1561
1190
|
await new Promise((resolve) => setTimeout(resolve, ms))
|
|
1562
1191
|
}
|
|
1563
1192
|
|
|
1564
|
-
/**
|
|
1565
|
-
* Every step of a run in one read, or `null` if this backend has no bulk read.
|
|
1566
|
-
*
|
|
1567
|
-
* A replay walks the DSL body from the top, and each step it passes asks for
|
|
1568
|
-
* its own row — so a run of N steps costs N reads per replay and O(N^2) over
|
|
1569
|
-
* its lifetime. Backends that can answer this in a single query collapse that
|
|
1570
|
-
* to one read per replay.
|
|
1571
|
-
*/
|
|
1572
1193
|
protected async listStepStates(
|
|
1573
1194
|
_runId: string
|
|
1574
1195
|
): Promise<Array<StepState & { stepName: string }> | null> {
|
|
1575
1196
|
return null
|
|
1576
1197
|
}
|
|
1577
1198
|
|
|
1578
|
-
/**
|
|
1579
|
-
* Begin a replay pass: fresh ordinal counters, and one read of the steps the
|
|
1580
|
-
* run has already taken so the walk back to where it left off is served from
|
|
1581
|
-
* memory. Safe because a pass reaches each step key at most once, and the
|
|
1582
|
-
* steps it replays past are `succeeded` and therefore immutable.
|
|
1583
|
-
*/
|
|
1584
1199
|
private async beginReplay(runId: string): Promise<void> {
|
|
1585
|
-
const context = this.
|
|
1586
|
-
context.
|
|
1200
|
+
const context = this.enterExecution(runId)
|
|
1201
|
+
context.ordinals = new Map()
|
|
1202
|
+
context.lastStep = undefined
|
|
1203
|
+
context.replay = {}
|
|
1587
1204
|
const steps = await this.listStepStates(runId)
|
|
1588
1205
|
if (steps) {
|
|
1589
1206
|
context.replay.steps = new Map(steps.map((step) => [step.stepName, step]))
|
|
@@ -1591,16 +1208,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1591
1208
|
}
|
|
1592
1209
|
|
|
1593
1210
|
private endReplay(runId: string): void {
|
|
1594
|
-
|
|
1595
|
-
if (!context) return
|
|
1596
|
-
context.replay = undefined
|
|
1597
|
-
this.releaseContext(runId)
|
|
1211
|
+
this.exitExecution(runId)
|
|
1598
1212
|
}
|
|
1599
1213
|
|
|
1600
|
-
/**
|
|
1601
|
-
* The step row for `stepName`, creating it if the run has not reached it
|
|
1602
|
-
* before. Served from the replay snapshot when one is loaded.
|
|
1603
|
-
*/
|
|
1604
1214
|
private async loadOrCreateStep(
|
|
1605
1215
|
runId: string,
|
|
1606
1216
|
stepName: string,
|
|
@@ -1616,7 +1226,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1616
1226
|
try {
|
|
1617
1227
|
return await this.getStepState(runId, stepName)
|
|
1618
1228
|
} catch {
|
|
1619
|
-
//
|
|
1229
|
+
// knowledge: decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md
|
|
1620
1230
|
}
|
|
1621
1231
|
}
|
|
1622
1232
|
|
|
@@ -1624,9 +1234,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1624
1234
|
try {
|
|
1625
1235
|
step = await create()
|
|
1626
1236
|
} catch (error) {
|
|
1627
|
-
// A concurrent replay of this run created the row after the snapshot was
|
|
1628
|
-
// taken. Its state is the truth; if it isn't really there, the insert
|
|
1629
|
-
// failed for its own reasons and that error is the one worth seeing.
|
|
1630
1237
|
try {
|
|
1631
1238
|
step = await this.getStepState(runId, stepName)
|
|
1632
1239
|
} catch {
|
|
@@ -1637,15 +1244,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1637
1244
|
return step
|
|
1638
1245
|
}
|
|
1639
1246
|
|
|
1640
|
-
/**
|
|
1641
|
-
* The run's immutable half — which workflow it is, the wire it was started
|
|
1642
|
-
* on, its input. `getRun` is otherwise called several times per step for
|
|
1643
|
-
* answers that were all fixed at creation, so a replay reads it once and
|
|
1644
|
-
* hands the same object to everyone who only needs that half.
|
|
1645
|
-
*
|
|
1646
|
-
* Anyone who needs `status`, `output`, `error` or `state` must call `getRun`:
|
|
1647
|
-
* those move while the run executes, and a cached copy would be a lie.
|
|
1648
|
-
*/
|
|
1649
1247
|
private async getRunIdentity(runId: string): Promise<WorkflowRun | null> {
|
|
1650
1248
|
const replay = this.runContexts.get(runId)?.replay
|
|
1651
1249
|
if (replay?.run) {
|
|
@@ -1658,33 +1256,21 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1658
1256
|
return run
|
|
1659
1257
|
}
|
|
1660
1258
|
|
|
1661
|
-
/** The step the DSL walk last reached (the predecessor for the next step). */
|
|
1662
1259
|
private lastStepName(runId: string): string | undefined {
|
|
1663
|
-
return this.runContexts.get(runId)?.
|
|
1260
|
+
return this.runContexts.get(runId)?.lastStep
|
|
1664
1261
|
}
|
|
1665
1262
|
|
|
1666
|
-
/**
|
|
1667
|
-
* Physical, replay-stable key for the Nth reach of `logicalStepName` in a run:
|
|
1668
|
-
* bare name for the first reach (ordinal 0, unchanged behavior), `name#N` for
|
|
1669
|
-
* repeats — so the same literal step name can be invoked multiple times without
|
|
1670
|
-
* the rows clobbering. Deterministic given a deterministic DSL body.
|
|
1671
|
-
*/
|
|
1672
1263
|
private nextStepKey(runId: string, logicalStepName: string): string {
|
|
1673
1264
|
const context = this.contextFor(runId)
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
})
|
|
1677
|
-
const ordinal = replay.ordinals.get(logicalStepName) ?? 0
|
|
1678
|
-
replay.ordinals.set(logicalStepName, ordinal + 1)
|
|
1265
|
+
const ordinal = context.ordinals.get(logicalStepName) ?? 0
|
|
1266
|
+
context.ordinals.set(logicalStepName, ordinal + 1)
|
|
1679
1267
|
const stepName =
|
|
1680
1268
|
ordinal === 0 ? logicalStepName : `${logicalStepName}#${ordinal}`
|
|
1681
|
-
|
|
1269
|
+
context.lastStep = stepName
|
|
1682
1270
|
return stepName
|
|
1683
1271
|
}
|
|
1684
1272
|
|
|
1685
1273
|
public async runWorkflowJob(runId: string, rpcService: any): Promise<void> {
|
|
1686
|
-
// Fresh ordinal counters per replay so step keys are deterministic, and one
|
|
1687
|
-
// read of the steps the run has already taken.
|
|
1688
1274
|
await this.beginReplay(runId)
|
|
1689
1275
|
try {
|
|
1690
1276
|
await this.runWorkflowJobInner(runId, rpcService)
|
|
@@ -1697,8 +1283,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1697
1283
|
runId: string,
|
|
1698
1284
|
rpcService: any
|
|
1699
1285
|
): Promise<void> {
|
|
1700
|
-
// Caches the run for the rest of this replay, so the steps it walks don't
|
|
1701
|
-
// each re-read the workflow name and wire it already has.
|
|
1702
1286
|
const run = await this.getRunIdentity(runId)
|
|
1703
1287
|
if (!run) {
|
|
1704
1288
|
throw new WorkflowRunNotFoundError(runId)
|
|
@@ -1772,9 +1356,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1772
1356
|
packageName: pkgName,
|
|
1773
1357
|
}
|
|
1774
1358
|
|
|
1775
|
-
// `interrupted` means the run has not reached a terminal state — it is
|
|
1776
|
-
// suspended or waiting — so teardown would run while the run is still
|
|
1777
|
-
// mid-flight.
|
|
1778
1359
|
let outcome: 'completed' | 'failed' | 'interrupted' = 'completed'
|
|
1779
1360
|
let failure: any
|
|
1780
1361
|
try {
|
|
@@ -1896,10 +1477,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1896
1477
|
await runFromMeta(this, run.id, version.graph, rpcService)
|
|
1897
1478
|
}
|
|
1898
1479
|
|
|
1899
|
-
/**
|
|
1900
|
-
* Execute a single workflow step (called by worker)
|
|
1901
|
-
* Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
|
|
1902
|
-
*/
|
|
1903
1480
|
public async executeWorkflowStep(
|
|
1904
1481
|
runId: string,
|
|
1905
1482
|
stepName: string,
|
|
@@ -1907,19 +1484,32 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1907
1484
|
data: any,
|
|
1908
1485
|
rpcService: any
|
|
1909
1486
|
): Promise<void> {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1487
|
+
this.enterExecution(runId)
|
|
1488
|
+
try {
|
|
1489
|
+
await this.executeWorkflowStepInner(
|
|
1490
|
+
runId,
|
|
1491
|
+
stepName,
|
|
1492
|
+
rpcName,
|
|
1493
|
+
data,
|
|
1494
|
+
rpcService
|
|
1495
|
+
)
|
|
1496
|
+
} finally {
|
|
1497
|
+
this.exitExecution(runId)
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
private async executeWorkflowStepInner(
|
|
1502
|
+
runId: string,
|
|
1503
|
+
stepName: string,
|
|
1504
|
+
rpcName: string,
|
|
1505
|
+
data: any,
|
|
1506
|
+
rpcService: any
|
|
1507
|
+
): Promise<void> {
|
|
1916
1508
|
const claimed = await this.withStepLock(runId, stepName, async () => {
|
|
1917
1509
|
const stepState = await this.getStepState(runId, stepName)
|
|
1918
|
-
// Already succeeded, or already claimed by another worker — nothing to do.
|
|
1919
1510
|
if (stepState.status === 'succeeded' || stepState.status === 'running') {
|
|
1920
1511
|
return null
|
|
1921
1512
|
}
|
|
1922
|
-
// A 'failed' status means this is a retry — start a fresh 'running' attempt.
|
|
1923
1513
|
if (stepState.status === 'failed') {
|
|
1924
1514
|
return this.createRetryAttempt(stepState.stepId, 'running')
|
|
1925
1515
|
}
|
|
@@ -1929,7 +1519,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1929
1519
|
return stepState
|
|
1930
1520
|
})
|
|
1931
1521
|
|
|
1932
|
-
// Nothing to execute: already succeeded, or another worker owns this step.
|
|
1933
1522
|
if (!claimed) {
|
|
1934
1523
|
return
|
|
1935
1524
|
}
|
|
@@ -1947,8 +1536,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1947
1536
|
const workflowMeta = meta[run.workflow]
|
|
1948
1537
|
|
|
1949
1538
|
const isGraphWorkflow = workflowMeta?.source === 'graph'
|
|
1950
|
-
// Map the physical step key back to its logical node: a revisit instance
|
|
1951
|
-
// is `node#N` (ordinal), which isn't a literal key in `nodes`.
|
|
1952
1539
|
let graphNodeId: string | undefined
|
|
1953
1540
|
if (isGraphWorkflow && workflowMeta?.nodes) {
|
|
1954
1541
|
if (stepName in workflowMeta.nodes) {
|
|
@@ -1971,7 +1558,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1971
1558
|
run.workflow
|
|
1972
1559
|
)
|
|
1973
1560
|
} else {
|
|
1974
|
-
// Check if rpcName refers to a sub-workflow
|
|
1975
1561
|
const subWorkflowMeta = meta[rpcName]
|
|
1976
1562
|
if (subWorkflowMeta) {
|
|
1977
1563
|
const childWire: WorkflowRunWire = {
|
|
@@ -2019,10 +1605,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2019
1605
|
}
|
|
2020
1606
|
}
|
|
2021
1607
|
|
|
2022
|
-
// Store result and mark succeeded
|
|
2023
1608
|
await this.setStepResult(stepState.stepId, result)
|
|
2024
1609
|
|
|
2025
|
-
// Resume orchestrator to continue workflow
|
|
2026
1610
|
await this.resumeWorkflow(runId)
|
|
2027
1611
|
} catch (error: any) {
|
|
2028
1612
|
if (error instanceof ChildWorkflowStartedException) {
|
|
@@ -2041,32 +1625,24 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2041
1625
|
return
|
|
2042
1626
|
}
|
|
2043
1627
|
|
|
2044
|
-
// Store error and mark failed
|
|
2045
1628
|
await this.setStepError(stepState.stepId, error)
|
|
2046
1629
|
|
|
2047
1630
|
const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1
|
|
2048
1631
|
const retriesExhausted = stepState.attemptCount >= maxAttempts
|
|
2049
1632
|
|
|
2050
1633
|
if (retriesExhausted) {
|
|
2051
|
-
// No more retries - resume orchestrator to mark workflow as failed
|
|
2052
1634
|
await this.resumeWorkflow(runId)
|
|
2053
1635
|
}
|
|
2054
1636
|
|
|
2055
|
-
// Always throw so queue knows the job failed and can retry if needed
|
|
2056
1637
|
throw error
|
|
2057
1638
|
}
|
|
2058
1639
|
}
|
|
2059
1640
|
|
|
2060
|
-
/**
|
|
2061
|
-
* Orchestrate workflow execution (called by orchestrator)
|
|
2062
|
-
* Runs workflow job and handles async exceptions
|
|
2063
|
-
*/
|
|
2064
1641
|
public async orchestrateWorkflow(
|
|
2065
1642
|
runId: string,
|
|
2066
1643
|
rpcService: any
|
|
2067
1644
|
): Promise<void> {
|
|
2068
1645
|
try {
|
|
2069
|
-
// Run workflow job (replays with caching)
|
|
2070
1646
|
await this.runWorkflowJob(runId, rpcService)
|
|
2071
1647
|
} catch (error: any) {
|
|
2072
1648
|
if (
|
|
@@ -2078,9 +1654,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2078
1654
|
}
|
|
2079
1655
|
|
|
2080
1656
|
if (error.name === 'WorkflowDispatchException') {
|
|
2081
|
-
// Transient: the queue was unreachable, not a workflow failure. Leave the
|
|
2082
|
-
// run running and rethrow so the orchestrator job is redelivered and the
|
|
2083
|
-
// workflow replays from its snapshot. Do NOT mark the run failed.
|
|
2084
1657
|
getSingletonServices()!.logger.warn(
|
|
2085
1658
|
`Workflow run ${runId} could not dispatch a step (queue unavailable); leaving run for orchestrator retry`,
|
|
2086
1659
|
error
|
|
@@ -2108,11 +1681,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2108
1681
|
return getSingletonServices()!.queueService!
|
|
2109
1682
|
}
|
|
2110
1683
|
|
|
2111
|
-
/**
|
|
2112
|
-
* Invoke a step's RPC with the workflow-step wire (step identity + provenance).
|
|
2113
|
-
* Identical for the queue executor and the inline executor — the only thing
|
|
2114
|
-
* that differs between transports is who calls it, not the call itself.
|
|
2115
|
-
*/
|
|
2116
1684
|
private async invokeStepRpc(
|
|
2117
1685
|
runId: string,
|
|
2118
1686
|
stepName: string,
|
|
@@ -2122,8 +1690,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2122
1690
|
rpcService: any,
|
|
2123
1691
|
knownRun?: WorkflowRun | null
|
|
2124
1692
|
): Promise<any> {
|
|
2125
|
-
// Carry the run's pikkuUserId onto the step wire so authed steps rehydrate their
|
|
2126
|
-
// session on the queued path too (the bare job wire lacks it; inline already has it).
|
|
2127
1693
|
const run = knownRun ?? (await this.getRunIdentity(runId))
|
|
2128
1694
|
return rpcService.rpcWithWire(rpcName, data, {
|
|
2129
1695
|
...(run?.wire?.pikkuUserId ? { pikkuUserId: run.wire.pikkuUserId } : {}),
|
|
@@ -2139,16 +1705,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2139
1705
|
})
|
|
2140
1706
|
}
|
|
2141
1707
|
|
|
2142
|
-
/**
|
|
2143
|
-
* Inline (straight-through) step execution with an in-process retry loop —
|
|
2144
|
-
* shared by inline RPC steps and inline function steps. Same scaffolding
|
|
2145
|
-
* (running → result, or fail → retry-attempt → backoff → retry) wrapped
|
|
2146
|
-
* around a step-specific `doWork` body. Stays O(K): no suspend/replay.
|
|
2147
|
-
*
|
|
2148
|
-
* `onError` is an optional hook for terminal errors that must NOT retry
|
|
2149
|
-
* (e.g. RPC-not-found → suspend the run for redeploy). If it throws, the
|
|
2150
|
-
* loop exits immediately without recording a step error or retrying.
|
|
2151
|
-
*/
|
|
2152
1708
|
private async runInlineRetryLoop(
|
|
2153
1709
|
stepState: StepState,
|
|
2154
1710
|
retries: number,
|
|
@@ -2166,11 +1722,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2166
1722
|
} catch (error: any) {
|
|
2167
1723
|
if (onError) await onError(error)
|
|
2168
1724
|
|
|
2169
|
-
// Record the error (marks step as failed)
|
|
2170
1725
|
await this.setStepError(currentStepState.stepId, error)
|
|
2171
1726
|
|
|
2172
1727
|
if (currentStepState.attemptCount < retries) {
|
|
2173
|
-
// Create a new pending retry attempt, then back off if configured.
|
|
2174
1728
|
currentStepState = await this.createRetryAttempt(
|
|
2175
1729
|
currentStepState.stepId,
|
|
2176
1730
|
'pending'
|
|
@@ -2180,22 +1734,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2180
1734
|
setTimeout(resolve, getDurationInMilliseconds(retryDelay))
|
|
2181
1735
|
)
|
|
2182
1736
|
}
|
|
2183
|
-
// Continue loop to retry
|
|
2184
1737
|
} else {
|
|
2185
|
-
// No more retries, fail the workflow
|
|
2186
1738
|
throw error
|
|
2187
1739
|
}
|
|
2188
1740
|
}
|
|
2189
1741
|
}
|
|
2190
1742
|
}
|
|
2191
1743
|
|
|
2192
|
-
/**
|
|
2193
|
-
* Run a failed step's compensation handler as a durable step of its own.
|
|
2194
|
-
*
|
|
2195
|
-
* Durable rather than a bare invoke so a replay does not compensate twice —
|
|
2196
|
-
* a handler is typically a refund or a rollback. `onError` is deliberately
|
|
2197
|
-
* not forwarded: a compensation handler cannot itself compensate.
|
|
2198
|
-
*/
|
|
2199
1744
|
private async runStepCompensation(
|
|
2200
1745
|
runId: string,
|
|
2201
1746
|
stepName: string,
|
|
@@ -2221,20 +1766,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2221
1766
|
rpcService: any,
|
|
2222
1767
|
stepOptions?: WorkflowStepOptions
|
|
2223
1768
|
): Promise<any> {
|
|
2224
|
-
// Capture the predecessor before nextStepKey advances the lineage to us.
|
|
2225
1769
|
const fromStepName = this.lastStepName(runId)
|
|
2226
1770
|
const stepName = this.nextStepKey(runId, logicalStepName)
|
|
2227
|
-
// Resolve the retry policy ONCE here so the value persisted on the step
|
|
2228
|
-
// (which drives `retriesExhausted`) is the same one the queue dispatch turns
|
|
2229
|
-
// into `attempts`. Without this the queue could retry N times while the
|
|
2230
|
-
// engine thinks retries are already exhausted (or vice-versa).
|
|
2231
1771
|
const resolvedStepOptions: WorkflowStepOptions = {
|
|
2232
1772
|
retries: stepOptions?.retries ?? DEFAULT_STEP_RETRIES,
|
|
2233
1773
|
retryDelay: stepOptions?.retryDelay,
|
|
2234
1774
|
actor: stepOptions?.actor,
|
|
2235
1775
|
onError: stepOptions?.onError,
|
|
2236
1776
|
}
|
|
2237
|
-
// Reuse the step if the run already reached it, otherwise create it.
|
|
2238
1777
|
const stepState = await this.loadOrCreateStep(runId, stepName, () =>
|
|
2239
1778
|
this.insertStepState(
|
|
2240
1779
|
runId,
|
|
@@ -2247,18 +1786,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2247
1786
|
)
|
|
2248
1787
|
|
|
2249
1788
|
if (stepState.status === 'succeeded') {
|
|
2250
|
-
// Return cached result
|
|
2251
1789
|
return stepState.result
|
|
2252
1790
|
}
|
|
2253
1791
|
|
|
2254
1792
|
if (stepState.status === 'failed') {
|
|
2255
|
-
// Step failed with retries exhausted - throw error to fail the workflow
|
|
2256
1793
|
const error = new Error(
|
|
2257
1794
|
stepState.error?.message ||
|
|
2258
1795
|
`Step '${stepName}' failed after exhausting all retries`
|
|
2259
1796
|
)
|
|
2260
|
-
// Compensation, mirroring a graph node's onError: run the handler, then
|
|
2261
|
-
// still throw — the workflow fails either way.
|
|
2262
1797
|
if (resolvedStepOptions.onError) {
|
|
2263
1798
|
await this.runStepCompensation(
|
|
2264
1799
|
runId,
|
|
@@ -2268,7 +1803,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2268
1803
|
error
|
|
2269
1804
|
)
|
|
2270
1805
|
}
|
|
2271
|
-
// Preserve original error properties if available
|
|
2272
1806
|
if (stepState.error) {
|
|
2273
1807
|
Object.assign(error, stepState.error)
|
|
2274
1808
|
}
|
|
@@ -2276,19 +1810,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2276
1810
|
}
|
|
2277
1811
|
|
|
2278
1812
|
if (stepState.status === 'scheduled') {
|
|
2279
|
-
// Step is already scheduled, pause workflow
|
|
2280
1813
|
throw new WorkflowAsyncException(runId, stepName)
|
|
2281
1814
|
}
|
|
2282
1815
|
|
|
2283
|
-
// Hand off to subclass-overridable transport. Default behavior enqueues
|
|
2284
|
-
// via the queue service; DO-style subclasses RPC to a step worker.
|
|
2285
|
-
// Dispatch BEFORE marking the step `scheduled`: if the queue is down,
|
|
2286
|
-
// dispatchStep throws WorkflowDispatchException and the step stays `pending`,
|
|
2287
|
-
// so the orchestrator's next replay re-dispatches it. Marking `scheduled`
|
|
2288
|
-
// first would strand the step (replay sees `scheduled`, pauses, never
|
|
2289
|
-
// re-enqueues the job that was never created).
|
|
2290
|
-
// Actor steps never queue: they are outbound HTTP calls made by the
|
|
2291
|
-
// runner itself, and the actor's session lives on this process.
|
|
2292
1816
|
const dispatched = resolvedStepOptions.actor
|
|
2293
1817
|
? false
|
|
2294
1818
|
: await this.dispatchStep(
|
|
@@ -2304,8 +1828,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2304
1828
|
throw new WorkflowAsyncException(runId, stepName)
|
|
2305
1829
|
}
|
|
2306
1830
|
|
|
2307
|
-
// Inline (no transport available) - execute locally with the shared retry
|
|
2308
|
-
// loop. The body resolves to a sub-workflow result or a plain RPC result.
|
|
2309
1831
|
const retries = resolvedStepOptions.retries ?? this.getConfig().retries
|
|
2310
1832
|
const retryDelay = resolvedStepOptions.retryDelay
|
|
2311
1833
|
|
|
@@ -2314,13 +1836,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2314
1836
|
retries,
|
|
2315
1837
|
retryDelay,
|
|
2316
1838
|
async (currentStepState) => {
|
|
2317
|
-
// Actor step: send through the actor's authenticated client over the
|
|
2318
|
-
// REAL transport. Never falls back to internal dispatch — that would
|
|
2319
|
-
// bypass auth and fake a green health check.
|
|
2320
1839
|
if (resolvedStepOptions.actor) {
|
|
2321
1840
|
return resolvedStepOptions.actor.invoke(rpcName, data)
|
|
2322
1841
|
}
|
|
2323
|
-
// Check if the name refers to a workflow
|
|
2324
1842
|
const workflowMeta = pikkuState(null, 'workflows', 'meta')[rpcName]
|
|
2325
1843
|
if (workflowMeta) {
|
|
2326
1844
|
const childWire = {
|
|
@@ -2337,7 +1855,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2337
1855
|
{ inline: true }
|
|
2338
1856
|
)
|
|
2339
1857
|
await this.setStepChildRunId(currentStepState.stepId, childRunId)
|
|
2340
|
-
// Poll until child workflow completes
|
|
2341
1858
|
const childRun = await this.awaitRunEnd(
|
|
2342
1859
|
childRunId,
|
|
2343
1860
|
WORKFLOW_CHILD_POLL_MAX_MS
|
|
@@ -2376,25 +1893,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2376
1893
|
logicalStepName: string,
|
|
2377
1894
|
fn: Function,
|
|
2378
1895
|
stepOptions?: WorkflowStepOptions,
|
|
2379
|
-
/**
|
|
2380
|
-
* The input this step was called with, recorded on the run so a reporter can
|
|
2381
|
-
* name the values under test. A closure step has none; a scenario step does.
|
|
2382
|
-
*/
|
|
2383
1896
|
data: any = null,
|
|
2384
|
-
/**
|
|
2385
|
-
* The name this step was dispatched by, for the kinds of inline step that
|
|
2386
|
-
* have one. A closure step has no name; a scenario step is a step RPC, so
|
|
2387
|
-
* it records the step function that ran — which is the only way to join a
|
|
2388
|
-
* step back to its declaration when its durable name was built at runtime
|
|
2389
|
-
* (a step called in a loop reaches the run as `sees @pikku/addon-todos`,
|
|
2390
|
-
* declared as `sees ${packageName}`).
|
|
2391
|
-
*/
|
|
2392
1897
|
rpcName: string | null = null
|
|
2393
1898
|
): Promise<any> {
|
|
2394
1899
|
const fromStepName = this.lastStepName(runId)
|
|
2395
1900
|
const stepName = this.nextStepKey(runId, logicalStepName)
|
|
2396
|
-
// Reuse the step if the run already reached it, otherwise create it
|
|
2397
|
-
// (inline, so never dispatched).
|
|
2398
1901
|
const stepState = await this.loadOrCreateStep(runId, stepName, () =>
|
|
2399
1902
|
this.insertStepState(
|
|
2400
1903
|
runId,
|
|
@@ -2407,20 +1910,15 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2407
1910
|
)
|
|
2408
1911
|
|
|
2409
1912
|
if (stepState.status === 'succeeded') {
|
|
2410
|
-
// Return cached result
|
|
2411
1913
|
return stepState.result
|
|
2412
1914
|
}
|
|
2413
1915
|
|
|
2414
|
-
// Execute inline function
|
|
2415
1916
|
const retries = stepOptions?.retries ?? this.getConfig().retries
|
|
2416
1917
|
const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay
|
|
2417
1918
|
|
|
2418
|
-
|
|
2419
|
-
if (this.isInline(runId)) {
|
|
2420
|
-
// Inline mode - execute with the shared in-process retry loop.
|
|
1919
|
+
if (await this.isInline(runId)) {
|
|
2421
1920
|
return this.runInlineRetryLoop(stepState, retries, retryDelay, () => fn())
|
|
2422
1921
|
} else {
|
|
2423
|
-
// Remote mode - single attempt, then suspend for orchestrator-driven retry.
|
|
2424
1922
|
let currentStepState = stepState
|
|
2425
1923
|
try {
|
|
2426
1924
|
await this.setStepRunning(currentStepState.stepId)
|
|
@@ -2428,22 +1926,16 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2428
1926
|
await this.setStepResult(currentStepState.stepId, result)
|
|
2429
1927
|
return result
|
|
2430
1928
|
} catch (error: any) {
|
|
2431
|
-
// Record the error (marks step as failed)
|
|
2432
1929
|
await this.setStepError(currentStepState.stepId, error)
|
|
2433
1930
|
|
|
2434
|
-
// Check if we should retry
|
|
2435
1931
|
if (currentStepState.attemptCount < retries) {
|
|
2436
|
-
// Create a new pending retry attempt (copies metadata from failed step)
|
|
2437
1932
|
await this.createRetryAttempt(currentStepState.stepId, 'pending')
|
|
2438
1933
|
|
|
2439
|
-
// Schedule orchestrator to retry after delay
|
|
2440
1934
|
await this.scheduleOrchestratorRetry(runId, retryDelay)
|
|
2441
1935
|
|
|
2442
|
-
// Pause workflow - orchestrator will replay and pick up new attempt
|
|
2443
1936
|
throw new WorkflowAsyncException(runId, stepName)
|
|
2444
1937
|
}
|
|
2445
1938
|
|
|
2446
|
-
// No more retries, fail the workflow
|
|
2447
1939
|
throw error
|
|
2448
1940
|
}
|
|
2449
1941
|
}
|
|
@@ -2456,8 +1948,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2456
1948
|
) {
|
|
2457
1949
|
const fromStepName = this.lastStepName(runId)
|
|
2458
1950
|
const stepName = this.nextStepKey(runId, logicalStepName)
|
|
2459
|
-
// Reuse the step if the run already reached it, otherwise create it
|
|
2460
|
-
// (sleep step, no RPC).
|
|
2461
1951
|
const stepState = await this.loadOrCreateStep(runId, stepName, () =>
|
|
2462
1952
|
this.insertStepState(
|
|
2463
1953
|
runId,
|
|
@@ -2470,20 +1960,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2470
1960
|
)
|
|
2471
1961
|
|
|
2472
1962
|
if (stepState.status === 'succeeded') {
|
|
2473
|
-
// Sleep already completed, return immediately
|
|
2474
1963
|
return
|
|
2475
1964
|
}
|
|
2476
1965
|
|
|
2477
1966
|
if (stepState.status === 'scheduled') {
|
|
2478
|
-
// Sleep is already scheduled, pause workflow
|
|
2479
1967
|
throw new WorkflowAsyncException(runId, stepName)
|
|
2480
1968
|
}
|
|
2481
1969
|
|
|
2482
|
-
// Hand off to subclass-overridable transport. Default behavior schedules
|
|
2483
|
-
// a delayed sleeper RPC via the scheduler service; DO-style subclasses
|
|
2484
|
-
// override to use native timer primitives (e.g. setAlarm). Schedule BEFORE
|
|
2485
|
-
// marking `scheduled` so a scheduler outage leaves the step `pending` for
|
|
2486
|
-
// re-scheduling on replay instead of stranding it (see rpcStep).
|
|
2487
1970
|
let scheduled: boolean
|
|
2488
1971
|
try {
|
|
2489
1972
|
scheduled = await this.scheduleSleep(runId, stepState.stepId, duration)
|
|
@@ -2495,24 +1978,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2495
1978
|
throw new WorkflowAsyncException(runId, stepName)
|
|
2496
1979
|
}
|
|
2497
1980
|
|
|
2498
|
-
// Inline mode - use setTimeout with actual duration
|
|
2499
1981
|
await new Promise((resolve) =>
|
|
2500
1982
|
setTimeout(resolve, getDurationInMilliseconds(duration))
|
|
2501
1983
|
)
|
|
2502
1984
|
await this.setStepResult(stepState.stepId, null)
|
|
2503
1985
|
}
|
|
2504
1986
|
|
|
2505
|
-
/**
|
|
2506
|
-
* Derive the durable step name for a suspend point from its `reason`, so each
|
|
2507
|
-
* distinct reason is its own step row — letting one workflow have multiple
|
|
2508
|
-
* independent suspends (e.g. wait-for-build, then wait-for-approval), and
|
|
2509
|
-
* supporting dynamic reasons in loops (`suspend(`Wait for ${i}`)`) exactly
|
|
2510
|
-
* like dynamic `do()` step names. The reason is used raw (it's just a text
|
|
2511
|
-
* step name), only namespaced so it can't collide with a `do`/`sleep` step of
|
|
2512
|
-
* the same name. Like `do()` / `sleep()`, the reason is the step's stable
|
|
2513
|
-
* identity: it MUST be derived deterministically so it's the same every time
|
|
2514
|
-
* the workflow replays through that point.
|
|
2515
|
-
*/
|
|
2516
1987
|
private getSuspendStepName(reason: string): string {
|
|
2517
1988
|
return `__workflow_suspend:${reason}`
|
|
2518
1989
|
}
|
|
@@ -2568,16 +2039,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2568
2039
|
})
|
|
2569
2040
|
}
|
|
2570
2041
|
|
|
2571
|
-
/**
|
|
2572
|
-
* Wake a run later by enqueuing a delayed orchestrator pass. Deliberately NOT
|
|
2573
|
-
* {@link scheduleSleep}: that resolves the step it is given, which for an
|
|
2574
|
-
* approval would resolve the gate itself. This only nudges the run to replay
|
|
2575
|
-
* and re-evaluate — the gate stays the sole judge of its own outcome.
|
|
2576
|
-
*
|
|
2577
|
-
* Best-effort by design. Expiry is decided from the recorded deadline on
|
|
2578
|
-
* replay, so losing this wake costs liveness (the run sits until something
|
|
2579
|
-
* else resumes it), never correctness.
|
|
2580
|
-
*/
|
|
2581
2042
|
private async scheduleRunWake(runId: string, delay: number): Promise<void> {
|
|
2582
2043
|
try {
|
|
2583
2044
|
const queueService = this.verifyQueueService()
|
|
@@ -2600,21 +2061,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2600
2061
|
}
|
|
2601
2062
|
}
|
|
2602
2063
|
|
|
2603
|
-
/**
|
|
2604
|
-
* Durable step name for an approval gate. Namespaced separately from suspend
|
|
2605
|
-
* so the two can't collide, and derived from `reason` for the same reason
|
|
2606
|
-
* {@link getSuspendStepName} is: it must be stable across replays.
|
|
2607
|
-
*/
|
|
2608
2064
|
private getApprovalStepName(reason: string): string {
|
|
2609
2065
|
return `__workflow_approval:${reason}`
|
|
2610
2066
|
}
|
|
2611
2067
|
|
|
2612
|
-
/**
|
|
2613
|
-
* Run-state key holding an approval gate's record. Hex-encoded because the
|
|
2614
|
-
* Mongo backend restricts state keys to `/^[a-zA-Z0-9_]+$/` and a `reason` is
|
|
2615
|
-
* arbitrary human text. One key per gate, so two gates resolving concurrently
|
|
2616
|
-
* can't clobber each other through a read-modify-write.
|
|
2617
|
-
*/
|
|
2618
2068
|
private approvalStateKey(stepName: string): string {
|
|
2619
2069
|
let hex = ''
|
|
2620
2070
|
for (const byte of new TextEncoder().encode(stepName)) {
|
|
@@ -2623,17 +2073,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2623
2073
|
return `__approval_${hex}`
|
|
2624
2074
|
}
|
|
2625
2075
|
|
|
2626
|
-
/**
|
|
2627
|
-
* Record a decision against an approval gate and wake the run. Called from
|
|
2628
|
-
* outside the workflow (an HTTP route, an RPC), so the schema value is NOT in
|
|
2629
|
-
* scope here — the payload is stored raw and validated on replay inside the
|
|
2630
|
-
* workflow body, which is the only place the schema exists. An invalid payload
|
|
2631
|
-
* therefore leaves the gate closed rather than failing the run.
|
|
2632
|
-
*
|
|
2633
|
-
* `reason` addresses the first reach of that gate. An approval reached more
|
|
2634
|
-
* than once under the same reason (e.g. in a loop) gets `#N`-suffixed step
|
|
2635
|
-
* rows that this cannot currently target.
|
|
2636
|
-
*/
|
|
2637
2076
|
public async approveStep(
|
|
2638
2077
|
runId: string,
|
|
2639
2078
|
reason: string,
|
|
@@ -2642,16 +2081,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2642
2081
|
const stepName = this.getApprovalStepName(reason)
|
|
2643
2082
|
const stateKey = this.approvalStateKey(stepName)
|
|
2644
2083
|
|
|
2645
|
-
// A resolved gate returns its cached step result and never re-reads state,
|
|
2646
|
-
// so a decision recorded now would be silently discarded — most obviously
|
|
2647
|
-
// when it loses the race with expiry. Reject instead, so the approver
|
|
2648
|
-
// learns their decision did not land.
|
|
2649
2084
|
let resolved: StepState | undefined
|
|
2650
2085
|
try {
|
|
2651
2086
|
resolved = await this.getStepState(runId, stepName)
|
|
2652
2087
|
} catch {
|
|
2653
|
-
//
|
|
2654
|
-
// ahead of it is legitimate — the gate picks it up on arrival.
|
|
2088
|
+
// knowledge: decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md
|
|
2655
2089
|
}
|
|
2656
2090
|
if (resolved?.stepId && resolved.status === 'succeeded') {
|
|
2657
2091
|
const outcome = resolved.result as ApprovalOutcome<unknown> | undefined
|
|
@@ -2707,8 +2141,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2707
2141
|
)
|
|
2708
2142
|
}
|
|
2709
2143
|
|
|
2710
|
-
// Unlike suspend, `succeeded` here means a decision (or expiry) was
|
|
2711
|
-
// actually resolved, and the step result IS the return channel.
|
|
2712
2144
|
if (stepState.status === 'succeeded') {
|
|
2713
2145
|
return stepState.result as ApprovalOutcome<unknown>
|
|
2714
2146
|
}
|
|
@@ -2723,8 +2155,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2723
2155
|
|
|
2724
2156
|
if (stepState.status === 'pending') {
|
|
2725
2157
|
await this.setStepRunning(stepState.stepId)
|
|
2726
|
-
// First reach: stamp the deadline and nudge the run awake when it
|
|
2727
|
-
// passes. The deadline is what's authoritative — see below.
|
|
2728
2158
|
if (options.expiry !== undefined && !record.expiresAt) {
|
|
2729
2159
|
const expiresAt = new Date(
|
|
2730
2160
|
Date.now() + getDurationInMilliseconds(options.expiry)
|
|
@@ -2743,9 +2173,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2743
2173
|
record.decision
|
|
2744
2174
|
)
|
|
2745
2175
|
if (validation.issues) {
|
|
2746
|
-
// Drop the bad decision and re-close the gate, leaving the failure
|
|
2747
|
-
// legible to whoever tries next. Failing the run instead would let any
|
|
2748
|
-
// caller kill a workflow with a malformed payload.
|
|
2749
2176
|
await this.updateRunState(runId, stateKey, {
|
|
2750
2177
|
...record,
|
|
2751
2178
|
decision: undefined,
|
|
@@ -2767,9 +2194,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2767
2194
|
return outcome
|
|
2768
2195
|
}
|
|
2769
2196
|
|
|
2770
|
-
// Expiry is decided by comparing against the recorded deadline rather than
|
|
2771
|
-
// by the timer having fired, so a duplicate, late, or dropped timer all
|
|
2772
|
-
// produce the same answer.
|
|
2773
2197
|
if (record.expiresAt && Date.now() >= Date.parse(record.expiresAt)) {
|
|
2774
2198
|
const outcome: ApprovalOutcome<unknown> = { status: 'expired' }
|
|
2775
2199
|
await this.setStepResult(stepState.stepId, outcome)
|
|
@@ -2791,7 +2215,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2791
2215
|
runId,
|
|
2792
2216
|
getRun: async () => (await this.getRun(runId)) as WorkflowRun,
|
|
2793
2217
|
|
|
2794
|
-
// Implement workflow.do() - RPC form
|
|
2795
2218
|
do: async (
|
|
2796
2219
|
stepName: string,
|
|
2797
2220
|
rpcNameOrFn: any,
|
|
@@ -2822,7 +2245,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2822
2245
|
}
|
|
2823
2246
|
},
|
|
2824
2247
|
|
|
2825
|
-
// Implement workflow.sleep()
|
|
2826
2248
|
sleep: async (stepName: string, duration: string | number) => {
|
|
2827
2249
|
this.verifyStepName(stepName)
|
|
2828
2250
|
await this.sleepStep(
|
|
@@ -2871,16 +2293,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2871
2293
|
}
|
|
2872
2294
|
}
|
|
2873
2295
|
|
|
2874
|
-
/**
|
|
2875
|
-
* Get the orchestrator queue name for a specific workflow.
|
|
2876
|
-
* Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
|
|
2877
|
-
* falls back to the shared orchestrator queue.
|
|
2878
|
-
*
|
|
2879
|
-
* Reads from `queue.meta` (always populated globally) rather than
|
|
2880
|
-
* `queue.registrations` (only populated for queues this unit consumes).
|
|
2881
|
-
* In a per-unit deploy the orchestrator unit doesn't consume per-step
|
|
2882
|
-
* queues — but it produces to them — so registrations would miss them.
|
|
2883
|
-
*/
|
|
2884
2296
|
protected getOrchestratorQueueName(workflowName?: string): string {
|
|
2885
2297
|
if (workflowName && this.queueStrategy !== 'shared-groups') {
|
|
2886
2298
|
const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`
|
|
@@ -2903,15 +2315,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2903
2315
|
return this.getConfig().stepWorkerQueueName
|
|
2904
2316
|
}
|
|
2905
2317
|
|
|
2906
|
-
/**
|
|
2907
|
-
* Fairness key for a job on a shared queue. Under `'per-workflow'` the queue
|
|
2908
|
-
* name already isolates workflows, so no group is needed — returning one
|
|
2909
|
-
* anyway would cap a workflow inside its own dedicated queue.
|
|
2910
|
-
*
|
|
2911
|
-
* The tier repeats the id so a workflow can be given its own limit purely
|
|
2912
|
-
* from config, with no per-workflow wiring; an unmatched tier falls back to
|
|
2913
|
-
* the default limit.
|
|
2914
|
-
*/
|
|
2915
2318
|
protected getJobGroup(id?: string): JobGroup | undefined {
|
|
2916
2319
|
if (!id || this.queueStrategy !== 'shared-groups') {
|
|
2917
2320
|
return undefined
|