@pikku/core 0.12.71 → 0.12.74
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 +848 -0
- package/dist/crypto-utils.d.ts +30 -5
- package/dist/crypto-utils.js +146 -41
- 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 +40 -30
- package/dist/function/functions.types.d.ts +44 -136
- package/dist/function/functions.types.js +0 -58
- package/dist/function/list.types.d.ts +12 -62
- package/dist/function/list.types.js +4 -25
- package/dist/handle-error.d.ts +0 -11
- package/dist/handle-error.js +6 -18
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- 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 +3 -40
- 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 +3 -2
- 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 +8 -7
- package/dist/schema.d.ts +7 -17
- package/dist/schema.js +30 -18
- package/dist/scopes.d.ts +4 -23
- package/dist/scopes.js +7 -48
- 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.js +1 -2
- package/dist/services/content-service.d.ts +1 -46
- package/dist/services/credential-service.d.ts +3 -40
- package/dist/services/deployment-service.d.ts +3 -9
- 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 +5 -6
- package/dist/services/index.js +2 -5
- 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.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 +0 -4
- package/dist/services/local-secrets.js +0 -4
- package/dist/services/logger-console.d.ts +3 -7
- package/dist/services/logger-console.js +3 -7
- package/dist/services/logger.d.ts +2 -37
- 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 +9 -41
- 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 +0 -4
- 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 +5 -33
- 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 +0 -7
- 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 +19 -63
- package/dist/services/webhook-service.js +6 -20
- package/dist/services/workflow-service.d.ts +3 -15
- package/dist/testing/service-tests.js +0 -17
- package/dist/time-utils.d.ts +0 -16
- package/dist/time-utils.js +1 -19
- package/dist/types/core.types.d.ts +99 -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 +34 -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 +58 -131
- 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 +49 -20
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +25 -2
- package/dist/wirings/ai-agent/ai-agent-stream.js +153 -63
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +84 -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 +39 -1
- package/dist/wirings/ai-agent/voice-input.js +46 -3
- package/dist/wirings/ai-agent/voice-output.d.ts +54 -1
- package/dist/wirings/ai-agent/voice-output.js +153 -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 +20 -0
- package/dist/wirings/cli/channel/cli-raw-client-runner.js +121 -0
- package/dist/wirings/cli/channel/index.d.ts +4 -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 +1 -100
- package/dist/wirings/http/http-runner.js +12 -166
- package/dist/wirings/http/http.types.d.ts +16 -55
- package/dist/wirings/http/index.d.ts +2 -1
- package/dist/wirings/http/index.js +1 -1
- 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 +6 -36
- package/dist/wirings/http/pikku-fetch-http-request.js +56 -50
- 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 +2 -0
- package/dist/wirings/mcp/mcp-runner.js +6 -16
- 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 +28 -0
- package/dist/wirings/persona/define-personas.js +27 -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 +125 -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 +102 -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 +18 -89
- 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 +16 -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 +1 -12
- package/dist/wirings/rpc/remote-addon-auth.js +1 -9
- 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 +27 -0
- package/dist/wirings/virtual-user/index.js +8 -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-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 +5 -5
- 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 -42
- 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 +0 -18
- 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 +72 -6
- 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 +0 -197
- 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/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-data-classification-brand-is-an-optional-property.md +34 -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 +113 -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/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/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/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 +4 -2
- package/run-tests.sh +0 -0
- package/src/crypto-utils.test.ts +460 -19
- package/src/crypto-utils.ts +283 -55
- package/src/data-classification.ts +1 -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 +75 -7
- package/src/function/function-runner.ts +73 -30
- package/src/function/functions.types.ts +56 -136
- package/src/function/list.types.test.ts +3 -25
- package/src/function/list.types.ts +12 -62
- package/src/gopass-secrets-removed.test.ts +51 -0
- package/src/handle-error.test.ts +108 -1
- package/src/handle-error.ts +8 -18
- package/src/index.ts +60 -1
- package/src/middleware/auth-apikey.test.ts +0 -1
- package/src/middleware/auth-apikey.ts +0 -17
- package/src/middleware/auth-bearer.test.ts +0 -3
- package/src/middleware/auth-bearer.ts +3 -40
- 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 +24 -9
- package/src/middleware/remote-auth.ts +10 -2
- 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 +172 -0
- package/src/remote.ts +17 -7
- package/src/schema.test.ts +103 -0
- package/src/schema.ts +35 -18
- package/src/scopes.ts +7 -48
- 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 +1 -2
- 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/deployment-service.ts +3 -9
- 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 +20 -15
- package/src/services/istanbul-coverage-service.ts +2 -8
- package/src/services/jwt-service.ts +1 -16
- 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.ts +0 -4
- package/src/services/logger-console.test.ts +0 -1
- package/src/services/logger-console.ts +3 -7
- package/src/services/logger.ts +2 -37
- 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.ts +9 -41
- 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 +0 -4
- 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 +5 -33
- 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 +1 -7
- package/src/services/v8-coverage-service.ts +3 -6
- package/src/services/variables-service.ts +1 -8
- package/src/services/webhook-service.ts +19 -63
- package/src/services/workflow-service.ts +3 -20
- package/src/testing/service-tests.ts +0 -26
- package/src/time-utils.ts +1 -19
- package/src/types/core.types.ts +116 -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 +75 -10
- package/src/wirings/ai-agent/ai-agent-agui.ts +36 -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 +82 -138
- 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 +67 -33
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +205 -103
- package/src/wirings/ai-agent/ai-agent-stream.ts +192 -75
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +301 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +85 -4
- package/src/wirings/ai-agent/index.ts +35 -3
- package/src/wirings/ai-agent/voice-input.test.ts +72 -7
- package/src/wirings/ai-agent/voice-input.ts +48 -3
- package/src/wirings/ai-agent/voice-output.test.ts +422 -0
- package/src/wirings/ai-agent/voice-output.ts +216 -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 +155 -0
- package/src/wirings/cli/channel/index.ts +9 -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 +12 -168
- package/src/wirings/http/http.types.ts +17 -62
- package/src/wirings/http/index.ts +5 -1
- package/src/wirings/http/log-http-routes.ts +0 -4
- package/src/wirings/http/pikku-fetch-http-request.test.ts +88 -7
- package/src/wirings/http/pikku-fetch-http-request.ts +74 -50
- 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.test.ts +40 -0
- package/src/wirings/mcp/mcp-runner.ts +9 -15
- 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 +29 -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 +131 -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 +453 -0
- package/src/wirings/queue/queue-identity.ts +173 -0
- package/src/wirings/queue/queue-runner.ts +12 -31
- package/src/wirings/queue/queue.types.ts +19 -89
- package/src/wirings/queue/register-queue-helper.ts +0 -14
- package/src/wirings/queue/signed-queue-service.ts +59 -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 +1 -13
- package/src/wirings/rpc/rpc-runner.test.ts +186 -2
- 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 +76 -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-target.ts +47 -0
- package/src/wirings/virtual-user/virtual-user.types.ts +219 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +5 -4
- 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 -52
- 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.ts +0 -28
- package/src/wirings/workflow/scenario-service.test.ts +2 -9
- package/src/wirings/workflow/scenario-step-guards.ts +1 -14
- package/src/wirings/workflow/scenario-step.test.ts +159 -14
- package/src/wirings/workflow/scenario-step.types.ts +82 -6
- package/src/wirings/workflow/scenario-surface.test.ts +145 -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 +0 -203
- package/tsconfig.tsbuildinfo +1 -1
- package/src/middleware/timeout.ts +0 -22
- package/src/pikku-response.ts +0 -5
- package/src/services/gopass-secrets.ts +0 -78
- 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
|
@@ -34,17 +34,7 @@ import { RPCNotFoundError } from '../rpc/rpc-runner.js';
|
|
|
34
34
|
import { ChildWorkflowStartedException } from './graph/graph-runner.js';
|
|
35
35
|
import { deriveInvocationId } from './workflow-invocation-id.js';
|
|
36
36
|
import { buildRunTimeline, reconstructStateAt, } from './run-timeline.js';
|
|
37
|
-
/**
|
|
38
|
-
* Default number of retries for a workflow step when none is specified. The
|
|
39
|
-
* workflow — not the queue — owns retry policy; a step inherits this unless it
|
|
40
|
-
* sets its own `retries` (including `retries: 0` to opt out entirely). Picked >0
|
|
41
|
-
* so a transient failure (a DB blip, a downstream restart, a deploy) is ridden
|
|
42
|
-
* out by default; safe because every step gets a stable `invocationId` to dedupe on.
|
|
43
|
-
*/
|
|
44
37
|
export const DEFAULT_STEP_RETRIES = 5;
|
|
45
|
-
/**
|
|
46
|
-
* Exception thrown when workflow needs to pause for async step
|
|
47
|
-
*/
|
|
48
38
|
export class WorkflowAsyncException extends Error {
|
|
49
39
|
runId;
|
|
50
40
|
stepName;
|
|
@@ -55,9 +45,6 @@ export class WorkflowAsyncException extends Error {
|
|
|
55
45
|
this.name = 'WorkflowAsyncException';
|
|
56
46
|
}
|
|
57
47
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Exception thrown when workflow is cancelled
|
|
60
|
-
*/
|
|
61
48
|
export class WorkflowCancelledException extends Error {
|
|
62
49
|
runId;
|
|
63
50
|
reason;
|
|
@@ -68,9 +55,6 @@ export class WorkflowCancelledException extends Error {
|
|
|
68
55
|
this.name = 'WorkflowCancelledException';
|
|
69
56
|
}
|
|
70
57
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Exception thrown when workflow is suspended
|
|
73
|
-
*/
|
|
74
58
|
export class WorkflowSuspendedException extends Error {
|
|
75
59
|
runId;
|
|
76
60
|
reason;
|
|
@@ -81,14 +65,6 @@ export class WorkflowSuspendedException extends Error {
|
|
|
81
65
|
this.name = 'WorkflowSuspendedException';
|
|
82
66
|
}
|
|
83
67
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Thrown when a step (or the orchestrator) could not be enqueued — the queue
|
|
86
|
-
* itself failed (e.g. pg-boss is momentarily down), NOT the step's own logic.
|
|
87
|
-
* This is transient infrastructure failure: the run is left untouched (the step
|
|
88
|
-
* stays `pending`, the run stays running) and the orchestrator job is rethrown
|
|
89
|
-
* so the queue redelivers it and the workflow replays from its snapshot. Treat
|
|
90
|
-
* it as non-terminal — never mark the run `failed` for it.
|
|
91
|
-
*/
|
|
92
68
|
export class WorkflowDispatchException extends Error {
|
|
93
69
|
runId;
|
|
94
70
|
stepName;
|
|
@@ -99,9 +75,6 @@ export class WorkflowDispatchException extends Error {
|
|
|
99
75
|
this.name = 'WorkflowDispatchException';
|
|
100
76
|
}
|
|
101
77
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Error class for workflow not found
|
|
104
|
-
*/
|
|
105
78
|
export class WorkflowNotFoundError extends PikkuError {
|
|
106
79
|
constructor(name) {
|
|
107
80
|
super(`Workflow not found: ${name}`);
|
|
@@ -140,12 +113,6 @@ addError(WorkflowRunCancelledError, {
|
|
|
140
113
|
status: 409,
|
|
141
114
|
message: 'Workflow was cancelled.',
|
|
142
115
|
});
|
|
143
|
-
/**
|
|
144
|
-
* A decision arrived for an approval gate that has already resolved. The gate
|
|
145
|
-
* caches its outcome as the step result and never re-reads run state, so the
|
|
146
|
-
* decision could not take effect — it is rejected rather than accepted and
|
|
147
|
-
* dropped.
|
|
148
|
-
*/
|
|
149
116
|
export class WorkflowApprovalResolvedError extends PikkuError {
|
|
150
117
|
payload;
|
|
151
118
|
constructor(reason, outcome) {
|
|
@@ -170,25 +137,13 @@ const WORKFLOW_END_STATES = new Set([
|
|
|
170
137
|
'cancelled',
|
|
171
138
|
'suspended',
|
|
172
139
|
]);
|
|
173
|
-
/**
|
|
174
|
-
* States a run never leaves. `suspended` is deliberately absent: a suspended
|
|
175
|
-
* run stops a poll loop but can still be resumed, so anything the process holds
|
|
176
|
-
* for it has to survive.
|
|
177
|
-
*/
|
|
178
140
|
const WORKFLOW_TERMINAL_STATES = new Set([
|
|
179
141
|
'completed',
|
|
180
142
|
'failed',
|
|
181
143
|
'cancelled',
|
|
182
144
|
]);
|
|
183
|
-
/** First wait when polling a run, before the backoff starts widening it. */
|
|
184
145
|
const WORKFLOW_POLL_MIN_MS = 10;
|
|
185
|
-
/** How much each successive wait grows, up to the caller's ceiling. */
|
|
186
146
|
const WORKFLOW_POLL_FACTOR = 1.6;
|
|
187
|
-
/**
|
|
188
|
-
* Ceiling for the wait on an inline sub-workflow. Lower than a top-level run's
|
|
189
|
-
* default, because the parent step is blocked on it and every wait here is
|
|
190
|
-
* added latency in the middle of a workflow rather than at its edge.
|
|
191
|
-
*/
|
|
192
147
|
const WORKFLOW_CHILD_POLL_MAX_MS = 500;
|
|
193
148
|
export class PikkuWorkflowService {
|
|
194
149
|
runExtension;
|
|
@@ -196,20 +151,38 @@ export class PikkuWorkflowService {
|
|
|
196
151
|
contextFor(runId) {
|
|
197
152
|
let context = this.runContexts.get(runId);
|
|
198
153
|
if (!context) {
|
|
199
|
-
context = {
|
|
154
|
+
context = { activeExecutions: 0, ordinals: new Map() };
|
|
200
155
|
this.runContexts.set(runId, context);
|
|
201
156
|
}
|
|
202
157
|
return context;
|
|
203
158
|
}
|
|
204
|
-
/** Drop a run's context once nothing is holding it open. */
|
|
205
159
|
releaseContext(runId) {
|
|
206
160
|
const context = this.runContexts.get(runId);
|
|
207
161
|
if (!context)
|
|
208
162
|
return;
|
|
209
|
-
if (context.
|
|
163
|
+
if (context.activeExecutions > 0)
|
|
210
164
|
return;
|
|
211
165
|
this.runContexts.delete(runId);
|
|
212
166
|
}
|
|
167
|
+
enterExecution(runId) {
|
|
168
|
+
const context = this.contextFor(runId);
|
|
169
|
+
context.activeExecutions++;
|
|
170
|
+
return context;
|
|
171
|
+
}
|
|
172
|
+
exitExecution(runId) {
|
|
173
|
+
const context = this.runContexts.get(runId);
|
|
174
|
+
if (!context)
|
|
175
|
+
return;
|
|
176
|
+
if (context.activeExecutions > 0) {
|
|
177
|
+
context.activeExecutions--;
|
|
178
|
+
}
|
|
179
|
+
if (context.activeExecutions === 0) {
|
|
180
|
+
context.replay = undefined;
|
|
181
|
+
context.ordinals = new Map();
|
|
182
|
+
context.lastStep = undefined;
|
|
183
|
+
}
|
|
184
|
+
this.releaseContext(runId);
|
|
185
|
+
}
|
|
213
186
|
get logger() {
|
|
214
187
|
return getSingletonServices()?.logger;
|
|
215
188
|
}
|
|
@@ -227,17 +200,6 @@ export class PikkuWorkflowService {
|
|
|
227
200
|
this.wireQueueWorkers();
|
|
228
201
|
}
|
|
229
202
|
}
|
|
230
|
-
/**
|
|
231
|
-
* Perform a state write, then shadow it to the mirror.
|
|
232
|
-
*
|
|
233
|
-
* The mirror is an observability sink, never a second source of truth, and
|
|
234
|
-
* both halves of that follow from this one shape: it is only ever told about
|
|
235
|
-
* a write that already landed, and a mirror that is down or throwing cannot
|
|
236
|
-
* fail — or even be seen by — the workflow it is watching.
|
|
237
|
-
*
|
|
238
|
-
* @param write - the authoritative write; its result is what the caller gets
|
|
239
|
-
* @param mirror - shadows the write, given the live mirror and what was written
|
|
240
|
-
*/
|
|
241
203
|
async mirrored(write, mirror) {
|
|
242
204
|
const written = await write();
|
|
243
205
|
if (this.mirror) {
|
|
@@ -249,22 +211,15 @@ export class PikkuWorkflowService {
|
|
|
249
211
|
this.logger?.warn?.(`[pikku] WorkflowRunMirror write failed: ${err?.message ?? err}`);
|
|
250
212
|
}
|
|
251
213
|
catch {
|
|
252
|
-
//
|
|
214
|
+
// knowledge: decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md
|
|
253
215
|
}
|
|
254
216
|
}
|
|
255
217
|
}
|
|
256
218
|
return written;
|
|
257
219
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Wire the queue-based orchestrator/step/sleeper workers.
|
|
260
|
-
* Subclasses that orchestrate without queues (e.g. Durable Objects) should
|
|
261
|
-
* pass `wireQueues: false` to the base constructor and skip this entirely.
|
|
262
|
-
* Call this explicitly after adding addons dynamically.
|
|
263
|
-
*/
|
|
264
220
|
wireQueueWorkers() {
|
|
265
221
|
const functions = pikkuState(null, 'function', 'functions');
|
|
266
222
|
const functionsMeta = pikkuState(null, 'function', 'meta');
|
|
267
|
-
// Minimal meta for internal workflow functions (satisfies FunctionMeta)
|
|
268
223
|
const mkMeta = (funcId) => ({
|
|
269
224
|
pikkuFuncId: funcId,
|
|
270
225
|
sessionless: true,
|
|
@@ -285,10 +240,6 @@ export class PikkuWorkflowService {
|
|
|
285
240
|
functionsMeta[funcId] = mkMeta(funcId);
|
|
286
241
|
}
|
|
287
242
|
};
|
|
288
|
-
// Under 'shared-groups' every workflow runs through these two queues and is
|
|
289
|
-
// kept from hogging them by the per-group cap, so the per-workflow queues
|
|
290
|
-
// below are left unconsumed — one set of pollers for the whole system
|
|
291
|
-
// instead of one per workflow.
|
|
292
243
|
const sharedGroups = this.queueStrategy === 'shared-groups';
|
|
293
244
|
const sharedQueueConfig = sharedGroups
|
|
294
245
|
? {
|
|
@@ -296,10 +247,8 @@ export class PikkuWorkflowService {
|
|
|
296
247
|
groupConcurrency: this.queueGroupConcurrency,
|
|
297
248
|
}
|
|
298
249
|
: undefined;
|
|
299
|
-
// Register shared queue workers for monolith deployments
|
|
300
250
|
registerWorkflowFunc('pikkuWorkflowOrchestrator', { func: pikkuWorkflowOrchestratorFunc }, 'pikku-workflow-orchestrator', sharedQueueConfig);
|
|
301
251
|
registerWorkflowFunc('pikkuWorkflowStepWorker', { func: pikkuWorkflowWorkerFunc }, 'pikku-workflow-step-worker', sharedQueueConfig);
|
|
302
|
-
// Register per-workflow queue workers (root + addon packages)
|
|
303
252
|
const registerQueueWorkers = (queueMeta) => {
|
|
304
253
|
for (const [queueName, meta] of Object.entries(queueMeta)) {
|
|
305
254
|
if (functions.has(meta.pikkuFuncId))
|
|
@@ -324,13 +273,6 @@ export class PikkuWorkflowService {
|
|
|
324
273
|
}
|
|
325
274
|
}
|
|
326
275
|
}
|
|
327
|
-
// Workflows exist but no per-workflow orchestrator queue was registered:
|
|
328
|
-
// the generated queue meta never reached the runtime (most often the
|
|
329
|
-
// bootstrap doesn't import the queue-workers meta, so `queue.meta` is
|
|
330
|
-
// empty). Everything still "works" — dispatch silently falls back to the
|
|
331
|
-
// single shared orchestrator queue — but the isolation is gone: one slow
|
|
332
|
-
// workflow step head-of-line-blocks every other workflow behind it. That
|
|
333
|
-
// is invisible until a queue starves, so say so loudly at wiring time.
|
|
334
276
|
const workflowCount = Object.keys(pikkuState(null, 'workflows', 'meta') ?? {}).length;
|
|
335
277
|
const perWorkflowQueues = Object.keys(queueMeta).filter((name) => name.startsWith('wf-orchestrator-')).length;
|
|
336
278
|
if (workflowCount > 0 && perWorkflowQueues === 0) {
|
|
@@ -347,26 +289,25 @@ export class PikkuWorkflowService {
|
|
|
347
289
|
functionsMeta.pikkuWorkflowSleeper = mkMeta('pikkuWorkflowSleeper');
|
|
348
290
|
}
|
|
349
291
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
292
|
+
async isInline(runId) {
|
|
293
|
+
const context = this.runContexts.get(runId);
|
|
294
|
+
if (context?.inline !== undefined) {
|
|
295
|
+
return context.inline;
|
|
296
|
+
}
|
|
297
|
+
const inline = (await this.getRunIdentity(runId))?.inline === true;
|
|
298
|
+
if (context) {
|
|
299
|
+
context.inline = inline;
|
|
300
|
+
}
|
|
301
|
+
return inline;
|
|
355
302
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Register a run as inline (for graph-runner to use)
|
|
358
|
-
*/
|
|
359
303
|
registerInlineRun(runId) {
|
|
360
304
|
this.contextFor(runId).inline = true;
|
|
361
305
|
}
|
|
362
|
-
/**
|
|
363
|
-
* Unregister a run from inline tracking
|
|
364
|
-
*/
|
|
365
306
|
unregisterInlineRun(runId) {
|
|
366
307
|
const context = this.runContexts.get(runId);
|
|
367
308
|
if (!context)
|
|
368
309
|
return;
|
|
369
|
-
context.inline =
|
|
310
|
+
context.inline = undefined;
|
|
370
311
|
this.releaseContext(runId);
|
|
371
312
|
}
|
|
372
313
|
async registerWorkflowVersions() {
|
|
@@ -380,17 +321,12 @@ export class PikkuWorkflowService {
|
|
|
380
321
|
async createRun(workflowName, input, inline, graphHash, wire, options) {
|
|
381
322
|
return this.mirrored(() => this.createRunImpl(workflowName, input, inline, graphHash, wire, options), (mirror, runId) => mirror.createRun(runId, workflowName, input, inline, graphHash, wire, options));
|
|
382
323
|
}
|
|
383
|
-
/**
|
|
384
|
-
* Get minimal workflow run status with step summaries.
|
|
385
|
-
* Used by the public API — the console addon provides the full verbose view.
|
|
386
|
-
*/
|
|
387
324
|
async getRunStatus(id) {
|
|
388
325
|
const run = await this.getRun(id);
|
|
389
326
|
if (!run)
|
|
390
327
|
return null;
|
|
391
328
|
const history = await this.getRunHistory(id);
|
|
392
329
|
const terminalStatuses = new Set(['completed', 'failed', 'cancelled']);
|
|
393
|
-
// Build step summaries from history (latest attempt per step)
|
|
394
330
|
const stepMap = new Map();
|
|
395
331
|
for (const step of history) {
|
|
396
332
|
const existing = stepMap.get(step.stepName);
|
|
@@ -425,100 +361,40 @@ export class PikkuWorkflowService {
|
|
|
425
361
|
: undefined,
|
|
426
362
|
};
|
|
427
363
|
}
|
|
428
|
-
/**
|
|
429
|
-
* Build the run's time-travel event stream from durable history.
|
|
430
|
-
* @param id - Run ID
|
|
431
|
-
* @returns Ordered timeline, or null if the run doesn't exist
|
|
432
|
-
*/
|
|
433
364
|
async getRunTimeline(id) {
|
|
434
365
|
const run = await this.getRun(id);
|
|
435
366
|
if (!run)
|
|
436
367
|
return null;
|
|
437
368
|
return buildRunTimeline(await this.getRunHistory(id));
|
|
438
369
|
}
|
|
439
|
-
/**
|
|
440
|
-
* Reconstruct the run's state at a point in its timeline.
|
|
441
|
-
* @param id - Run ID
|
|
442
|
-
* @param at - A seq index (inclusive) or a Date (inclusive); omit for the
|
|
443
|
-
* final state.
|
|
444
|
-
* @returns Reconstructed state, or null if the run doesn't exist
|
|
445
|
-
*/
|
|
446
370
|
async reconstructRunStateAt(id, at) {
|
|
447
371
|
const timeline = await this.getRunTimeline(id);
|
|
448
372
|
if (!timeline)
|
|
449
373
|
return null;
|
|
450
374
|
return reconstructStateAt(timeline, at ?? timeline.length - 1);
|
|
451
375
|
}
|
|
452
|
-
/**
|
|
453
|
-
* Update workflow run status
|
|
454
|
-
* @param id - Run ID
|
|
455
|
-
* @param status - New status
|
|
456
|
-
*/
|
|
457
376
|
async updateRunStatus(id, status, output, error) {
|
|
458
377
|
await this.mirrored(() => this.updateRunStatusImpl(id, status, output, error), (mirror) => mirror.updateRunStatus(id, status, output, error));
|
|
459
378
|
if (WORKFLOW_TERMINAL_STATES.has(status)) {
|
|
460
|
-
// The run is over: release whatever this process opened for it. Queued
|
|
461
|
-
// runs never pass through the inline path that does this, so their
|
|
462
|
-
// context was held for the life of the process.
|
|
463
379
|
this.runExtension?.detachRunContext(id);
|
|
464
380
|
this.releaseContext(id);
|
|
465
381
|
}
|
|
466
382
|
}
|
|
467
|
-
/**
|
|
468
|
-
* Insert initial step state (called by orchestrator)
|
|
469
|
-
* Creates pending step in both workflow_step and workflow_step_history
|
|
470
|
-
* @param runId - Run ID
|
|
471
|
-
* @param stepName - Step cache key
|
|
472
|
-
* @param rpcName - The name this step was dispatched by: an RPC for a
|
|
473
|
-
* `workflow.do` step, a step function for a scenario step, null for a
|
|
474
|
-
* closure. Nothing dispatches off this value — it is recorded so a reader
|
|
475
|
-
* can join a step back to the function that ran it.
|
|
476
|
-
* @param data - Step input data
|
|
477
|
-
* @param stepOptions - Step options (retries, retryDelay)
|
|
478
|
-
* @returns Step state with generated stepId
|
|
479
|
-
*/
|
|
480
383
|
async insertStepState(runId, stepName, rpcName, data, stepOptions, fromStepName) {
|
|
481
384
|
return this.mirrored(() => this.insertStepStateImpl(runId, stepName, rpcName, data, stepOptions, fromStepName), (mirror, step) => mirror.insertStepState(runId, { ...step, stepName, rpcName, data }));
|
|
482
385
|
}
|
|
483
|
-
/**
|
|
484
|
-
* Mark step as running
|
|
485
|
-
* Updates both workflow_step and workflow_step_history
|
|
486
|
-
* @param stepId - Step ID
|
|
487
|
-
*/
|
|
488
386
|
async setStepRunning(stepId) {
|
|
489
387
|
await this.mirrored(() => this.setStepRunningImpl(stepId), (mirror) => mirror.setStepRunning(stepId));
|
|
490
388
|
}
|
|
491
|
-
/**
|
|
492
|
-
* Mark step as scheduled (queued for execution)
|
|
493
|
-
* Updates both workflow_step and workflow_step_history
|
|
494
|
-
* @param stepId - Step ID
|
|
495
|
-
*/
|
|
496
389
|
async setStepScheduled(stepId) {
|
|
497
390
|
await this.mirrored(() => this.setStepScheduledImpl(stepId), (mirror) => mirror.setStepScheduled(stepId));
|
|
498
391
|
}
|
|
499
|
-
/**
|
|
500
|
-
* Store step result and mark as succeeded
|
|
501
|
-
* Updates both workflow_step and workflow_step_history
|
|
502
|
-
* @param stepId - Step ID
|
|
503
|
-
* @param result - Step result
|
|
504
|
-
*/
|
|
505
392
|
async setStepResult(stepId, result) {
|
|
506
393
|
await this.mirrored(() => this.setStepResultImpl(stepId, result), (mirror) => mirror.setStepResult(stepId, result));
|
|
507
394
|
}
|
|
508
|
-
/**
|
|
509
|
-
* Set the child workflow run ID on a step
|
|
510
|
-
* @param stepId - Step ID
|
|
511
|
-
* @param childRunId - Child workflow run ID
|
|
512
|
-
*/
|
|
513
395
|
async setStepChildRunId(stepId, childRunId) {
|
|
514
396
|
await this.mirrored(() => this.setStepChildRunIdImpl(stepId, childRunId), (mirror) => mirror.setStepChildRunId(stepId, childRunId));
|
|
515
397
|
}
|
|
516
|
-
/**
|
|
517
|
-
* Store step error and mark as failed
|
|
518
|
-
* Updates both workflow_step and workflow_step_history
|
|
519
|
-
* @param stepId - Step ID
|
|
520
|
-
* @param error - Error object
|
|
521
|
-
*/
|
|
522
398
|
async setStepError(stepId, error) {
|
|
523
399
|
await this.mirrored(() => this.setStepErrorImpl(stepId, error), (mirror) => {
|
|
524
400
|
const serialized = {
|
|
@@ -530,34 +406,15 @@ export class PikkuWorkflowService {
|
|
|
530
406
|
return mirror.setStepError(stepId, serialized);
|
|
531
407
|
});
|
|
532
408
|
}
|
|
533
|
-
/**
|
|
534
|
-
* Create a new retry attempt for a failed step
|
|
535
|
-
* Inserts new pending step in both workflow_step and workflow_step_history
|
|
536
|
-
* Resets status to 'pending' with new stepId
|
|
537
|
-
* Copies metadata (rpcName, data, retries, retryDelay) from failed attempt
|
|
538
|
-
* @param failedStepId - Failed step ID to copy from
|
|
539
|
-
* @returns New step state for the retry attempt
|
|
540
|
-
*/
|
|
541
409
|
async createRetryAttempt(failedStepId, status) {
|
|
542
410
|
return this.mirrored(() => this.createRetryAttemptImpl(failedStepId, status), (mirror, newStep) => mirror.createRetryAttempt(failedStepId, {
|
|
543
411
|
...newStep,
|
|
544
412
|
stepName: newStep.stepName ?? '',
|
|
545
413
|
}));
|
|
546
414
|
}
|
|
547
|
-
/**
|
|
548
|
-
* Set the branch key for a graph node step
|
|
549
|
-
* @param stepId - Step ID
|
|
550
|
-
* @param branchKey - Branch key selected by graph.branch()
|
|
551
|
-
*/
|
|
552
415
|
async setBranchTaken(stepId, branchKey) {
|
|
553
416
|
await this.mirrored(() => this.setBranchTakenImpl(stepId, branchKey), (mirror) => mirror.setBranchTaken(stepId, branchKey));
|
|
554
417
|
}
|
|
555
|
-
/**
|
|
556
|
-
* Update a state variable in the workflow run's state
|
|
557
|
-
* @param runId - Run ID
|
|
558
|
-
* @param name - Variable name
|
|
559
|
-
* @param value - Value to store
|
|
560
|
-
*/
|
|
561
418
|
async updateRunState(runId, name, value) {
|
|
562
419
|
await this.mirrored(() => this.updateRunStateImpl(runId, name, value), (mirror) => mirror.updateRunState(runId, name, value));
|
|
563
420
|
}
|
|
@@ -567,44 +424,20 @@ export class PikkuWorkflowService {
|
|
|
567
424
|
async updateWorkflowVersionStatus(name, graphHash, status) {
|
|
568
425
|
await this.mirrored(() => this.updateWorkflowVersionStatusImpl(name, graphHash, status), (mirror) => mirror.updateWorkflowVersionStatus(name, graphHash, status));
|
|
569
426
|
}
|
|
570
|
-
// ============================================================================
|
|
571
|
-
// Workflow Lifecycle Methods
|
|
572
|
-
// ============================================================================
|
|
573
|
-
/**
|
|
574
|
-
* Resume a paused workflow by triggering the orchestrator
|
|
575
|
-
* @param runId - Run ID
|
|
576
|
-
*/
|
|
577
427
|
async resumeWorkflow(runId, workflowName) {
|
|
578
428
|
const queueService = this.verifyQueueService();
|
|
579
429
|
if (!workflowName) {
|
|
580
430
|
const run = await this.getRun(runId);
|
|
581
431
|
workflowName = run?.workflow;
|
|
582
432
|
}
|
|
583
|
-
// Carry an explicit retry policy on the orchestrator job too. Orchestrator
|
|
584
|
-
// runs are idempotent (they replay from the snapshot, returning cached step
|
|
585
|
-
// results), so redelivery is always safe — and it's what lets a transient
|
|
586
|
-
// dispatch/infra failure recover: the job is rethrown and retried instead of
|
|
587
|
-
// the run hanging. Passing `attempts` per-job overrides the queue default, so
|
|
588
|
-
// this holds even when the orchestrator queue is configured `retry_limit 0`.
|
|
589
433
|
await queueService.add(this.getOrchestratorQueueName(workflowName), { runId }, {
|
|
590
434
|
...this.resolveStepJobOptions(),
|
|
591
435
|
group: this.getJobGroup(workflowName),
|
|
592
436
|
});
|
|
593
437
|
}
|
|
594
|
-
/**
|
|
595
|
-
* Resolve a step's retry policy into queue job options. The workflow is the
|
|
596
|
-
* sole source of truth for retries: an explicitly-set `retries` (including 0)
|
|
597
|
-
* is always honored, an unset one defaults to {@link DEFAULT_STEP_RETRIES},
|
|
598
|
-
* and we ALWAYS pass `attempts` so the queue can never fall back to its own
|
|
599
|
-
* default — which would re-run a step the workflow said not to retry. Backoff
|
|
600
|
-
* defaults to exponential whenever there's at least one retry, so retries ride
|
|
601
|
-
* out a transient outage instead of firing instantly.
|
|
602
|
-
*/
|
|
603
438
|
resolveStepJobOptions(stepOptions) {
|
|
604
439
|
const retries = stepOptions?.retries ?? DEFAULT_STEP_RETRIES;
|
|
605
440
|
const retryDelay = stepOptions?.retryDelay;
|
|
606
|
-
// A concrete retryDelay (15000, '15s') is a fixed backoff; only the literal
|
|
607
|
-
// 'exponential' — or no delay at all — selects exponential.
|
|
608
441
|
const backoff = retryDelay !== undefined && retryDelay !== 'exponential'
|
|
609
442
|
? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
|
|
610
443
|
: retries > 0 || retryDelay === 'exponential'
|
|
@@ -616,25 +449,13 @@ export class PikkuWorkflowService {
|
|
|
616
449
|
const queueService = this.verifyQueueService();
|
|
617
450
|
await queueService.add(this.getStepWorkerQueueName(rpcName), { runId, stepName, rpcName, data, fromStepName }, {
|
|
618
451
|
...this.resolveStepJobOptions(stepOptions),
|
|
619
|
-
// Group by step function, mirroring how per-step queues split them —
|
|
620
|
-
// one slow step function can't monopolise the shared step worker.
|
|
621
452
|
group: this.getJobGroup(rpcName),
|
|
622
453
|
});
|
|
623
454
|
}
|
|
624
|
-
/**
|
|
625
|
-
* Execute a workflow sleep step completion
|
|
626
|
-
* Sets the step result to null and resumes the workflow
|
|
627
|
-
* @param data - Sleeper input data
|
|
628
|
-
*/
|
|
629
455
|
async executeWorkflowSleepCompleted(runId, stepId) {
|
|
630
456
|
await this.setStepResult(stepId, null);
|
|
631
457
|
await this.resumeWorkflow(runId);
|
|
632
458
|
}
|
|
633
|
-
/**
|
|
634
|
-
* Schedule orchestrator retry with delay
|
|
635
|
-
* @param runId - Run ID
|
|
636
|
-
* @param retryDelay - Delay in milliseconds or duration string (optional)
|
|
637
|
-
*/
|
|
638
459
|
async scheduleOrchestratorRetry(runId, retryDelay, workflowName) {
|
|
639
460
|
const queueService = this.verifyQueueService();
|
|
640
461
|
if (!workflowName) {
|
|
@@ -648,25 +469,7 @@ export class PikkuWorkflowService {
|
|
|
648
469
|
group: this.getJobGroup(workflowName),
|
|
649
470
|
});
|
|
650
471
|
}
|
|
651
|
-
/**
|
|
652
|
-
* Dispatch a workflow step to be executed asynchronously.
|
|
653
|
-
*
|
|
654
|
-
* Default implementation enqueues a step worker job via the queue service.
|
|
655
|
-
* Subclasses with non-queue transports (e.g. Durable Objects) override this
|
|
656
|
-
* to dispatch via their own mechanism (RPC to a step worker, etc.).
|
|
657
|
-
*
|
|
658
|
-
* On return, the workflow is paused via `WorkflowAsyncException` thrown by
|
|
659
|
-
* the caller; the step transport is responsible for calling back into the
|
|
660
|
-
* orchestrator (via `resumeWorkflow` or equivalent) when the step completes.
|
|
661
|
-
*
|
|
662
|
-
* @returns true if dispatch was async (caller should pause), false to fall
|
|
663
|
-
* through to the inline execution path.
|
|
664
|
-
*/
|
|
665
472
|
async dispatchStep(runId, stepName, rpcName, data, stepOptions, fromStepName) {
|
|
666
|
-
// Step execution is decided purely by the function's `workflowQueued` flag
|
|
667
|
-
// (default false). Only a function explicitly marked `workflowQueued: true`
|
|
668
|
-
// dispatches via the queue. If the queue service is not configured that is
|
|
669
|
-
// a hard error — there is no inline fallback.
|
|
670
473
|
const functionsMeta = pikkuState(null, 'function', 'meta');
|
|
671
474
|
const rpcFuncId = pikkuState(null, 'rpc', 'meta')[rpcName];
|
|
672
475
|
const rpcMeta = typeof rpcFuncId === 'string' ? functionsMeta[rpcFuncId] : undefined;
|
|
@@ -684,37 +487,18 @@ export class PikkuWorkflowService {
|
|
|
684
487
|
});
|
|
685
488
|
}
|
|
686
489
|
catch (cause) {
|
|
687
|
-
// The queue is down/unreachable — NOT a step failure. Surface it as a
|
|
688
|
-
// transient dispatch error so the caller leaves the step `pending` and the
|
|
689
|
-
// orchestrator job is retried (replayed from snapshot) rather than the run
|
|
690
|
-
// being marked failed. `add` already throws on failure in every adapter.
|
|
691
490
|
throw new WorkflowDispatchException(runId, stepName, { cause });
|
|
692
491
|
}
|
|
693
492
|
return true;
|
|
694
493
|
}
|
|
695
|
-
/**
|
|
696
|
-
* Schedule a workflow sleep wakeup at the given duration.
|
|
697
|
-
*
|
|
698
|
-
* Default implementation uses the scheduler service to enqueue a delayed
|
|
699
|
-
* sleeper RPC. Subclasses with native timer primitives (e.g. Durable Object
|
|
700
|
-
* alarms) override this to schedule directly without going through queues.
|
|
701
|
-
*
|
|
702
|
-
* @returns true if the wakeup was scheduled remotely (caller should pause),
|
|
703
|
-
* false to fall through to inline `setTimeout` behavior.
|
|
704
|
-
*/
|
|
705
494
|
async scheduleSleep(runId, stepId, duration) {
|
|
706
|
-
if (this.isInline(runId) ||
|
|
495
|
+
if ((await this.isInline(runId)) ||
|
|
496
|
+
!getSingletonServices()?.schedulerService) {
|
|
707
497
|
return false;
|
|
708
498
|
}
|
|
709
499
|
await getSingletonServices().schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, { runId, stepId });
|
|
710
500
|
return true;
|
|
711
501
|
}
|
|
712
|
-
/**
|
|
713
|
-
* Install the one extension a run may have, built from a handle onto the run
|
|
714
|
-
* engine so that `inlineStep` and friends stay protected rather than becoming
|
|
715
|
-
* public API. Returns the extension, so the caller keeps a typed reference to
|
|
716
|
-
* whatever it just built.
|
|
717
|
-
*/
|
|
718
502
|
setRunExtension(create) {
|
|
719
503
|
const engine = {
|
|
720
504
|
inlineStep: this.inlineStep.bind(this),
|
|
@@ -729,17 +513,7 @@ export class PikkuWorkflowService {
|
|
|
729
513
|
getRunExtension() {
|
|
730
514
|
return this.runExtension;
|
|
731
515
|
}
|
|
732
|
-
/**
|
|
733
|
-
* Start a new workflow run
|
|
734
|
-
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
735
|
-
* @param options.inline - If true, execute workflow directly without queue service
|
|
736
|
-
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
737
|
-
* @param options.onRunCreated - Called with the run id the moment the run exists.
|
|
738
|
-
* An inline run that fails throws instead of returning, so this is the only
|
|
739
|
-
* way a caller can still read that run back — its steps, and which one failed.
|
|
740
|
-
*/
|
|
741
516
|
async startWorkflow(name, input, wire, rpcService, options) {
|
|
742
|
-
// Resolve workflow from static meta (root or addon namespace)
|
|
743
517
|
const resolved = resolveWorkflowMeta(name);
|
|
744
518
|
const workflowMeta = resolved?.meta;
|
|
745
519
|
const packageName = resolved?.packageName ?? null;
|
|
@@ -750,7 +524,6 @@ export class PikkuWorkflowService {
|
|
|
750
524
|
const shouldInline = options?.inline || !getSingletonServices()?.queueService;
|
|
751
525
|
return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
|
|
752
526
|
}
|
|
753
|
-
// DSL workflow - check registration exists
|
|
754
527
|
const registrations = pikkuState(packageName, 'workflows', 'registrations');
|
|
755
528
|
const workflow = registrations.get(resolved?.resolvedName ?? name);
|
|
756
529
|
if (!workflow) {
|
|
@@ -775,24 +548,16 @@ export class PikkuWorkflowService {
|
|
|
775
548
|
if (error.name !== 'WorkflowAsyncException' &&
|
|
776
549
|
error.name !== 'WorkflowCancelledException' &&
|
|
777
550
|
error.name !== 'WorkflowSuspendedException' &&
|
|
778
|
-
// Transient queue failure — leave the run resumable, don't fail it.
|
|
779
551
|
error.name !== 'WorkflowDispatchException') {
|
|
780
552
|
await this.updateRunStatus(runId, 'failed', undefined, {
|
|
781
553
|
name: error.name,
|
|
782
554
|
message: error.message,
|
|
783
555
|
stack: error.stack,
|
|
784
556
|
});
|
|
785
|
-
// An expected failure (a PikkuError, e.g. a build gate tripping) —
|
|
786
|
-
// its message is the whole story, so don't dump the stack. The
|
|
787
|
-
// `expected` flag survives the step-boundary rehydration that strips
|
|
788
|
-
// the class. Anything else is an uncaught/unexpected error: log it in
|
|
789
|
-
// full so the trace is there to debug.
|
|
790
557
|
getSingletonServices().logger.error(`Workflow ${name} (run ${runId}) failed:`, isExpectedError(error) ? error.message : error);
|
|
791
558
|
throw error;
|
|
792
559
|
}
|
|
793
560
|
if (error.name === 'WorkflowDispatchException') {
|
|
794
|
-
// Rethrow so the caller (poll loop / starter) sees the transient
|
|
795
|
-
// failure; the run stays running and can be resumed.
|
|
796
561
|
throw error;
|
|
797
562
|
}
|
|
798
563
|
}
|
|
@@ -817,15 +582,6 @@ export class PikkuWorkflowService {
|
|
|
817
582
|
}
|
|
818
583
|
return run.output;
|
|
819
584
|
}
|
|
820
|
-
/**
|
|
821
|
-
* Read a run until it reaches an end state, backing off as it drags on.
|
|
822
|
-
*
|
|
823
|
-
* A fixed interval is wrong at both ends: it makes a workflow that finished
|
|
824
|
-
* in milliseconds wait out the whole interval anyway, and it keeps reading a
|
|
825
|
-
* long-running one at full rate for as long as it lasts. Starting short and
|
|
826
|
-
* growing to `maxIntervalMs` returns quick runs promptly while a slow run's
|
|
827
|
-
* read cost grows logarithmically rather than linearly with its duration.
|
|
828
|
-
*/
|
|
829
585
|
async awaitRunEnd(runId, maxIntervalMs) {
|
|
830
586
|
let interval = Math.min(WORKFLOW_POLL_MIN_MS, maxIntervalMs);
|
|
831
587
|
while (true) {
|
|
@@ -840,53 +596,25 @@ export class PikkuWorkflowService {
|
|
|
840
596
|
interval = Math.min(interval * WORKFLOW_POLL_FACTOR, maxIntervalMs);
|
|
841
597
|
}
|
|
842
598
|
}
|
|
843
|
-
/**
|
|
844
|
-
* Wait between two reads of a run.
|
|
845
|
-
*
|
|
846
|
-
* Its own method so the backoff schedule can be asserted on directly. Timing
|
|
847
|
-
* a poll loop by the clock measures the host's scheduler as much as the
|
|
848
|
-
* policy — `setTimeout(40)` routinely returns late on a loaded runner — which
|
|
849
|
-
* makes the obvious test both slow and flaky.
|
|
850
|
-
*/
|
|
851
599
|
async waitBeforeNextRead(ms) {
|
|
852
600
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
853
601
|
}
|
|
854
|
-
/**
|
|
855
|
-
* Every step of a run in one read, or `null` if this backend has no bulk read.
|
|
856
|
-
*
|
|
857
|
-
* A replay walks the DSL body from the top, and each step it passes asks for
|
|
858
|
-
* its own row — so a run of N steps costs N reads per replay and O(N^2) over
|
|
859
|
-
* its lifetime. Backends that can answer this in a single query collapse that
|
|
860
|
-
* to one read per replay.
|
|
861
|
-
*/
|
|
862
602
|
async listStepStates(_runId) {
|
|
863
603
|
return null;
|
|
864
604
|
}
|
|
865
|
-
/**
|
|
866
|
-
* Begin a replay pass: fresh ordinal counters, and one read of the steps the
|
|
867
|
-
* run has already taken so the walk back to where it left off is served from
|
|
868
|
-
* memory. Safe because a pass reaches each step key at most once, and the
|
|
869
|
-
* steps it replays past are `succeeded` and therefore immutable.
|
|
870
|
-
*/
|
|
871
605
|
async beginReplay(runId) {
|
|
872
|
-
const context = this.
|
|
873
|
-
context.
|
|
606
|
+
const context = this.enterExecution(runId);
|
|
607
|
+
context.ordinals = new Map();
|
|
608
|
+
context.lastStep = undefined;
|
|
609
|
+
context.replay = {};
|
|
874
610
|
const steps = await this.listStepStates(runId);
|
|
875
611
|
if (steps) {
|
|
876
612
|
context.replay.steps = new Map(steps.map((step) => [step.stepName, step]));
|
|
877
613
|
}
|
|
878
614
|
}
|
|
879
615
|
endReplay(runId) {
|
|
880
|
-
|
|
881
|
-
if (!context)
|
|
882
|
-
return;
|
|
883
|
-
context.replay = undefined;
|
|
884
|
-
this.releaseContext(runId);
|
|
616
|
+
this.exitExecution(runId);
|
|
885
617
|
}
|
|
886
|
-
/**
|
|
887
|
-
* The step row for `stepName`, creating it if the run has not reached it
|
|
888
|
-
* before. Served from the replay snapshot when one is loaded.
|
|
889
|
-
*/
|
|
890
618
|
async loadOrCreateStep(runId, stepName, create) {
|
|
891
619
|
const snapshot = this.runContexts.get(runId)?.replay?.steps;
|
|
892
620
|
if (snapshot) {
|
|
@@ -900,7 +628,7 @@ export class PikkuWorkflowService {
|
|
|
900
628
|
return await this.getStepState(runId, stepName);
|
|
901
629
|
}
|
|
902
630
|
catch {
|
|
903
|
-
//
|
|
631
|
+
// knowledge: decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md
|
|
904
632
|
}
|
|
905
633
|
}
|
|
906
634
|
let step;
|
|
@@ -908,9 +636,6 @@ export class PikkuWorkflowService {
|
|
|
908
636
|
step = await create();
|
|
909
637
|
}
|
|
910
638
|
catch (error) {
|
|
911
|
-
// A concurrent replay of this run created the row after the snapshot was
|
|
912
|
-
// taken. Its state is the truth; if it isn't really there, the insert
|
|
913
|
-
// failed for its own reasons and that error is the one worth seeing.
|
|
914
639
|
try {
|
|
915
640
|
step = await this.getStepState(runId, stepName);
|
|
916
641
|
}
|
|
@@ -921,15 +646,6 @@ export class PikkuWorkflowService {
|
|
|
921
646
|
snapshot?.set(stepName, step);
|
|
922
647
|
return step;
|
|
923
648
|
}
|
|
924
|
-
/**
|
|
925
|
-
* The run's immutable half — which workflow it is, the wire it was started
|
|
926
|
-
* on, its input. `getRun` is otherwise called several times per step for
|
|
927
|
-
* answers that were all fixed at creation, so a replay reads it once and
|
|
928
|
-
* hands the same object to everyone who only needs that half.
|
|
929
|
-
*
|
|
930
|
-
* Anyone who needs `status`, `output`, `error` or `state` must call `getRun`:
|
|
931
|
-
* those move while the run executes, and a cached copy would be a lie.
|
|
932
|
-
*/
|
|
933
649
|
async getRunIdentity(runId) {
|
|
934
650
|
const replay = this.runContexts.get(runId)?.replay;
|
|
935
651
|
if (replay?.run) {
|
|
@@ -941,30 +657,18 @@ export class PikkuWorkflowService {
|
|
|
941
657
|
}
|
|
942
658
|
return run;
|
|
943
659
|
}
|
|
944
|
-
/** The step the DSL walk last reached (the predecessor for the next step). */
|
|
945
660
|
lastStepName(runId) {
|
|
946
|
-
return this.runContexts.get(runId)?.
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Physical, replay-stable key for the Nth reach of `logicalStepName` in a run:
|
|
950
|
-
* bare name for the first reach (ordinal 0, unchanged behavior), `name#N` for
|
|
951
|
-
* repeats — so the same literal step name can be invoked multiple times without
|
|
952
|
-
* the rows clobbering. Deterministic given a deterministic DSL body.
|
|
953
|
-
*/
|
|
661
|
+
return this.runContexts.get(runId)?.lastStep;
|
|
662
|
+
}
|
|
954
663
|
nextStepKey(runId, logicalStepName) {
|
|
955
664
|
const context = this.contextFor(runId);
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
});
|
|
959
|
-
const ordinal = replay.ordinals.get(logicalStepName) ?? 0;
|
|
960
|
-
replay.ordinals.set(logicalStepName, ordinal + 1);
|
|
665
|
+
const ordinal = context.ordinals.get(logicalStepName) ?? 0;
|
|
666
|
+
context.ordinals.set(logicalStepName, ordinal + 1);
|
|
961
667
|
const stepName = ordinal === 0 ? logicalStepName : `${logicalStepName}#${ordinal}`;
|
|
962
|
-
|
|
668
|
+
context.lastStep = stepName;
|
|
963
669
|
return stepName;
|
|
964
670
|
}
|
|
965
671
|
async runWorkflowJob(runId, rpcService) {
|
|
966
|
-
// Fresh ordinal counters per replay so step keys are deterministic, and one
|
|
967
|
-
// read of the steps the run has already taken.
|
|
968
672
|
await this.beginReplay(runId);
|
|
969
673
|
try {
|
|
970
674
|
await this.runWorkflowJobInner(runId, rpcService);
|
|
@@ -974,8 +678,6 @@ export class PikkuWorkflowService {
|
|
|
974
678
|
}
|
|
975
679
|
}
|
|
976
680
|
async runWorkflowJobInner(runId, rpcService) {
|
|
977
|
-
// Caches the run for the rest of this replay, so the steps it walks don't
|
|
978
|
-
// each re-read the workflow name and wire it already has.
|
|
979
681
|
const run = await this.getRunIdentity(runId);
|
|
980
682
|
if (!run) {
|
|
981
683
|
throw new WorkflowRunNotFoundError(runId);
|
|
@@ -1031,9 +733,6 @@ export class PikkuWorkflowService {
|
|
|
1031
733
|
wire,
|
|
1032
734
|
packageName: pkgName,
|
|
1033
735
|
};
|
|
1034
|
-
// `interrupted` means the run has not reached a terminal state — it is
|
|
1035
|
-
// suspended or waiting — so teardown would run while the run is still
|
|
1036
|
-
// mid-flight.
|
|
1037
736
|
let outcome = 'completed';
|
|
1038
737
|
let failure;
|
|
1039
738
|
try {
|
|
@@ -1124,24 +823,21 @@ export class PikkuWorkflowService {
|
|
|
1124
823
|
}
|
|
1125
824
|
await runFromMeta(this, run.id, version.graph, rpcService);
|
|
1126
825
|
}
|
|
1127
|
-
/**
|
|
1128
|
-
* Execute a single workflow step (called by worker)
|
|
1129
|
-
* Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
|
|
1130
|
-
*/
|
|
1131
826
|
async executeWorkflowStep(runId, stepName, rpcName, data, rpcService) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
827
|
+
this.enterExecution(runId);
|
|
828
|
+
try {
|
|
829
|
+
await this.executeWorkflowStepInner(runId, stepName, rpcName, data, rpcService);
|
|
830
|
+
}
|
|
831
|
+
finally {
|
|
832
|
+
this.exitExecution(runId);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async executeWorkflowStepInner(runId, stepName, rpcName, data, rpcService) {
|
|
1138
836
|
const claimed = await this.withStepLock(runId, stepName, async () => {
|
|
1139
837
|
const stepState = await this.getStepState(runId, stepName);
|
|
1140
|
-
// Already succeeded, or already claimed by another worker — nothing to do.
|
|
1141
838
|
if (stepState.status === 'succeeded' || stepState.status === 'running') {
|
|
1142
839
|
return null;
|
|
1143
840
|
}
|
|
1144
|
-
// A 'failed' status means this is a retry — start a fresh 'running' attempt.
|
|
1145
841
|
if (stepState.status === 'failed') {
|
|
1146
842
|
return this.createRetryAttempt(stepState.stepId, 'running');
|
|
1147
843
|
}
|
|
@@ -1150,7 +846,6 @@ export class PikkuWorkflowService {
|
|
|
1150
846
|
}
|
|
1151
847
|
return stepState;
|
|
1152
848
|
});
|
|
1153
|
-
// Nothing to execute: already succeeded, or another worker owns this step.
|
|
1154
849
|
if (!claimed) {
|
|
1155
850
|
return;
|
|
1156
851
|
}
|
|
@@ -1164,8 +859,6 @@ export class PikkuWorkflowService {
|
|
|
1164
859
|
const meta = pikkuState(null, 'workflows', 'meta');
|
|
1165
860
|
const workflowMeta = meta[run.workflow];
|
|
1166
861
|
const isGraphWorkflow = workflowMeta?.source === 'graph';
|
|
1167
|
-
// Map the physical step key back to its logical node: a revisit instance
|
|
1168
|
-
// is `node#N` (ordinal), which isn't a literal key in `nodes`.
|
|
1169
862
|
let graphNodeId;
|
|
1170
863
|
if (isGraphWorkflow && workflowMeta?.nodes) {
|
|
1171
864
|
if (stepName in workflowMeta.nodes) {
|
|
@@ -1182,7 +875,6 @@ export class PikkuWorkflowService {
|
|
|
1182
875
|
result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, rpcName, data, run.workflow);
|
|
1183
876
|
}
|
|
1184
877
|
else {
|
|
1185
|
-
// Check if rpcName refers to a sub-workflow
|
|
1186
878
|
const subWorkflowMeta = meta[rpcName];
|
|
1187
879
|
if (subWorkflowMeta) {
|
|
1188
880
|
const childWire = {
|
|
@@ -1213,9 +905,7 @@ export class PikkuWorkflowService {
|
|
|
1213
905
|
result = await this.invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService, run);
|
|
1214
906
|
}
|
|
1215
907
|
}
|
|
1216
|
-
// Store result and mark succeeded
|
|
1217
908
|
await this.setStepResult(stepState.stepId, result);
|
|
1218
|
-
// Resume orchestrator to continue workflow
|
|
1219
909
|
await this.resumeWorkflow(runId);
|
|
1220
910
|
}
|
|
1221
911
|
catch (error) {
|
|
@@ -1231,25 +921,17 @@ export class PikkuWorkflowService {
|
|
|
1231
921
|
});
|
|
1232
922
|
return;
|
|
1233
923
|
}
|
|
1234
|
-
// Store error and mark failed
|
|
1235
924
|
await this.setStepError(stepState.stepId, error);
|
|
1236
925
|
const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1;
|
|
1237
926
|
const retriesExhausted = stepState.attemptCount >= maxAttempts;
|
|
1238
927
|
if (retriesExhausted) {
|
|
1239
|
-
// No more retries - resume orchestrator to mark workflow as failed
|
|
1240
928
|
await this.resumeWorkflow(runId);
|
|
1241
929
|
}
|
|
1242
|
-
// Always throw so queue knows the job failed and can retry if needed
|
|
1243
930
|
throw error;
|
|
1244
931
|
}
|
|
1245
932
|
}
|
|
1246
|
-
/**
|
|
1247
|
-
* Orchestrate workflow execution (called by orchestrator)
|
|
1248
|
-
* Runs workflow job and handles async exceptions
|
|
1249
|
-
*/
|
|
1250
933
|
async orchestrateWorkflow(runId, rpcService) {
|
|
1251
934
|
try {
|
|
1252
|
-
// Run workflow job (replays with caching)
|
|
1253
935
|
await this.runWorkflowJob(runId, rpcService);
|
|
1254
936
|
}
|
|
1255
937
|
catch (error) {
|
|
@@ -1259,9 +941,6 @@ export class PikkuWorkflowService {
|
|
|
1259
941
|
return;
|
|
1260
942
|
}
|
|
1261
943
|
if (error.name === 'WorkflowDispatchException') {
|
|
1262
|
-
// Transient: the queue was unreachable, not a workflow failure. Leave the
|
|
1263
|
-
// run running and rethrow so the orchestrator job is redelivered and the
|
|
1264
|
-
// workflow replays from its snapshot. Do NOT mark the run failed.
|
|
1265
944
|
getSingletonServices().logger.warn(`Workflow run ${runId} could not dispatch a step (queue unavailable); leaving run for orchestrator retry`, error);
|
|
1266
945
|
throw error;
|
|
1267
946
|
}
|
|
@@ -1279,14 +958,7 @@ export class PikkuWorkflowService {
|
|
|
1279
958
|
}
|
|
1280
959
|
return getSingletonServices().queueService;
|
|
1281
960
|
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Invoke a step's RPC with the workflow-step wire (step identity + provenance).
|
|
1284
|
-
* Identical for the queue executor and the inline executor — the only thing
|
|
1285
|
-
* that differs between transports is who calls it, not the call itself.
|
|
1286
|
-
*/
|
|
1287
961
|
async invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService, knownRun) {
|
|
1288
|
-
// Carry the run's pikkuUserId onto the step wire so authed steps rehydrate their
|
|
1289
|
-
// session on the queued path too (the bare job wire lacks it; inline already has it).
|
|
1290
962
|
const run = knownRun ?? (await this.getRunIdentity(runId));
|
|
1291
963
|
return rpcService.rpcWithWire(rpcName, data, {
|
|
1292
964
|
...(run?.wire?.pikkuUserId ? { pikkuUserId: run.wire.pikkuUserId } : {}),
|
|
@@ -1301,16 +973,6 @@ export class PikkuWorkflowService {
|
|
|
1301
973
|
},
|
|
1302
974
|
});
|
|
1303
975
|
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Inline (straight-through) step execution with an in-process retry loop —
|
|
1306
|
-
* shared by inline RPC steps and inline function steps. Same scaffolding
|
|
1307
|
-
* (running → result, or fail → retry-attempt → backoff → retry) wrapped
|
|
1308
|
-
* around a step-specific `doWork` body. Stays O(K): no suspend/replay.
|
|
1309
|
-
*
|
|
1310
|
-
* `onError` is an optional hook for terminal errors that must NOT retry
|
|
1311
|
-
* (e.g. RPC-not-found → suspend the run for redeploy). If it throws, the
|
|
1312
|
-
* loop exits immediately without recording a step error or retrying.
|
|
1313
|
-
*/
|
|
1314
976
|
async runInlineRetryLoop(stepState, retries, retryDelay, doWork, onError) {
|
|
1315
977
|
let currentStepState = stepState;
|
|
1316
978
|
while (true) {
|
|
@@ -1323,81 +985,49 @@ export class PikkuWorkflowService {
|
|
|
1323
985
|
catch (error) {
|
|
1324
986
|
if (onError)
|
|
1325
987
|
await onError(error);
|
|
1326
|
-
// Record the error (marks step as failed)
|
|
1327
988
|
await this.setStepError(currentStepState.stepId, error);
|
|
1328
989
|
if (currentStepState.attemptCount < retries) {
|
|
1329
|
-
// Create a new pending retry attempt, then back off if configured.
|
|
1330
990
|
currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
1331
991
|
if (retryDelay) {
|
|
1332
992
|
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
|
|
1333
993
|
}
|
|
1334
|
-
// Continue loop to retry
|
|
1335
994
|
}
|
|
1336
995
|
else {
|
|
1337
|
-
// No more retries, fail the workflow
|
|
1338
996
|
throw error;
|
|
1339
997
|
}
|
|
1340
998
|
}
|
|
1341
999
|
}
|
|
1342
1000
|
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Run a failed step's compensation handler as a durable step of its own.
|
|
1345
|
-
*
|
|
1346
|
-
* Durable rather than a bare invoke so a replay does not compensate twice —
|
|
1347
|
-
* a handler is typically a refund or a rollback. `onError` is deliberately
|
|
1348
|
-
* not forwarded: a compensation handler cannot itself compensate.
|
|
1349
|
-
*/
|
|
1350
1001
|
async runStepCompensation(runId, stepName, onErrorRpcName, rpcService, error) {
|
|
1351
1002
|
await this.rpcStep(runId, `${stepName}:onError`, onErrorRpcName, { error: { message: error.message } }, rpcService, { retries: 0 });
|
|
1352
1003
|
}
|
|
1353
1004
|
async rpcStep(runId, logicalStepName, rpcName, data, rpcService, stepOptions) {
|
|
1354
|
-
// Capture the predecessor before nextStepKey advances the lineage to us.
|
|
1355
1005
|
const fromStepName = this.lastStepName(runId);
|
|
1356
1006
|
const stepName = this.nextStepKey(runId, logicalStepName);
|
|
1357
|
-
// Resolve the retry policy ONCE here so the value persisted on the step
|
|
1358
|
-
// (which drives `retriesExhausted`) is the same one the queue dispatch turns
|
|
1359
|
-
// into `attempts`. Without this the queue could retry N times while the
|
|
1360
|
-
// engine thinks retries are already exhausted (or vice-versa).
|
|
1361
1007
|
const resolvedStepOptions = {
|
|
1362
1008
|
retries: stepOptions?.retries ?? DEFAULT_STEP_RETRIES,
|
|
1363
1009
|
retryDelay: stepOptions?.retryDelay,
|
|
1364
1010
|
actor: stepOptions?.actor,
|
|
1365
1011
|
onError: stepOptions?.onError,
|
|
1366
1012
|
};
|
|
1367
|
-
// Reuse the step if the run already reached it, otherwise create it.
|
|
1368
1013
|
const stepState = await this.loadOrCreateStep(runId, stepName, () => this.insertStepState(runId, stepName, rpcName, data, resolvedStepOptions, fromStepName));
|
|
1369
1014
|
if (stepState.status === 'succeeded') {
|
|
1370
|
-
// Return cached result
|
|
1371
1015
|
return stepState.result;
|
|
1372
1016
|
}
|
|
1373
1017
|
if (stepState.status === 'failed') {
|
|
1374
|
-
// Step failed with retries exhausted - throw error to fail the workflow
|
|
1375
1018
|
const error = new Error(stepState.error?.message ||
|
|
1376
1019
|
`Step '${stepName}' failed after exhausting all retries`);
|
|
1377
|
-
// Compensation, mirroring a graph node's onError: run the handler, then
|
|
1378
|
-
// still throw — the workflow fails either way.
|
|
1379
1020
|
if (resolvedStepOptions.onError) {
|
|
1380
1021
|
await this.runStepCompensation(runId, stepName, resolvedStepOptions.onError, rpcService, error);
|
|
1381
1022
|
}
|
|
1382
|
-
// Preserve original error properties if available
|
|
1383
1023
|
if (stepState.error) {
|
|
1384
1024
|
Object.assign(error, stepState.error);
|
|
1385
1025
|
}
|
|
1386
1026
|
throw error;
|
|
1387
1027
|
}
|
|
1388
1028
|
if (stepState.status === 'scheduled') {
|
|
1389
|
-
// Step is already scheduled, pause workflow
|
|
1390
1029
|
throw new WorkflowAsyncException(runId, stepName);
|
|
1391
1030
|
}
|
|
1392
|
-
// Hand off to subclass-overridable transport. Default behavior enqueues
|
|
1393
|
-
// via the queue service; DO-style subclasses RPC to a step worker.
|
|
1394
|
-
// Dispatch BEFORE marking the step `scheduled`: if the queue is down,
|
|
1395
|
-
// dispatchStep throws WorkflowDispatchException and the step stays `pending`,
|
|
1396
|
-
// so the orchestrator's next replay re-dispatches it. Marking `scheduled`
|
|
1397
|
-
// first would strand the step (replay sees `scheduled`, pauses, never
|
|
1398
|
-
// re-enqueues the job that was never created).
|
|
1399
|
-
// Actor steps never queue: they are outbound HTTP calls made by the
|
|
1400
|
-
// runner itself, and the actor's session lives on this process.
|
|
1401
1031
|
const dispatched = resolvedStepOptions.actor
|
|
1402
1032
|
? false
|
|
1403
1033
|
: await this.dispatchStep(runId, stepName, rpcName, data, resolvedStepOptions, fromStepName);
|
|
@@ -1405,18 +1035,12 @@ export class PikkuWorkflowService {
|
|
|
1405
1035
|
await this.setStepScheduled(stepState.stepId);
|
|
1406
1036
|
throw new WorkflowAsyncException(runId, stepName);
|
|
1407
1037
|
}
|
|
1408
|
-
// Inline (no transport available) - execute locally with the shared retry
|
|
1409
|
-
// loop. The body resolves to a sub-workflow result or a plain RPC result.
|
|
1410
1038
|
const retries = resolvedStepOptions.retries ?? this.getConfig().retries;
|
|
1411
1039
|
const retryDelay = resolvedStepOptions.retryDelay;
|
|
1412
1040
|
return this.runInlineRetryLoop(stepState, retries, retryDelay, async (currentStepState) => {
|
|
1413
|
-
// Actor step: send through the actor's authenticated client over the
|
|
1414
|
-
// REAL transport. Never falls back to internal dispatch — that would
|
|
1415
|
-
// bypass auth and fake a green health check.
|
|
1416
1041
|
if (resolvedStepOptions.actor) {
|
|
1417
1042
|
return resolvedStepOptions.actor.invoke(rpcName, data);
|
|
1418
1043
|
}
|
|
1419
|
-
// Check if the name refers to a workflow
|
|
1420
1044
|
const workflowMeta = pikkuState(null, 'workflows', 'meta')[rpcName];
|
|
1421
1045
|
if (workflowMeta) {
|
|
1422
1046
|
const childWire = {
|
|
@@ -1427,7 +1051,6 @@ export class PikkuWorkflowService {
|
|
|
1427
1051
|
};
|
|
1428
1052
|
const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: true });
|
|
1429
1053
|
await this.setStepChildRunId(currentStepState.stepId, childRunId);
|
|
1430
|
-
// Poll until child workflow completes
|
|
1431
1054
|
const childRun = await this.awaitRunEnd(childRunId, WORKFLOW_CHILD_POLL_MAX_MS);
|
|
1432
1055
|
if (childRun.status === 'failed') {
|
|
1433
1056
|
throw new Error(childRun.error?.message || 'Sub-workflow failed');
|
|
@@ -1448,40 +1071,19 @@ export class PikkuWorkflowService {
|
|
|
1448
1071
|
}
|
|
1449
1072
|
});
|
|
1450
1073
|
}
|
|
1451
|
-
async inlineStep(runId, logicalStepName, fn, stepOptions,
|
|
1452
|
-
/**
|
|
1453
|
-
* The input this step was called with, recorded on the run so a reporter can
|
|
1454
|
-
* name the values under test. A closure step has none; a scenario step does.
|
|
1455
|
-
*/
|
|
1456
|
-
data = null,
|
|
1457
|
-
/**
|
|
1458
|
-
* The name this step was dispatched by, for the kinds of inline step that
|
|
1459
|
-
* have one. A closure step has no name; a scenario step is a step RPC, so
|
|
1460
|
-
* it records the step function that ran — which is the only way to join a
|
|
1461
|
-
* step back to its declaration when its durable name was built at runtime
|
|
1462
|
-
* (a step called in a loop reaches the run as `sees @pikku/addon-todos`,
|
|
1463
|
-
* declared as `sees ${packageName}`).
|
|
1464
|
-
*/
|
|
1465
|
-
rpcName = null) {
|
|
1074
|
+
async inlineStep(runId, logicalStepName, fn, stepOptions, data = null, rpcName = null) {
|
|
1466
1075
|
const fromStepName = this.lastStepName(runId);
|
|
1467
1076
|
const stepName = this.nextStepKey(runId, logicalStepName);
|
|
1468
|
-
// Reuse the step if the run already reached it, otherwise create it
|
|
1469
|
-
// (inline, so never dispatched).
|
|
1470
1077
|
const stepState = await this.loadOrCreateStep(runId, stepName, () => this.insertStepState(runId, stepName, rpcName, data, stepOptions, fromStepName));
|
|
1471
1078
|
if (stepState.status === 'succeeded') {
|
|
1472
|
-
// Return cached result
|
|
1473
1079
|
return stepState.result;
|
|
1474
1080
|
}
|
|
1475
|
-
// Execute inline function
|
|
1476
1081
|
const retries = stepOptions?.retries ?? this.getConfig().retries;
|
|
1477
1082
|
const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay;
|
|
1478
|
-
|
|
1479
|
-
if (this.isInline(runId)) {
|
|
1480
|
-
// Inline mode - execute with the shared in-process retry loop.
|
|
1083
|
+
if (await this.isInline(runId)) {
|
|
1481
1084
|
return this.runInlineRetryLoop(stepState, retries, retryDelay, () => fn());
|
|
1482
1085
|
}
|
|
1483
1086
|
else {
|
|
1484
|
-
// Remote mode - single attempt, then suspend for orchestrator-driven retry.
|
|
1485
1087
|
let currentStepState = stepState;
|
|
1486
1088
|
try {
|
|
1487
1089
|
await this.setStepRunning(currentStepState.stepId);
|
|
@@ -1490,18 +1092,12 @@ export class PikkuWorkflowService {
|
|
|
1490
1092
|
return result;
|
|
1491
1093
|
}
|
|
1492
1094
|
catch (error) {
|
|
1493
|
-
// Record the error (marks step as failed)
|
|
1494
1095
|
await this.setStepError(currentStepState.stepId, error);
|
|
1495
|
-
// Check if we should retry
|
|
1496
1096
|
if (currentStepState.attemptCount < retries) {
|
|
1497
|
-
// Create a new pending retry attempt (copies metadata from failed step)
|
|
1498
1097
|
await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
1499
|
-
// Schedule orchestrator to retry after delay
|
|
1500
1098
|
await this.scheduleOrchestratorRetry(runId, retryDelay);
|
|
1501
|
-
// Pause workflow - orchestrator will replay and pick up new attempt
|
|
1502
1099
|
throw new WorkflowAsyncException(runId, stepName);
|
|
1503
1100
|
}
|
|
1504
|
-
// No more retries, fail the workflow
|
|
1505
1101
|
throw error;
|
|
1506
1102
|
}
|
|
1507
1103
|
}
|
|
@@ -1509,22 +1105,13 @@ export class PikkuWorkflowService {
|
|
|
1509
1105
|
async sleepStep(runId, logicalStepName, duration) {
|
|
1510
1106
|
const fromStepName = this.lastStepName(runId);
|
|
1511
1107
|
const stepName = this.nextStepKey(runId, logicalStepName);
|
|
1512
|
-
// Reuse the step if the run already reached it, otherwise create it
|
|
1513
|
-
// (sleep step, no RPC).
|
|
1514
1108
|
const stepState = await this.loadOrCreateStep(runId, stepName, () => this.insertStepState(runId, stepName, null, { duration }, undefined, fromStepName));
|
|
1515
1109
|
if (stepState.status === 'succeeded') {
|
|
1516
|
-
// Sleep already completed, return immediately
|
|
1517
1110
|
return;
|
|
1518
1111
|
}
|
|
1519
1112
|
if (stepState.status === 'scheduled') {
|
|
1520
|
-
// Sleep is already scheduled, pause workflow
|
|
1521
1113
|
throw new WorkflowAsyncException(runId, stepName);
|
|
1522
1114
|
}
|
|
1523
|
-
// Hand off to subclass-overridable transport. Default behavior schedules
|
|
1524
|
-
// a delayed sleeper RPC via the scheduler service; DO-style subclasses
|
|
1525
|
-
// override to use native timer primitives (e.g. setAlarm). Schedule BEFORE
|
|
1526
|
-
// marking `scheduled` so a scheduler outage leaves the step `pending` for
|
|
1527
|
-
// re-scheduling on replay instead of stranding it (see rpcStep).
|
|
1528
1115
|
let scheduled;
|
|
1529
1116
|
try {
|
|
1530
1117
|
scheduled = await this.scheduleSleep(runId, stepState.stepId, duration);
|
|
@@ -1536,21 +1123,9 @@ export class PikkuWorkflowService {
|
|
|
1536
1123
|
await this.setStepScheduled(stepState.stepId);
|
|
1537
1124
|
throw new WorkflowAsyncException(runId, stepName);
|
|
1538
1125
|
}
|
|
1539
|
-
// Inline mode - use setTimeout with actual duration
|
|
1540
1126
|
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(duration)));
|
|
1541
1127
|
await this.setStepResult(stepState.stepId, null);
|
|
1542
1128
|
}
|
|
1543
|
-
/**
|
|
1544
|
-
* Derive the durable step name for a suspend point from its `reason`, so each
|
|
1545
|
-
* distinct reason is its own step row — letting one workflow have multiple
|
|
1546
|
-
* independent suspends (e.g. wait-for-build, then wait-for-approval), and
|
|
1547
|
-
* supporting dynamic reasons in loops (`suspend(`Wait for ${i}`)`) exactly
|
|
1548
|
-
* like dynamic `do()` step names. The reason is used raw (it's just a text
|
|
1549
|
-
* step name), only namespaced so it can't collide with a `do`/`sleep` step of
|
|
1550
|
-
* the same name. Like `do()` / `sleep()`, the reason is the step's stable
|
|
1551
|
-
* identity: it MUST be derived deterministically so it's the same every time
|
|
1552
|
-
* the workflow replays through that point.
|
|
1553
|
-
*/
|
|
1554
1129
|
getSuspendStepName(reason) {
|
|
1555
1130
|
return `__workflow_suspend:${reason}`;
|
|
1556
1131
|
}
|
|
@@ -1585,16 +1160,6 @@ export class PikkuWorkflowService {
|
|
|
1585
1160
|
throw new WorkflowSuspendedException(runId, reason);
|
|
1586
1161
|
});
|
|
1587
1162
|
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Wake a run later by enqueuing a delayed orchestrator pass. Deliberately NOT
|
|
1590
|
-
* {@link scheduleSleep}: that resolves the step it is given, which for an
|
|
1591
|
-
* approval would resolve the gate itself. This only nudges the run to replay
|
|
1592
|
-
* and re-evaluate — the gate stays the sole judge of its own outcome.
|
|
1593
|
-
*
|
|
1594
|
-
* Best-effort by design. Expiry is decided from the recorded deadline on
|
|
1595
|
-
* replay, so losing this wake costs liveness (the run sits until something
|
|
1596
|
-
* else resumes it), never correctness.
|
|
1597
|
-
*/
|
|
1598
1163
|
async scheduleRunWake(runId, delay) {
|
|
1599
1164
|
try {
|
|
1600
1165
|
const queueService = this.verifyQueueService();
|
|
@@ -1611,20 +1176,9 @@ export class PikkuWorkflowService {
|
|
|
1611
1176
|
this.logger?.warn(`Failed to schedule approval expiry wake for run ${runId}; expiry will still resolve on the next replay`, error);
|
|
1612
1177
|
}
|
|
1613
1178
|
}
|
|
1614
|
-
/**
|
|
1615
|
-
* Durable step name for an approval gate. Namespaced separately from suspend
|
|
1616
|
-
* so the two can't collide, and derived from `reason` for the same reason
|
|
1617
|
-
* {@link getSuspendStepName} is: it must be stable across replays.
|
|
1618
|
-
*/
|
|
1619
1179
|
getApprovalStepName(reason) {
|
|
1620
1180
|
return `__workflow_approval:${reason}`;
|
|
1621
1181
|
}
|
|
1622
|
-
/**
|
|
1623
|
-
* Run-state key holding an approval gate's record. Hex-encoded because the
|
|
1624
|
-
* Mongo backend restricts state keys to `/^[a-zA-Z0-9_]+$/` and a `reason` is
|
|
1625
|
-
* arbitrary human text. One key per gate, so two gates resolving concurrently
|
|
1626
|
-
* can't clobber each other through a read-modify-write.
|
|
1627
|
-
*/
|
|
1628
1182
|
approvalStateKey(stepName) {
|
|
1629
1183
|
let hex = '';
|
|
1630
1184
|
for (const byte of new TextEncoder().encode(stepName)) {
|
|
@@ -1632,31 +1186,15 @@ export class PikkuWorkflowService {
|
|
|
1632
1186
|
}
|
|
1633
1187
|
return `__approval_${hex}`;
|
|
1634
1188
|
}
|
|
1635
|
-
/**
|
|
1636
|
-
* Record a decision against an approval gate and wake the run. Called from
|
|
1637
|
-
* outside the workflow (an HTTP route, an RPC), so the schema value is NOT in
|
|
1638
|
-
* scope here — the payload is stored raw and validated on replay inside the
|
|
1639
|
-
* workflow body, which is the only place the schema exists. An invalid payload
|
|
1640
|
-
* therefore leaves the gate closed rather than failing the run.
|
|
1641
|
-
*
|
|
1642
|
-
* `reason` addresses the first reach of that gate. An approval reached more
|
|
1643
|
-
* than once under the same reason (e.g. in a loop) gets `#N`-suffixed step
|
|
1644
|
-
* rows that this cannot currently target.
|
|
1645
|
-
*/
|
|
1646
1189
|
async approveStep(runId, reason, decision) {
|
|
1647
1190
|
const stepName = this.getApprovalStepName(reason);
|
|
1648
1191
|
const stateKey = this.approvalStateKey(stepName);
|
|
1649
|
-
// A resolved gate returns its cached step result and never re-reads state,
|
|
1650
|
-
// so a decision recorded now would be silently discarded — most obviously
|
|
1651
|
-
// when it loses the race with expiry. Reject instead, so the approver
|
|
1652
|
-
// learns their decision did not land.
|
|
1653
1192
|
let resolved;
|
|
1654
1193
|
try {
|
|
1655
1194
|
resolved = await this.getStepState(runId, stepName);
|
|
1656
1195
|
}
|
|
1657
1196
|
catch {
|
|
1658
|
-
//
|
|
1659
|
-
// ahead of it is legitimate — the gate picks it up on arrival.
|
|
1197
|
+
// knowledge: decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md
|
|
1660
1198
|
}
|
|
1661
1199
|
if (resolved?.stepId && resolved.status === 'succeeded') {
|
|
1662
1200
|
const outcome = resolved.result;
|
|
@@ -1686,8 +1224,6 @@ export class PikkuWorkflowService {
|
|
|
1686
1224
|
if (!stepState.stepId) {
|
|
1687
1225
|
stepState = await this.insertStepState(runId, approvalStepName, 'pikkuWorkflowApproval', { reason, expiry: options.expiry }, undefined, fromStepName);
|
|
1688
1226
|
}
|
|
1689
|
-
// Unlike suspend, `succeeded` here means a decision (or expiry) was
|
|
1690
|
-
// actually resolved, and the step result IS the return channel.
|
|
1691
1227
|
if (stepState.status === 'succeeded') {
|
|
1692
1228
|
return stepState.result;
|
|
1693
1229
|
}
|
|
@@ -1695,8 +1231,6 @@ export class PikkuWorkflowService {
|
|
|
1695
1231
|
let record = ((await this.getRunState(runId))[stateKey] ?? {});
|
|
1696
1232
|
if (stepState.status === 'pending') {
|
|
1697
1233
|
await this.setStepRunning(stepState.stepId);
|
|
1698
|
-
// First reach: stamp the deadline and nudge the run awake when it
|
|
1699
|
-
// passes. The deadline is what's authoritative — see below.
|
|
1700
1234
|
if (options.expiry !== undefined && !record.expiresAt) {
|
|
1701
1235
|
const expiresAt = new Date(Date.now() + getDurationInMilliseconds(options.expiry)).toISOString();
|
|
1702
1236
|
record = { ...record, expiresAt };
|
|
@@ -1707,9 +1241,6 @@ export class PikkuWorkflowService {
|
|
|
1707
1241
|
if (record.decision !== undefined) {
|
|
1708
1242
|
const validation = await options.schema['~standard'].validate(record.decision);
|
|
1709
1243
|
if (validation.issues) {
|
|
1710
|
-
// Drop the bad decision and re-close the gate, leaving the failure
|
|
1711
|
-
// legible to whoever tries next. Failing the run instead would let any
|
|
1712
|
-
// caller kill a workflow with a malformed payload.
|
|
1713
1244
|
await this.updateRunState(runId, stateKey, {
|
|
1714
1245
|
...record,
|
|
1715
1246
|
decision: undefined,
|
|
@@ -1728,9 +1259,6 @@ export class PikkuWorkflowService {
|
|
|
1728
1259
|
await this.setStepResult(stepState.stepId, outcome);
|
|
1729
1260
|
return outcome;
|
|
1730
1261
|
}
|
|
1731
|
-
// Expiry is decided by comparing against the recorded deadline rather than
|
|
1732
|
-
// by the timer having fired, so a duplicate, late, or dropped timer all
|
|
1733
|
-
// produce the same answer.
|
|
1734
1262
|
if (record.expiresAt && Date.now() >= Date.parse(record.expiresAt)) {
|
|
1735
1263
|
const outcome = { status: 'expired' };
|
|
1736
1264
|
await this.setStepResult(stepState.stepId, outcome);
|
|
@@ -1744,7 +1272,6 @@ export class PikkuWorkflowService {
|
|
|
1744
1272
|
name,
|
|
1745
1273
|
runId,
|
|
1746
1274
|
getRun: async () => (await this.getRun(runId)),
|
|
1747
|
-
// Implement workflow.do() - RPC form
|
|
1748
1275
|
do: async (stepName, rpcNameOrFn, dataOrOptions, options) => {
|
|
1749
1276
|
this.verifyStepName(stepName);
|
|
1750
1277
|
if (typeof rpcNameOrFn === 'string') {
|
|
@@ -1757,7 +1284,6 @@ export class PikkuWorkflowService {
|
|
|
1757
1284
|
return await this.inlineStep(runId, stepName, rpcNameOrFn, dataOrOptions);
|
|
1758
1285
|
}
|
|
1759
1286
|
},
|
|
1760
|
-
// Implement workflow.sleep()
|
|
1761
1287
|
sleep: async (stepName, duration) => {
|
|
1762
1288
|
this.verifyStepName(stepName);
|
|
1763
1289
|
await this.sleepStep(runId, stepName, getDurationInMilliseconds(duration));
|
|
@@ -1795,16 +1321,6 @@ export class PikkuWorkflowService {
|
|
|
1795
1321
|
sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
|
|
1796
1322
|
};
|
|
1797
1323
|
}
|
|
1798
|
-
/**
|
|
1799
|
-
* Get the orchestrator queue name for a specific workflow.
|
|
1800
|
-
* Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
|
|
1801
|
-
* falls back to the shared orchestrator queue.
|
|
1802
|
-
*
|
|
1803
|
-
* Reads from `queue.meta` (always populated globally) rather than
|
|
1804
|
-
* `queue.registrations` (only populated for queues this unit consumes).
|
|
1805
|
-
* In a per-unit deploy the orchestrator unit doesn't consume per-step
|
|
1806
|
-
* queues — but it produces to them — so registrations would miss them.
|
|
1807
|
-
*/
|
|
1808
1324
|
getOrchestratorQueueName(workflowName) {
|
|
1809
1325
|
if (workflowName && this.queueStrategy !== 'shared-groups') {
|
|
1810
1326
|
const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`;
|
|
@@ -1825,15 +1341,6 @@ export class PikkuWorkflowService {
|
|
|
1825
1341
|
}
|
|
1826
1342
|
return this.getConfig().stepWorkerQueueName;
|
|
1827
1343
|
}
|
|
1828
|
-
/**
|
|
1829
|
-
* Fairness key for a job on a shared queue. Under `'per-workflow'` the queue
|
|
1830
|
-
* name already isolates workflows, so no group is needed — returning one
|
|
1831
|
-
* anyway would cap a workflow inside its own dedicated queue.
|
|
1832
|
-
*
|
|
1833
|
-
* The tier repeats the id so a workflow can be given its own limit purely
|
|
1834
|
-
* from config, with no per-workflow wiring; an unmatched tier falls back to
|
|
1835
|
-
* the default limit.
|
|
1836
|
-
*/
|
|
1837
1344
|
getJobGroup(id) {
|
|
1838
1345
|
if (!id || this.queueStrategy !== 'shared-groups') {
|
|
1839
1346
|
return undefined;
|