@pikku/core 0.12.72 → 0.12.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1171 -0
- package/dist/column-form.d.ts +32 -0
- package/dist/column-form.js +42 -0
- package/dist/crypto-utils.d.ts +43 -7
- package/dist/crypto-utils.js +163 -42
- package/dist/data-classification.d.ts +44 -0
- package/dist/dev/hot-reload.js +11 -30
- package/dist/dev/module-runner.d.ts +3 -7
- package/dist/dev/module-runner.js +4 -10
- package/dist/dev/reload-meta.d.ts +8 -20
- package/dist/dev/reload-meta.js +9 -29
- package/dist/errors/error-handler.d.ts +5 -30
- package/dist/errors/error-handler.js +16 -32
- package/dist/errors/errors.d.ts +32 -151
- package/dist/errors/errors.js +55 -157
- package/dist/function/abort-scope.d.ts +47 -0
- package/dist/function/abort-scope.js +63 -0
- package/dist/function/function-runner.js +37 -32
- package/dist/function/functions.types.d.ts +57 -136
- package/dist/function/functions.types.js +0 -58
- package/dist/function/index.d.ts +1 -1
- package/dist/function/list.types.d.ts +12 -62
- package/dist/function/list.types.js +4 -25
- package/dist/handle-error.d.ts +0 -13
- package/dist/handle-error.js +0 -18
- package/dist/index.d.ts +11 -5
- package/dist/index.js +7 -2
- package/dist/middleware/auth-apikey.d.ts +3 -18
- package/dist/middleware/auth-apikey.js +0 -17
- package/dist/middleware/auth-bearer.d.ts +6 -41
- package/dist/middleware/auth-bearer.js +5 -41
- package/dist/middleware/auth-cookie.d.ts +5 -27
- package/dist/middleware/auth-cookie.js +2 -26
- package/dist/middleware/cors.d.ts +7 -34
- package/dist/middleware/cors.js +7 -34
- package/dist/middleware/remote-auth.d.ts +3 -1
- package/dist/middleware/remote-auth.js +4 -3
- package/dist/middleware/telemetry.d.ts +8 -33
- package/dist/middleware/telemetry.js +2 -31
- package/dist/middleware-runner.d.ts +4 -55
- package/dist/middleware-runner.js +5 -74
- package/dist/permissions.d.ts +3 -44
- package/dist/permissions.js +19 -71
- package/dist/pikku-request.d.ts +0 -6
- package/dist/pikku-request.js +0 -6
- package/dist/pikku-state.d.ts +0 -26
- package/dist/pikku-state.js +2 -30
- package/dist/remote.d.ts +3 -5
- package/dist/remote.js +9 -8
- package/dist/schema.d.ts +5 -39
- package/dist/schema.js +5 -39
- package/dist/scopes.d.ts +4 -23
- package/dist/scopes.js +7 -48
- package/dist/secret-value.d.ts +56 -0
- package/dist/secret-value.js +46 -0
- package/dist/services/ai-agent-runner-service.d.ts +20 -0
- package/dist/services/ai-embedding-service.d.ts +2 -25
- package/dist/services/audit-service.d.ts +74 -4
- package/dist/services/audit-service.js +8 -7
- package/dist/services/content-service.d.ts +1 -46
- package/dist/services/credential-service.d.ts +3 -40
- package/dist/services/credential-wire-service.d.ts +5 -0
- package/dist/services/credential-wire-service.js +9 -1
- package/dist/services/deployment-service.d.ts +3 -9
- package/dist/services/email-service.d.ts +2 -1
- package/dist/services/gateway-service.d.ts +0 -15
- package/dist/services/http-personas.d.ts +80 -0
- package/dist/services/http-personas.js +233 -0
- package/dist/services/in-memory-queue-service.d.ts +0 -14
- package/dist/services/in-memory-queue-service.js +1 -15
- package/dist/services/in-memory-trigger-service.d.ts +0 -18
- package/dist/services/in-memory-trigger-service.js +1 -18
- package/dist/services/in-memory-workflow-service.d.ts +0 -16
- package/dist/services/in-memory-workflow-service.js +4 -33
- package/dist/services/index.d.ts +8 -9
- package/dist/services/index.js +3 -6
- package/dist/services/istanbul-coverage-service.d.ts +1 -5
- package/dist/services/istanbul-coverage-service.js +2 -8
- package/dist/services/jwt-service.d.ts +1 -16
- package/dist/services/local-content-request-handler.d.ts +29 -0
- package/dist/services/local-content-request-handler.js +176 -0
- package/dist/services/local-content.d.ts +13 -2
- package/dist/services/local-content.js +40 -13
- package/dist/services/local-gateway-service.d.ts +0 -16
- package/dist/services/local-gateway-service.js +2 -17
- package/dist/services/local-secrets.d.ts +4 -7
- package/dist/services/local-secrets.js +7 -7
- package/dist/services/logger-console.d.ts +3 -7
- package/dist/services/logger-console.js +3 -7
- package/dist/services/logger.d.ts +22 -40
- package/dist/services/meta-service.d.ts +23 -26
- package/dist/services/meta-service.js +22 -36
- package/dist/services/personas-service.d.ts +134 -0
- package/dist/services/personas-service.js +40 -0
- package/dist/services/pikku-user-id.js +0 -4
- package/dist/services/queue-webhook-service.d.ts +2 -36
- package/dist/services/queue-webhook-service.js +10 -42
- package/dist/services/scheduler-service.d.ts +1 -50
- package/dist/services/scheduler-service.js +0 -10
- package/dist/services/schema-service.d.ts +1 -24
- package/dist/services/scope-service.d.ts +49 -34
- package/dist/services/scoped-secret-service.d.ts +4 -7
- package/dist/services/scoped-secret-service.js +0 -4
- package/dist/services/secret-host-binding.d.ts +8 -0
- package/dist/services/secret-host-binding.js +36 -0
- package/dist/services/secret-service.d.ts +12 -35
- package/dist/services/secretless.d.ts +6 -0
- package/dist/services/secretless.js +21 -0
- package/dist/services/stub-tracker.d.ts +7 -18
- package/dist/services/stub-tracker.js +8 -18
- package/dist/services/system-role-guard.d.ts +33 -0
- package/dist/services/system-role-guard.js +38 -0
- package/dist/services/trigger-service.d.ts +0 -12
- package/dist/services/typed-secret-service.d.ts +5 -11
- package/dist/services/typed-secret-service.js +1 -7
- package/dist/services/v8-coverage-service.d.ts +2 -3
- package/dist/services/v8-coverage-service.js +1 -2
- package/dist/services/variables-service.d.ts +1 -8
- package/dist/services/webhook-service.d.ts +21 -64
- package/dist/services/webhook-service.js +6 -20
- package/dist/services/workflow-service.d.ts +3 -15
- package/dist/testing/service-tests.js +6 -23
- package/dist/time-utils.d.ts +0 -16
- package/dist/time-utils.js +1 -19
- package/dist/types/core.types.d.ts +120 -219
- package/dist/types/core.types.js +0 -42
- package/dist/types/state.types.d.ts +4 -9
- package/dist/utils/hmac.d.ts +4 -10
- package/dist/utils/hmac.js +4 -10
- package/dist/utils/safe-fetch.d.ts +7 -35
- package/dist/utils/safe-fetch.js +13 -53
- package/dist/utils.d.ts +1 -6
- package/dist/utils.js +6 -15
- package/dist/wirings/actor-flow/actor-flow.types.d.ts +1 -34
- package/dist/wirings/actor-flow/index.d.ts +0 -9
- package/dist/wirings/actor-flow/run-conversation.d.ts +5 -5
- package/dist/wirings/actor-flow/run-conversation.js +14 -7
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +0 -5
- package/dist/wirings/ai-agent/ai-agent-agui.js +46 -12
- package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-helpers.js +7 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.d.ts +153 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +256 -0
- package/dist/wirings/ai-agent/ai-agent-memory.js +0 -2
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +0 -9
- package/dist/wirings/ai-agent/ai-agent-model-config.js +1 -9
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -99
- package/dist/wirings/ai-agent/ai-agent-prepare.js +65 -132
- package/dist/wirings/ai-agent/ai-agent-registry.d.ts +2 -1
- package/dist/wirings/ai-agent/ai-agent-registry.js +5 -1
- package/dist/wirings/ai-agent/ai-agent-runner.js +63 -22
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +25 -2
- package/dist/wirings/ai-agent/ai-agent-stream.js +180 -64
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +124 -4
- package/dist/wirings/ai-agent/index.d.ts +6 -4
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-agent/voice-input.d.ts +59 -1
- package/dist/wirings/ai-agent/voice-input.js +90 -12
- package/dist/wirings/ai-agent/voice-output.d.ts +69 -1
- package/dist/wirings/ai-agent/voice-output.js +162 -50
- package/dist/wirings/channel/channel-common.d.ts +7 -20
- package/dist/wirings/channel/channel-common.js +7 -21
- package/dist/wirings/channel/channel-handler.js +25 -6
- package/dist/wirings/channel/channel-host-rpc.d.ts +25 -0
- package/dist/wirings/channel/channel-host-rpc.js +38 -0
- package/dist/wirings/channel/channel-middleware-runner.d.ts +0 -12
- package/dist/wirings/channel/channel-middleware-runner.js +0 -12
- package/dist/wirings/channel/channel-rpc-registry.d.ts +31 -0
- package/dist/wirings/channel/channel-rpc-registry.js +89 -0
- package/dist/wirings/channel/channel-rpc-responder.d.ts +15 -0
- package/dist/wirings/channel/channel-rpc-responder.js +71 -0
- package/dist/wirings/channel/channel-rpc-service.d.ts +40 -0
- package/dist/wirings/channel/channel-rpc-service.js +106 -0
- package/dist/wirings/channel/channel-rpc-validators.d.ts +14 -0
- package/dist/wirings/channel/channel-rpc-validators.js +30 -0
- package/dist/wirings/channel/channel-rpc.d.ts +5 -0
- package/dist/wirings/channel/channel-rpc.js +5 -0
- package/dist/wirings/channel/channel-rpc.types.d.ts +90 -0
- package/dist/wirings/channel/channel-rpc.types.js +50 -0
- package/dist/wirings/channel/channel-runner.d.ts +0 -4
- package/dist/wirings/channel/channel-runner.js +0 -14
- package/dist/wirings/channel/channel-store.d.ts +0 -10
- package/dist/wirings/channel/channel.types.d.ts +12 -1
- package/dist/wirings/channel/define-channel-routes.d.ts +0 -20
- package/dist/wirings/channel/define-channel-routes.js +0 -20
- package/dist/wirings/channel/eventhub-service.d.ts +0 -18
- package/dist/wirings/channel/index.d.ts +4 -1
- package/dist/wirings/channel/index.js +2 -0
- package/dist/wirings/channel/local/local-channel-runner.js +3 -1
- package/dist/wirings/channel/local/local-eventhub-service.d.ts +0 -33
- package/dist/wirings/channel/local/local-eventhub-service.js +2 -36
- package/dist/wirings/channel/log-channels.d.ts +0 -4
- package/dist/wirings/channel/log-channels.js +0 -4
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +6 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -5
- package/dist/wirings/cli/channel/cli-approval.d.ts +41 -0
- package/dist/wirings/cli/channel/cli-approval.js +81 -0
- package/dist/wirings/cli/channel/cli-channel-runner.d.ts +0 -4
- package/dist/wirings/cli/channel/cli-channel-runner.js +3 -25
- package/dist/wirings/cli/channel/cli-raw-channel-runner.d.ts +47 -9
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +24 -16
- package/dist/wirings/cli/channel/cli-raw-client-runner.d.ts +38 -0
- package/dist/wirings/cli/channel/cli-raw-client-runner.js +129 -0
- package/dist/wirings/cli/channel/index.d.ts +5 -0
- package/dist/wirings/cli/channel/index.js +2 -0
- package/dist/wirings/cli/cli-runner.d.ts +20 -20
- package/dist/wirings/cli/cli-runner.js +28 -89
- package/dist/wirings/cli/cli.types.d.ts +14 -3
- package/dist/wirings/cli/command-parser.d.ts +1 -10
- package/dist/wirings/cli/command-parser.js +10 -87
- package/dist/wirings/cli/define-cli-commands.d.ts +1 -17
- package/dist/wirings/cli/define-cli-commands.js +1 -17
- package/dist/wirings/credential/credential.types.d.ts +0 -12
- package/dist/wirings/credential/define-credential.d.ts +48 -0
- package/dist/wirings/credential/define-credential.js +47 -0
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/credential/index.js +1 -1
- package/dist/wirings/credential/validate-credential-definitions.d.ts +2 -4
- package/dist/wirings/gateway/gateway-runner.d.ts +1 -20
- package/dist/wirings/gateway/gateway-runner.js +8 -105
- package/dist/wirings/gateway/gateway.types.d.ts +7 -80
- package/dist/wirings/http/http-routes.d.ts +0 -63
- package/dist/wirings/http/http-routes.js +0 -63
- package/dist/wirings/http/http-runner.d.ts +0 -99
- package/dist/wirings/http/http-runner.js +9 -165
- package/dist/wirings/http/http.types.d.ts +14 -55
- package/dist/wirings/http/log-http-routes.d.ts +0 -4
- package/dist/wirings/http/log-http-routes.js +0 -4
- package/dist/wirings/http/pikku-fetch-http-request.d.ts +0 -40
- package/dist/wirings/http/pikku-fetch-http-request.js +0 -58
- package/dist/wirings/http/pikku-fetch-http-response.js +0 -3
- package/dist/wirings/http/routers/path-to-regex.js +2 -13
- package/dist/wirings/http/web-request.d.ts +0 -8
- package/dist/wirings/http/web-request.js +25 -17
- package/dist/wirings/mcp/mcp-runner.d.ts +1 -4
- package/dist/wirings/mcp/mcp-runner.js +1 -14
- package/dist/wirings/mcp/mcp.types.d.ts +2 -35
- package/dist/wirings/oauth2/oauth2.types.d.ts +0 -28
- package/dist/wirings/oauth2/oauth2.types.js +0 -3
- package/dist/wirings/persona/define-personas.d.ts +32 -0
- package/dist/wirings/persona/define-personas.js +31 -0
- package/dist/wirings/persona/index.d.ts +21 -0
- package/dist/wirings/persona/index.js +17 -0
- package/dist/wirings/persona/persona-email.d.ts +37 -0
- package/dist/wirings/persona/persona-email.js +69 -0
- package/dist/wirings/persona/persona-environments.d.ts +45 -0
- package/dist/wirings/persona/persona-environments.js +81 -0
- package/dist/wirings/persona/persona-mailbox.d.ts +101 -0
- package/dist/wirings/persona/persona-mailbox.js +53 -0
- package/dist/wirings/persona/persona.types.d.ts +136 -0
- package/dist/wirings/persona/persona.types.js +1 -0
- package/dist/wirings/persona/validate-personas.d.ts +53 -0
- package/dist/wirings/persona/validate-personas.js +94 -0
- package/dist/wirings/queue/index.d.ts +3 -0
- package/dist/wirings/queue/index.js +2 -3
- package/dist/wirings/queue/queue-identity.d.ts +28 -0
- package/dist/wirings/queue/queue-identity.js +103 -0
- package/dist/wirings/queue/queue-runner.d.ts +0 -19
- package/dist/wirings/queue/queue-runner.js +9 -30
- package/dist/wirings/queue/queue.types.d.ts +20 -90
- package/dist/wirings/queue/register-queue-helper.d.ts +0 -12
- package/dist/wirings/queue/register-queue-helper.js +0 -11
- package/dist/wirings/queue/signed-queue-service.d.ts +17 -0
- package/dist/wirings/queue/signed-queue-service.js +42 -0
- package/dist/wirings/queue/validate-worker-config.d.ts +2 -23
- package/dist/wirings/queue/validate-worker-config.js +0 -14
- package/dist/wirings/role/define-system-role.d.ts +32 -0
- package/dist/wirings/role/define-system-role.js +31 -0
- package/dist/wirings/role/index.d.ts +3 -0
- package/dist/wirings/role/index.js +2 -0
- package/dist/wirings/role/role.types.d.ts +43 -0
- package/dist/wirings/role/role.types.js +1 -0
- package/dist/wirings/role/validate-role-definitions.d.ts +21 -0
- package/dist/wirings/role/validate-role-definitions.js +71 -0
- package/dist/wirings/rpc/addon-runner.d.ts +0 -19
- package/dist/wirings/rpc/addon-runner.js +0 -51
- package/dist/wirings/rpc/remote-addon-auth.d.ts +3 -13
- package/dist/wirings/rpc/remote-addon-auth.js +7 -11
- package/dist/wirings/rpc/rpc-runner.d.ts +11 -18
- package/dist/wirings/rpc/rpc-runner.js +88 -105
- package/dist/wirings/rpc/rpc-types.d.ts +7 -6
- package/dist/wirings/rpc/wire-addon.d.ts +25 -0
- package/dist/wirings/rpc/wire-addon.js +62 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +3 -28
- package/dist/wirings/rpc/wire-remote-addon.js +0 -8
- package/dist/wirings/scheduler/log-schedulers.d.ts +0 -4
- package/dist/wirings/scheduler/log-schedulers.js +0 -4
- package/dist/wirings/scheduler/scheduler-runner.d.ts +0 -1
- package/dist/wirings/scheduler/scheduler-runner.js +0 -1
- package/dist/wirings/scheduler/scheduler.types.d.ts +1 -14
- package/dist/wirings/scope/define-scope.d.ts +32 -0
- package/dist/wirings/scope/define-scope.js +31 -0
- package/dist/wirings/scope/index.d.ts +1 -1
- package/dist/wirings/scope/index.js +1 -1
- package/dist/wirings/scope/scope.types.d.ts +7 -9
- package/dist/wirings/scope/validate-scope-definitions.d.ts +2 -21
- package/dist/wirings/scope/validate-scope-definitions.js +3 -21
- package/dist/wirings/secret/index.d.ts +1 -1
- package/dist/wirings/secret/index.js +1 -1
- package/dist/wirings/secret/secret.types.d.ts +19 -15
- package/dist/wirings/secret/secret.types.js +1 -1
- package/dist/wirings/secret/validate-secret-definitions.d.ts +2 -4
- package/dist/wirings/trigger/trigger-runner.d.ts +0 -27
- package/dist/wirings/trigger/trigger-runner.js +1 -24
- package/dist/wirings/trigger/trigger.types.d.ts +1 -82
- package/dist/wirings/trigger/trigger.types.js +0 -34
- package/dist/wirings/variable/index.d.ts +1 -1
- package/dist/wirings/variable/index.js +1 -1
- package/dist/wirings/variable/validate-variable-definitions.d.ts +2 -4
- package/dist/wirings/variable/variable.types.d.ts +1 -13
- package/dist/wirings/variable/variable.types.js +1 -1
- package/dist/wirings/virtual-user/index.d.ts +30 -0
- package/dist/wirings/virtual-user/index.js +10 -0
- package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +54 -0
- package/dist/wirings/virtual-user/prepare-virtual-user-run.js +49 -0
- package/dist/wirings/virtual-user/run-virtual-user.d.ts +92 -0
- package/dist/wirings/virtual-user/run-virtual-user.js +478 -0
- package/dist/wirings/virtual-user/virtual-user-agents.d.ts +38 -0
- package/dist/wirings/virtual-user/virtual-user-agents.js +24 -0
- package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +92 -0
- package/dist/wirings/virtual-user/virtual-user-catalogue.js +134 -0
- package/dist/wirings/virtual-user/virtual-user-derive.d.ts +26 -0
- package/dist/wirings/virtual-user/virtual-user-derive.js +137 -0
- package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +79 -0
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +128 -0
- package/dist/wirings/virtual-user/virtual-user-intents.d.ts +78 -0
- package/dist/wirings/virtual-user/virtual-user-intents.js +142 -0
- package/dist/wirings/virtual-user/virtual-user-rng.d.ts +24 -0
- package/dist/wirings/virtual-user/virtual-user-rng.js +44 -0
- package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +90 -0
- package/dist/wirings/virtual-user/virtual-user-run-store.js +1 -0
- package/dist/wirings/virtual-user/virtual-user-target.d.ts +21 -0
- package/dist/wirings/virtual-user/virtual-user-target.js +34 -0
- package/dist/wirings/virtual-user/virtual-user.types.d.ts +199 -0
- package/dist/wirings/virtual-user/virtual-user.types.js +8 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +19 -15
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +0 -4
- package/dist/wirings/workflow/dsl/workflow-runner.js +0 -4
- package/dist/wirings/workflow/feature.d.ts +0 -19
- package/dist/wirings/workflow/feature.js +0 -19
- package/dist/wirings/workflow/graph/graph-node.d.ts +0 -98
- package/dist/wirings/workflow/graph/graph-node.js +0 -34
- package/dist/wirings/workflow/graph/graph-runner.js +6 -41
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +0 -4
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -58
- package/dist/wirings/workflow/graph/workflow-graph.types.js +0 -6
- package/dist/wirings/workflow/index.d.ts +5 -7
- package/dist/wirings/workflow/index.js +3 -17
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +87 -5
- package/dist/wirings/workflow/pikku-scenario-service.js +204 -43
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +7 -459
- package/dist/wirings/workflow/pikku-workflow-service.js +58 -551
- package/dist/wirings/workflow/run-timeline.d.ts +0 -47
- package/dist/wirings/workflow/run-timeline.js +0 -22
- package/dist/wirings/workflow/scenario-cookie-jar.d.ts +0 -23
- package/dist/wirings/workflow/scenario-cookie-jar.js +0 -16
- package/dist/wirings/workflow/scenario-poll.d.ts +0 -15
- package/dist/wirings/workflow/scenario-poll.js +0 -12
- package/dist/wirings/workflow/scenario-prose.d.ts +0 -28
- package/dist/wirings/workflow/scenario-prose.js +1 -19
- package/dist/wirings/workflow/scenario-step-guards.d.ts +0 -13
- package/dist/wirings/workflow/scenario-step-guards.js +1 -14
- package/dist/wirings/workflow/scenario-step.types.d.ts +84 -12
- package/dist/wirings/workflow/scenario-step.types.js +5 -1
- package/dist/wirings/workflow/scenario-surface.d.ts +16 -0
- package/dist/wirings/workflow/scenario-surface.js +56 -0
- package/dist/wirings/workflow/workflow-invocation-id.d.ts +0 -18
- package/dist/wirings/workflow/workflow-invocation-id.js +2 -22
- package/dist/wirings/workflow/workflow-queue-workers.d.ts +0 -20
- package/dist/wirings/workflow/workflow-queue-workers.js +0 -19
- package/dist/wirings/workflow/workflow.types.d.ts +5 -195
- package/knowledge/decisions/index.md +19 -0
- package/knowledge/decisions/internals/a-secret-that-fails-to-decrypt-fails-the-whole-read.md +49 -0
- package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-and-not-a-queued-job.md +48 -0
- package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +23 -0
- package/knowledge/decisions/internals/actor-flow-drives-the-target-through-a-transport-seam.md +24 -0
- package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +25 -0
- package/knowledge/decisions/internals/addon-package-roots-resolve-by-walking-node-module-search-paths.md +26 -0
- package/knowledge/decisions/internals/addon-singleton-services-are-cached-per-namespace-not-per-package.md +33 -0
- package/knowledge/decisions/internals/addon-workflow-names-are-prefixed-with-the-consumer-namespace.md +28 -0
- package/knowledge/decisions/internals/ai-agent-agui-bridge-obeys-the-client-ordering-contract.md +29 -0
- package/knowledge/decisions/internals/ai-agent-audio-chunks-carry-the-format-the-provider-returned.md +20 -0
- package/knowledge/decisions/internals/ai-agent-credential-suspensions-hide-the-tool-result.md +26 -0
- package/knowledge/decisions/internals/ai-agent-delegate-and-supervise-hide-different-text.md +26 -0
- package/knowledge/decisions/internals/ai-agent-llm-tool-arguments-have-nulls-stripped.md +23 -0
- package/knowledge/decisions/internals/ai-agent-model-config-stays-a-single-resolution-seam.md +25 -0
- package/knowledge/decisions/internals/ai-agent-onerror-hooks-cannot-change-the-failure.md +22 -0
- package/knowledge/decisions/internals/ai-agent-runner-methods-must-keep-their-receiver.md +22 -0
- package/knowledge/decisions/internals/ai-agent-stream-persistence-is-best-effort.md +27 -0
- package/knowledge/decisions/internals/ai-agent-sub-agents-inherit-the-parent-context-block.md +26 -0
- package/knowledge/decisions/internals/ai-agent-tool-execute-failures-are-logged-unconditionally.md +25 -0
- package/knowledge/decisions/internals/ai-agent-voice-input-transcribes-audio-parts-in-place.md +22 -0
- package/knowledge/decisions/internals/ai-agent-working-memory-is-persisted-only-when-valid.md +25 -0
- package/knowledge/decisions/internals/channel-message-handlers-accept-three-config-shapes.md +30 -0
- package/knowledge/decisions/internals/channel-middleware-caches-only-statically-resolved-middleware.md +31 -0
- package/knowledge/decisions/internals/channel-state-is-per-socket-session-state-is-per-user.md +29 -0
- package/knowledge/decisions/internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md +28 -0
- package/knowledge/decisions/internals/cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md +27 -0
- package/knowledge/decisions/internals/cli-parse-errors-are-routed-by-message-prefix.md +28 -0
- package/knowledge/decisions/internals/cli-stdout-is-reserved-for-machine-readable-output.md +34 -0
- package/knowledge/decisions/internals/cli-unknown-long-options-warn-instead-of-failing.md +29 -0
- package/knowledge/decisions/internals/core-column-form-is-an-axis-of-its-own.md +84 -0
- package/knowledge/decisions/internals/core-data-classification-brand-is-an-optional-property.md +41 -0
- package/knowledge/decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md +44 -0
- package/knowledge/decisions/internals/core-hot-reload-merges-generated-meta-never-replaces-it.md +39 -0
- package/knowledge/decisions/internals/core-hot-reload-owns-its-module-registry.md +42 -0
- package/knowledge/decisions/internals/core-middleware-order-is-scope-then-priority.md +39 -0
- package/knowledge/decisions/internals/core-schema-defaults-apply-on-every-transport.md +43 -0
- package/knowledge/decisions/internals/core-scopes-are-an-and-gate-separate-from-permissions.md +38 -0
- package/knowledge/decisions/internals/core-state-is-a-global-map-written-only-at-registration-time.md +44 -0
- package/knowledge/decisions/internals/email-meta-is-read-uncached-because-codegen-rewrites-it-mid-session.md +27 -0
- package/knowledge/decisions/internals/gateway-adapters-resolve-lazily-and-are-promise-cached.md +32 -0
- package/knowledge/decisions/internals/gateway-webhook-challenges-echo-bytes-not-json.md +28 -0
- package/knowledge/decisions/internals/gateway-wiring-is-a-meta-wiring-over-http-and-channels.md +31 -0
- package/knowledge/decisions/internals/generated-src-paths-in-pikku-meta-are-absolute.md +26 -0
- package/knowledge/decisions/internals/http-request-bodies-are-read-once-and-shared.md +32 -0
- package/knowledge/decisions/internals/http-route-groups-cascade-config-in-a-fixed-order.md +28 -0
- package/knowledge/decisions/internals/http-router-matches-normalized-paths-but-returns-registered-ones.md +30 -0
- package/knowledge/decisions/internals/http-runner-logs-through-a-trace-scoped-logger-functions-do-not.md +26 -0
- package/knowledge/decisions/internals/http-set-cookie-headers-are-appended-never-joined.md +28 -0
- package/knowledge/decisions/internals/http-sse-streams-flush-headers-only-after-middleware.md +32 -0
- package/knowledge/decisions/internals/http-wiring-without-metadata-is-skipped-not-fatal.md +26 -0
- package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +25 -0
- package/knowledge/decisions/internals/in-memory-workflow-history-aliases-the-live-step-object.md +26 -0
- package/knowledge/decisions/internals/index.md +117 -0
- package/knowledge/decisions/internals/istanbul-statement-counts-attach-to-the-start-line-only.md +25 -0
- package/knowledge/decisions/internals/local-trigger-and-gateway-services-assume-a-single-process.md +26 -0
- package/knowledge/decisions/internals/node-only-builtins-are-imported-dynamically.md +24 -0
- package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +53 -0
- package/knowledge/decisions/internals/queue-group-concurrency-keeps-one-shared-queue-fair.md +28 -0
- package/knowledge/decisions/internals/queue-jobs-always-carry-an-explicit-attempts-count.md +27 -0
- package/knowledge/decisions/internals/remote-addons-dispatch-over-http-instead-of-local-meta.md +31 -0
- package/knowledge/decisions/internals/rpc-names-resolve-through-package-scope-before-root.md +32 -0
- package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +27 -0
- package/knowledge/decisions/internals/scenario-meta-lives-apart-from-app-meta-but-merges-when-read-off-disk.md +26 -0
- package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +27 -0
- package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +48 -0
- package/knowledge/decisions/internals/scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md +30 -0
- package/knowledge/decisions/internals/serverless-channel-disconnect-must-tolerate-a-missing-channel.md +28 -0
- package/knowledge/decisions/internals/the-dev-queue-copies-prod-timing-and-serialization-semantics.md +30 -0
- package/knowledge/decisions/internals/the-embedding-model-is-pinned-per-service-and-doc-query-embedding-is-split.md +29 -0
- package/knowledge/decisions/internals/the-in-memory-workflow-service-is-inline-only-and-single-process.md +27 -0
- package/knowledge/decisions/internals/the-kek-salt-is-scoped-to-the-key-version.md +40 -0
- package/knowledge/decisions/internals/the-schema-service-is-never-stubbed.md +26 -0
- package/knowledge/decisions/internals/trigger-declaration-is-split-from-trigger-source.md +33 -0
- package/knowledge/decisions/internals/typed-secret-service-caches-for-the-process-lifetime.md +26 -0
- package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +43 -0
- package/knowledge/decisions/internals/webhook-delivery-history-records-every-attempt-best-effort.md +26 -0
- package/knowledge/decisions/internals/webhook-service-collaborators-are-constructor-args-not-locator-lookups.md +25 -0
- package/knowledge/decisions/internals/whether-a-run-is-inline-is-read-from-the-run-record.md +58 -0
- package/knowledge/decisions/internals/workflow-approval-expiry-is-decided-from-a-recorded-deadline.md +34 -0
- package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +42 -0
- package/knowledge/decisions/internals/workflow-dsl-meta-separates-runtime-expressions-from-literals.md +38 -0
- package/knowledge/decisions/internals/workflow-features-resolve-scenarios-by-object-identity.md +29 -0
- package/knowledge/decisions/internals/workflow-graph-inline-and-queued-runs-share-one-planner.md +42 -0
- package/knowledge/decisions/internals/workflow-graph-node-notes-are-excluded-from-the-graph-hash.md +25 -0
- package/knowledge/decisions/internals/workflow-inline-runs-report-their-run-id-before-they-can-fail.md +29 -0
- package/knowledge/decisions/internals/workflow-invocation-id-is-the-dedupe-key-not-step-id.md +43 -0
- package/knowledge/decisions/internals/workflow-queued-step-dispatch-requires-an-explicit-opt-in.md +29 -0
- package/knowledge/decisions/internals/workflow-queues-are-per-workflow-by-default.md +42 -0
- package/knowledge/decisions/internals/workflow-repeated-step-names-get-an-ordinal-suffix.md +33 -0
- package/knowledge/decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md +32 -0
- package/knowledge/decisions/internals/workflow-retries-are-owned-by-the-workflow-not-the-queue.md +31 -0
- package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +39 -0
- package/knowledge/decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md +29 -0
- package/knowledge/decisions/internals/workflow-run-polling-backs-off-to-the-callers-ceiling.md +33 -0
- package/knowledge/decisions/internals/workflow-run-timeline-is-a-pure-fold-over-durable-history.md +37 -0
- package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +50 -0
- package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +43 -0
- package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +32 -0
- package/knowledge/decisions/internals/workflow-scenario-quarantine-reason-lives-in-code.md +18 -0
- package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +34 -0
- package/knowledge/decisions/internals/workflow-step-compensation-runs-as-its-own-durable-step.md +26 -0
- package/knowledge/decisions/internals/workflow-step-dispatch-failure-is-transient-not-a-run-failure.md +33 -0
- package/knowledge/decisions/internals/workflow-step-lock-is-held-only-to-claim-the-step.md +27 -0
- package/knowledge/decisions/internals/workflow-step-rpc-name-is-provenance-only.md +34 -0
- package/knowledge/decisions/internals/workflow-suspend-and-approval-reasons-are-durable-step-identities.md +38 -0
- package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +30 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +26 -0
- package/knowledge/decisions/security/actor-flow-missing-approval-decisions-default-to-denied.md +22 -0
- package/knowledge/decisions/security/actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md +27 -0
- package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +27 -0
- package/knowledge/decisions/security/addon-auth-and-tags-only-tighten.md +43 -0
- package/knowledge/decisions/security/addon-config-gates-apply-only-at-the-namespaced-rpc-boundary.md +52 -0
- package/knowledge/decisions/security/addon-scopes-are-resolved-where-the-function-runs.md +46 -0
- package/knowledge/decisions/security/ai-agent-approval-forwarding-requires-a-symbol-brand.md +27 -0
- package/knowledge/decisions/security/ai-agent-credential-requests-are-symbol-branded.md +37 -0
- package/knowledge/decisions/security/ai-agent-gate-requires-a-session-only-when-auth-is-true.md +31 -0
- package/knowledge/decisions/security/ai-agent-ownership-failures-never-echo-the-resource.md +23 -0
- package/knowledge/decisions/security/ai-agent-resume-re-runs-the-authorization-gate.md +22 -0
- package/knowledge/decisions/security/ai-agent-sessionless-deployments-have-no-thread-ownership.md +39 -0
- package/knowledge/decisions/security/ai-agent-thread-ownership-composes-the-session-principal.md +30 -0
- package/knowledge/decisions/security/ai-agent-tool-filtering-reads-the-live-function-config.md +24 -0
- package/knowledge/decisions/security/an-empty-owners-constraint-matches-nothing.md +30 -0
- package/knowledge/decisions/security/an-exposed-ungated-function-is-a-codegen-warning.md +49 -0
- package/knowledge/decisions/security/console-addon-privileged-functions-gate-themselves.md +76 -0
- package/knowledge/decisions/security/core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md +39 -0
- package/knowledge/decisions/security/core-secrets-use-a-per-secret-dek-wrapped-by-a-kek.md +37 -0
- package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +31 -0
- package/knowledge/decisions/security/gateway-middleware-sessions-must-be-bridged-onto-the-wire.md +30 -0
- package/knowledge/decisions/security/global-permissions-and-function-permissions-are-independent-gates.md +40 -0
- package/knowledge/decisions/security/http-error-detail-is-withheld-from-clients-in-production.md +33 -0
- package/knowledge/decisions/security/http-request-bodies-are-bounded-before-they-are-buffered.md +46 -0
- package/knowledge/decisions/security/index.md +55 -0
- package/knowledge/decisions/security/mcp-internal-error-details-are-double-gated-on-production.md +27 -0
- package/knowledge/decisions/security/passphrases-are-stretched-key-material-is-expanded.md +40 -0
- package/knowledge/decisions/security/permission-auth-filtering-requires-live-permission-functions.md +31 -0
- package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +26 -0
- package/knowledge/decisions/security/queue-job-identities-are-signed-at-enqueue.md +69 -0
- package/knowledge/decisions/security/queue-jobs-carry-the-producers-pikku-user-id.md +39 -0
- package/knowledge/decisions/security/remote-addon-tokens-are-client-credentials-not-mesh-trust.md +34 -0
- package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +49 -0
- package/knowledge/decisions/security/scenario-step-functions-are-never-externally-invocable.md +30 -0
- package/knowledge/decisions/security/scope-resolution-happens-at-the-session-boundary-and-sync-never-deletes.md +28 -0
- package/knowledge/decisions/security/self-authentication-is-declared-not-detected.md +34 -0
- package/knowledge/decisions/security/signed-content-urls-bind-the-request-path.md +37 -0
- package/knowledge/decisions/security/webhook-bodies-are-signed-before-they-are-enqueued.md +25 -0
- package/knowledge/decisions/security/workflow-actor-steps-always-use-the-real-transport.md +34 -0
- package/knowledge/decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md +40 -0
- package/knowledge/decisions/security/workflow-queued-steps-rehydrate-their-session-from-the-run-wire.md +32 -0
- package/knowledge/decisions/security/workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md +32 -0
- package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +31 -0
- package/knowledge/index.md +24 -0
- package/knowledge/questions/index.md +15 -0
- package/package.json +6 -2
- package/run-tests.sh +0 -0
- package/src/column-form.test.ts +97 -0
- package/src/column-form.ts +58 -0
- package/src/crypto-utils.test.ts +460 -19
- package/src/crypto-utils.ts +306 -59
- package/src/data-classification.ts +45 -7
- package/src/dev/hot-reload.test.ts +0 -4
- package/src/dev/hot-reload.ts +11 -30
- package/src/dev/module-runner.ts +7 -32
- package/src/dev/reload-meta.ts +9 -29
- package/src/errors/error-handler.ts +20 -35
- package/src/errors/error.test.ts +30 -1
- package/src/errors/errors.ts +73 -157
- package/src/function/abort-scope.test.ts +97 -0
- package/src/function/abort-scope.ts +80 -0
- package/src/function/function-runner.test.ts +0 -7
- package/src/function/function-runner.ts +62 -32
- package/src/function/functions.types.ts +95 -138
- package/src/function/index.ts +1 -0
- package/src/function/list.types.test.ts +3 -25
- package/src/function/list.types.ts +12 -62
- package/src/handle-error.ts +0 -18
- package/src/index.ts +84 -3
- package/src/middleware/auth-apikey.test.ts +0 -1
- package/src/middleware/auth-apikey.ts +0 -17
- package/src/middleware/auth-bearer.test.ts +3 -5
- package/src/middleware/auth-bearer.ts +5 -41
- package/src/middleware/auth-cookie.test.ts +0 -6
- package/src/middleware/auth-cookie.ts +2 -26
- package/src/middleware/cors.test.ts +34 -0
- package/src/middleware/cors.ts +12 -33
- package/src/middleware/remote-auth.test.ts +26 -10
- package/src/middleware/remote-auth.ts +11 -3
- package/src/middleware/telemetry.ts +2 -31
- package/src/middleware-runner.test.ts +0 -2
- package/src/middleware-runner.ts +5 -74
- package/src/permissions.test.ts +30 -0
- package/src/permissions.ts +24 -74
- package/src/pikku-request.ts +0 -6
- package/src/pikku-state.ts +2 -30
- package/src/production-barrels-stay-lean.test.ts +110 -0
- package/src/remote.test.ts +173 -0
- package/src/remote.ts +18 -8
- package/src/schema.ts +5 -39
- package/src/scopes.ts +7 -48
- package/src/secret-value.test.ts +204 -0
- package/src/secret-value.ts +111 -0
- package/src/services/ai-agent-runner-service.ts +20 -0
- package/src/services/ai-embedding-service.ts +3 -25
- package/src/services/audit-service.ts +88 -11
- package/src/services/content-service.ts +1 -46
- package/src/services/credential-service.ts +3 -40
- package/src/services/credential-wire-service.test.ts +0 -2
- package/src/services/credential-wire-service.ts +9 -1
- package/src/services/deployment-service.ts +3 -9
- package/src/services/email-service.ts +3 -1
- package/src/services/gateway-service.ts +0 -15
- package/src/services/{http-scenario-actors-converse.test.ts → http-personas-converse.test.ts} +38 -9
- package/src/services/{http-scenario-actors.test.ts → http-personas.test.ts} +39 -22
- package/src/services/{http-scenario-actors.ts → http-personas.ts} +85 -45
- package/src/services/in-memory-queue-service.ts +1 -15
- package/src/services/in-memory-trigger-service.ts +1 -18
- package/src/services/in-memory-workflow-service.test.ts +0 -13
- package/src/services/in-memory-workflow-service.ts +4 -38
- package/src/services/index.ts +23 -18
- package/src/services/istanbul-coverage-service.ts +2 -8
- package/src/services/jwt-service.ts +1 -16
- package/src/services/local-content-request-handler.test.ts +202 -0
- package/src/services/local-content-request-handler.ts +267 -0
- package/src/services/local-content.test.ts +159 -27
- package/src/services/local-content.ts +55 -23
- package/src/services/local-gateway-service.ts +2 -17
- package/src/services/local-secrets.test.ts +20 -5
- package/src/services/local-secrets.ts +15 -11
- package/src/services/logger-console.test.ts +0 -1
- package/src/services/logger-console.ts +3 -7
- package/src/services/logger.ts +31 -46
- package/src/services/meta-service.test.ts +1 -5
- package/src/services/meta-service.ts +41 -61
- package/src/services/{scenario-actors-service.ts → personas-service.ts} +48 -43
- package/src/services/pikku-user-id.ts +0 -4
- package/src/services/queue-webhook-service.test.ts +2 -1
- package/src/services/queue-webhook-service.ts +10 -42
- package/src/services/scheduler-service.ts +1 -50
- package/src/services/schema-service.ts +1 -24
- package/src/services/scope-service.ts +50 -34
- package/src/services/scoped-secret-service.ts +4 -7
- package/src/services/secret-host-binding.test.ts +138 -0
- package/src/services/secret-host-binding.ts +51 -0
- package/src/services/secret-service.ts +12 -35
- package/src/services/secretless.test.ts +54 -0
- package/src/services/secretless.ts +29 -0
- package/src/services/stub-tracker.ts +8 -18
- package/src/services/system-role-guard.test.ts +93 -0
- package/src/services/system-role-guard.ts +71 -0
- package/src/services/trigger-service.ts +0 -12
- package/src/services/typed-secret-service.ts +12 -14
- package/src/services/v8-coverage-service.ts +3 -6
- package/src/services/variables-service.ts +1 -8
- package/src/services/webhook-service.ts +23 -64
- package/src/services/workflow-service.ts +3 -20
- package/src/testing/service-tests.ts +6 -32
- package/src/time-utils.ts +1 -19
- package/src/types/core.types.ts +137 -229
- package/src/types/state.types.ts +7 -9
- package/src/utils/hmac.ts +4 -10
- package/src/utils/safe-fetch.ts +13 -54
- package/src/utils.test.ts +11 -2
- package/src/utils.ts +6 -15
- package/src/wirings/actor-flow/actor-flow.types.ts +1 -34
- package/src/wirings/actor-flow/index.ts +0 -9
- package/src/wirings/actor-flow/run-conversation.test.ts +11 -6
- package/src/wirings/actor-flow/run-conversation.ts +19 -12
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +91 -10
- package/src/wirings/ai-agent/ai-agent-agui.ts +49 -17
- package/src/wirings/ai-agent/ai-agent-helpers.ts +20 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +842 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +399 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +0 -2
- package/src/wirings/ai-agent/ai-agent-model-config.ts +1 -9
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +202 -31
- package/src/wirings/ai-agent/ai-agent-prepare.ts +89 -139
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +191 -6
- package/src/wirings/ai-agent/ai-agent-registry.ts +18 -1
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +0 -2
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +11 -9
- package/src/wirings/ai-agent/ai-agent-runner.ts +85 -35
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +205 -103
- package/src/wirings/ai-agent/ai-agent-stream.ts +224 -76
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +301 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +130 -5
- package/src/wirings/ai-agent/index.ts +37 -3
- package/src/wirings/ai-agent/voice-input.test.ts +137 -7
- package/src/wirings/ai-agent/voice-input.ts +96 -12
- package/src/wirings/ai-agent/voice-output.test.ts +512 -0
- package/src/wirings/ai-agent/voice-output.ts +243 -56
- package/src/wirings/channel/channel-common.ts +15 -20
- package/src/wirings/channel/channel-handler.test.ts +50 -0
- package/src/wirings/channel/channel-handler.ts +32 -11
- package/src/wirings/channel/channel-host-rpc.test.ts +150 -0
- package/src/wirings/channel/channel-host-rpc.ts +69 -0
- package/src/wirings/channel/channel-middleware-runner.test.ts +0 -1
- package/src/wirings/channel/channel-middleware-runner.ts +0 -12
- package/src/wirings/channel/channel-rpc-registry.ts +116 -0
- package/src/wirings/channel/channel-rpc-responder.ts +117 -0
- package/src/wirings/channel/channel-rpc-service.ts +146 -0
- package/src/wirings/channel/channel-rpc-validators.ts +65 -0
- package/src/wirings/channel/channel-rpc.test.ts +820 -0
- package/src/wirings/channel/channel-rpc.ts +5 -0
- package/src/wirings/channel/channel-rpc.types.ts +150 -0
- package/src/wirings/channel/channel-runner.ts +0 -14
- package/src/wirings/channel/channel-store.ts +0 -10
- package/src/wirings/channel/channel.types.ts +19 -8
- package/src/wirings/channel/define-channel-routes.ts +0 -20
- package/src/wirings/channel/eventhub-service.ts +0 -18
- package/src/wirings/channel/index.ts +35 -0
- package/src/wirings/channel/local/local-channel-handler.ts +3 -1
- package/src/wirings/channel/local/local-channel-runner.test.ts +0 -10
- package/src/wirings/channel/local/local-channel-runner.ts +3 -1
- package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -13
- package/src/wirings/channel/local/local-eventhub-service.ts +2 -37
- package/src/wirings/channel/log-channels.ts +0 -4
- package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +83 -2
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +7 -0
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -5
- package/src/wirings/cli/channel/cli-approval.test.ts +177 -0
- package/src/wirings/cli/channel/cli-approval.ts +135 -0
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -26
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +169 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +59 -16
- package/src/wirings/cli/channel/cli-raw-client-runner.test.ts +480 -0
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +185 -0
- package/src/wirings/cli/channel/index.ts +13 -0
- package/src/wirings/cli/cli-runner.test.ts +0 -1
- package/src/wirings/cli/cli-runner.ts +46 -88
- package/src/wirings/cli/cli.types.ts +14 -3
- package/src/wirings/cli/command-parser.test.ts +0 -4
- package/src/wirings/cli/command-parser.ts +11 -91
- package/src/wirings/cli/define-cli-commands.ts +1 -17
- package/src/wirings/credential/credential.types.ts +0 -12
- package/src/wirings/credential/{wire-credential.ts → define-credential.ts} +7 -7
- package/src/wirings/credential/index.ts +1 -1
- package/src/wirings/credential/validate-credential-definitions.ts +2 -4
- package/src/wirings/gateway/gateway-runner.test.ts +1 -21
- package/src/wirings/gateway/gateway-runner.ts +8 -110
- package/src/wirings/gateway/gateway.types.ts +7 -80
- package/src/wirings/http/http-routes.test.ts +0 -3
- package/src/wirings/http/http-routes.ts +0 -86
- package/src/wirings/http/http-runner.test.ts +0 -1
- package/src/wirings/http/http-runner.ts +8 -167
- package/src/wirings/http/http.types.ts +15 -62
- package/src/wirings/http/log-http-routes.ts +0 -4
- package/src/wirings/http/pikku-fetch-http-request.test.ts +2 -10
- package/src/wirings/http/pikku-fetch-http-request.ts +0 -58
- package/src/wirings/http/pikku-fetch-http-response.test.ts +1 -1
- package/src/wirings/http/pikku-fetch-http-response.ts +0 -3
- package/src/wirings/http/routers/path-to-regex.test.ts +4 -17
- package/src/wirings/http/routers/path-to-regex.ts +2 -13
- package/src/wirings/http/web-request.test.ts +33 -2
- package/src/wirings/http/web-request.ts +30 -17
- package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -1
- package/src/wirings/mcp/mcp-runner.ts +2 -17
- package/src/wirings/mcp/mcp.types.ts +7 -42
- package/src/wirings/oauth2/oauth2.types.ts +0 -30
- package/src/wirings/persona/define-personas.ts +33 -0
- package/src/wirings/persona/index.ts +62 -0
- package/src/wirings/persona/persona-email.ts +87 -0
- package/src/wirings/persona/persona-environments.test.ts +183 -0
- package/src/wirings/persona/persona-environments.ts +138 -0
- package/src/wirings/persona/persona-mailbox.ts +156 -0
- package/src/wirings/persona/persona.test.ts +220 -0
- package/src/wirings/persona/persona.types.ts +142 -0
- package/src/wirings/persona/validate-personas.ts +133 -0
- package/src/wirings/queue/index.ts +13 -3
- package/src/wirings/queue/queue-identity.test.ts +454 -0
- package/src/wirings/queue/queue-identity.ts +176 -0
- package/src/wirings/queue/queue-runner.ts +12 -31
- package/src/wirings/queue/queue.types.ts +25 -90
- package/src/wirings/queue/register-queue-helper.ts +0 -14
- package/src/wirings/queue/signed-queue-service.ts +60 -0
- package/src/wirings/queue/validate-worker-config.ts +2 -28
- package/src/wirings/role/define-system-role.ts +33 -0
- package/src/wirings/role/index.ts +13 -0
- package/src/wirings/role/role.test.ts +104 -0
- package/src/wirings/role/role.types.ts +47 -0
- package/src/wirings/role/validate-role-definitions.ts +93 -0
- package/src/wirings/rpc/addon-auth-tags.test.ts +223 -0
- package/src/wirings/rpc/addon-runner.ts +0 -56
- package/src/wirings/rpc/addon-scopes.test.ts +225 -0
- package/src/wirings/rpc/remote-addon-auth.ts +9 -16
- package/src/wirings/rpc/rpc-runner.test.ts +192 -6
- package/src/wirings/rpc/rpc-runner.ts +145 -127
- package/src/wirings/rpc/rpc-types.ts +11 -6
- package/src/wirings/rpc/wire-addon.test.ts +43 -1
- package/src/wirings/rpc/wire-addon.ts +99 -0
- package/src/wirings/rpc/wire-remote-addon.ts +9 -29
- package/src/wirings/scheduler/log-schedulers.ts +0 -4
- package/src/wirings/scheduler/scheduler-runner.test.ts +1 -8
- package/src/wirings/scheduler/scheduler-runner.ts +0 -2
- package/src/wirings/scheduler/scheduler.types.ts +1 -14
- package/src/wirings/scope/{wire-scope.ts → define-scope.ts} +5 -6
- package/src/wirings/scope/index.ts +1 -1
- package/src/wirings/scope/scope.test.ts +1 -2
- package/src/wirings/scope/scope.types.ts +7 -9
- package/src/wirings/scope/validate-scope-definitions.ts +3 -21
- package/src/wirings/secret/index.ts +1 -1
- package/src/wirings/secret/secret.types.ts +19 -15
- package/src/wirings/secret/validate-secret-definitions.ts +2 -4
- package/src/wirings/trigger/trigger-runner.ts +1 -27
- package/src/wirings/trigger/trigger.types.ts +1 -82
- package/src/wirings/variable/index.ts +1 -1
- package/src/wirings/variable/validate-variable-definitions.ts +2 -4
- package/src/wirings/variable/variable.types.ts +1 -13
- package/src/wirings/virtual-user/index.ts +88 -0
- package/src/wirings/virtual-user/prepare-virtual-user-run.test.ts +115 -0
- package/src/wirings/virtual-user/prepare-virtual-user-run.ts +95 -0
- package/src/wirings/virtual-user/run-virtual-user.test.ts +765 -0
- package/src/wirings/virtual-user/run-virtual-user.ts +671 -0
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +65 -0
- package/src/wirings/virtual-user/virtual-user-agents.ts +57 -0
- package/src/wirings/virtual-user/virtual-user-catalogue.test.ts +215 -0
- package/src/wirings/virtual-user/virtual-user-catalogue.ts +184 -0
- package/src/wirings/virtual-user/virtual-user-derive.test.ts +398 -0
- package/src/wirings/virtual-user/virtual-user-derive.ts +173 -0
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +63 -0
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +213 -0
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +208 -0
- package/src/wirings/virtual-user/virtual-user-intents.ts +185 -0
- package/src/wirings/virtual-user/virtual-user-rng.test.ts +72 -0
- package/src/wirings/virtual-user/virtual-user-rng.ts +50 -0
- package/src/wirings/virtual-user/virtual-user-run-store.ts +98 -0
- package/src/wirings/virtual-user/virtual-user-target.ts +47 -0
- package/src/wirings/virtual-user/virtual-user.types.ts +219 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +19 -20
- package/src/wirings/workflow/dsl/workflow-runner.ts +0 -4
- package/src/wirings/workflow/feature.ts +0 -19
- package/src/wirings/workflow/graph/graph-node.ts +0 -136
- package/src/wirings/workflow/graph/graph-runner.test.ts +20 -19
- package/src/wirings/workflow/graph/graph-runner.ts +6 -41
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -4
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -58
- package/src/wirings/workflow/index.ts +10 -44
- package/src/wirings/workflow/pikku-scenario-service.ts +235 -61
- package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -39
- package/src/wirings/workflow/pikku-workflow-service.ts +77 -674
- package/src/wirings/workflow/run-timeline.test.ts +7 -19
- package/src/wirings/workflow/run-timeline.ts +0 -56
- package/src/wirings/workflow/scenario-cookie-jar.test.ts +0 -1
- package/src/wirings/workflow/scenario-cookie-jar.ts +0 -25
- package/src/wirings/workflow/scenario-expectations.test.ts +2 -7
- package/src/wirings/workflow/scenario-hooks.test.ts +2 -7
- package/src/wirings/workflow/scenario-poll.test.ts +0 -2
- package/src/wirings/workflow/scenario-poll.ts +0 -15
- package/src/wirings/workflow/scenario-prose.test.ts +5 -7
- package/src/wirings/workflow/scenario-prose.ts +1 -29
- package/src/wirings/workflow/scenario-service.test.ts +2 -10
- package/src/wirings/workflow/scenario-step-guards.ts +1 -14
- package/src/wirings/workflow/scenario-step.test.ts +163 -19
- package/src/wirings/workflow/scenario-step.types.ts +94 -12
- package/src/wirings/workflow/scenario-surface.test.ts +146 -0
- package/src/wirings/workflow/scenario-surface.ts +71 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +14 -15
- package/src/wirings/workflow/workflow-dispatch-payload.test.ts +0 -4
- package/src/wirings/workflow/workflow-inline-authority.test.ts +169 -0
- package/src/wirings/workflow/workflow-invocation-id.test.ts +0 -2
- package/src/wirings/workflow/workflow-invocation-id.ts +2 -22
- package/src/wirings/workflow/workflow-mirror.test.ts +0 -7
- package/src/wirings/workflow/workflow-on-error.test.ts +0 -9
- package/src/wirings/workflow/workflow-queue-workers.ts +0 -21
- package/src/wirings/workflow/workflow-replay-snapshot.test.ts +8 -7
- package/src/wirings/workflow/workflow-retry-policy.test.ts +0 -5
- package/src/wirings/workflow/workflow-run-context.test.ts +5 -10
- package/src/wirings/workflow/workflow-run-polling.test.ts +0 -5
- package/src/wirings/workflow/workflow-step-ordinal.test.ts +19 -4
- package/src/wirings/workflow/workflow-step-session.test.ts +0 -7
- package/src/wirings/workflow/workflow.types.ts +5 -201
- package/tsconfig.tsbuildinfo +1 -1
- package/tsconfig.type-tests.json +12 -0
- package/src/middleware/timeout.ts +0 -22
- package/src/pikku-response.ts +0 -5
- package/src/wirings/mcp/mcp-endpoint-registry.test.d.ts +0 -1
- package/src/wirings/workflow/dsl/index.ts +0 -36
- package/src/wirings/workflow/graph/index.ts +0 -15
|
@@ -5,8 +5,9 @@ import { createScenarioRunner } from './pikku-scenario-service.js'
|
|
|
5
5
|
import type { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js'
|
|
6
6
|
import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
7
7
|
import { addFunction } from '../../function/function-runner.js'
|
|
8
|
-
import type {
|
|
8
|
+
import type { ScenarioPersona } from '../../services/personas-service.js'
|
|
9
9
|
import type { PikkuWire } from '../../types/core.types.js'
|
|
10
|
+
import type { ScenarioSurface } from './scenario-step.types.js'
|
|
10
11
|
import { requireActor, requireScenarioEnv } from './scenario-step-guards.js'
|
|
11
12
|
|
|
12
13
|
const noopLogger = { error() {}, info() {}, warn() {}, debug() {} }
|
|
@@ -14,7 +15,7 @@ const noopLogger = { error() {}, info() {}, warn() {}, debug() {} }
|
|
|
14
15
|
const fakeActor = (
|
|
15
16
|
name: string,
|
|
16
17
|
handler: (rpcName: string, data: unknown) => Promise<unknown>
|
|
17
|
-
):
|
|
18
|
+
): ScenarioPersona & { calls: Array<{ rpcName: string; data: unknown }> } => {
|
|
18
19
|
const calls: Array<{ rpcName: string; data: unknown }> = []
|
|
19
20
|
return {
|
|
20
21
|
name,
|
|
@@ -87,8 +88,6 @@ describe('scenario actor steps (workflow.do with `actor`)', () => {
|
|
|
87
88
|
const first = await wire.do('step', 'someRpc', {}, { actor: yasser })
|
|
88
89
|
assert.deepEqual(first, { n: 1 })
|
|
89
90
|
|
|
90
|
-
// Simulate replay: reset per-run ordinals so the same logical step name
|
|
91
|
-
// resolves to the same durable step key.
|
|
92
91
|
await (ws as any).beginReplay(runId)
|
|
93
92
|
const replayWire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
94
93
|
const replayed = await replayWire.do(
|
|
@@ -113,7 +112,6 @@ describe('scenario actor steps (workflow.do with `actor`)', () => {
|
|
|
113
112
|
},
|
|
114
113
|
},
|
|
115
114
|
})
|
|
116
|
-
// Function meta says this RPC would normally dispatch via the queue.
|
|
117
115
|
pikkuState(null, 'function', 'meta').queuedRpc = {
|
|
118
116
|
pikkuFuncId: 'queuedRpc',
|
|
119
117
|
workflowQueued: true,
|
|
@@ -146,20 +144,18 @@ const registerStep = (
|
|
|
146
144
|
name: string,
|
|
147
145
|
config: {
|
|
148
146
|
description?: string
|
|
149
|
-
|
|
147
|
+
surfaces?: ScenarioSurface[]
|
|
150
148
|
func: (services: any, data: any, wire: PikkuWire) => Promise<unknown>
|
|
151
149
|
}
|
|
152
150
|
) => {
|
|
153
151
|
addFunction(name, config as any)
|
|
154
|
-
// `pikkuScenarioStep` maps to PikkuFunctionSessionless, which is what the
|
|
155
|
-
// inspector records — a step is driven by an actor, not by a wire session.
|
|
156
152
|
pikkuState(null, 'function', 'meta')[name] = {
|
|
157
153
|
pikkuFuncId: name,
|
|
158
154
|
sessionless: true,
|
|
159
155
|
} as any
|
|
160
156
|
}
|
|
161
157
|
|
|
162
|
-
describe('pikkuScenarioStep (scenario.
|
|
158
|
+
describe('pikkuScenarioStep (scenario.given/when/then)', () => {
|
|
163
159
|
beforeEach(() => resetPikkuState())
|
|
164
160
|
|
|
165
161
|
test('the step func is called with the phase, step identity and data on the wire', async () => {
|
|
@@ -206,12 +202,11 @@ describe('pikkuScenarioStep (scenario.step/given/when/then)', () => {
|
|
|
206
202
|
|
|
207
203
|
const runId = await setup(ws)
|
|
208
204
|
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
209
|
-
await wire.step('a', 'noop')
|
|
210
205
|
await wire.given('b', 'noop')
|
|
211
206
|
await wire.when('c', 'noop')
|
|
212
207
|
await wire.then('d', 'noop')
|
|
213
208
|
|
|
214
|
-
assert.deepEqual(phases, ['
|
|
209
|
+
assert.deepEqual(phases, ['given', 'when', 'then'])
|
|
215
210
|
})
|
|
216
211
|
|
|
217
212
|
test('the actor is handed to the step rather than used to dispatch it', async () => {
|
|
@@ -295,7 +290,7 @@ describe('pikkuScenarioStep (scenario.step/given/when/then)', () => {
|
|
|
295
290
|
|
|
296
291
|
const runId = await setup(ws)
|
|
297
292
|
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
298
|
-
const result = await wire.
|
|
293
|
+
const result = await wire.when('waits for the page', 'flaky', undefined, {
|
|
299
294
|
retries: 3,
|
|
300
295
|
retryDelay: 1,
|
|
301
296
|
})
|
|
@@ -306,9 +301,10 @@ describe('pikkuScenarioStep (scenario.step/given/when/then)', () => {
|
|
|
306
301
|
|
|
307
302
|
test('a browser step fails loudly when no provider is registered', async () => {
|
|
308
303
|
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
304
|
+
scenarioService.setRunSurface('browser')
|
|
309
305
|
const shopper = fakeActor('shopper', async () => ({}))
|
|
310
306
|
registerStep('visitsCheckout', {
|
|
311
|
-
|
|
307
|
+
surfaces: ['browser'],
|
|
312
308
|
func: async () => null,
|
|
313
309
|
})
|
|
314
310
|
|
|
@@ -325,6 +321,7 @@ describe('pikkuScenarioStep (scenario.step/given/when/then)', () => {
|
|
|
325
321
|
|
|
326
322
|
test('a registered provider hands the step a session keyed by the actor', async () => {
|
|
327
323
|
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
324
|
+
scenarioService.setRunSurface('browser')
|
|
328
325
|
const shopper = fakeActor('shopper', async () => ({}))
|
|
329
326
|
const requested: string[] = []
|
|
330
327
|
const session = { actor: 'shopper' } as any
|
|
@@ -337,7 +334,7 @@ describe('pikkuScenarioStep (scenario.step/given/when/then)', () => {
|
|
|
337
334
|
|
|
338
335
|
let handed: unknown
|
|
339
336
|
registerStep('visitsCheckout', {
|
|
340
|
-
|
|
337
|
+
surfaces: ['browser'],
|
|
341
338
|
func: async (_services, _data, wire) => {
|
|
342
339
|
handed = wire.browser
|
|
343
340
|
return null
|
|
@@ -354,13 +351,164 @@ describe('pikkuScenarioStep (scenario.step/given/when/then)', () => {
|
|
|
354
351
|
assert.equal(handed, session)
|
|
355
352
|
})
|
|
356
353
|
|
|
354
|
+
test('an action step falls back to its default binding rather than failing', async () => {
|
|
355
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
356
|
+
scenarioService.setRunSurface('browser')
|
|
357
|
+
const ran: ScenarioSurface[] = []
|
|
358
|
+
registerStep('seedsAnAccount', {
|
|
359
|
+
surfaces: ['default'],
|
|
360
|
+
func: async (_services, _data, wire) => {
|
|
361
|
+
ran.push(wire.scenarioStep!.surface)
|
|
362
|
+
return null
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const runId = await setup(ws)
|
|
367
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
368
|
+
await wire.given('an account exists', 'seedsAnAccount')
|
|
369
|
+
|
|
370
|
+
assert.deepEqual(ran, ['default'], 'setup does not need a UI to be honest')
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test('an action step with no runnable binding fails rather than silently skipping', async () => {
|
|
374
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
375
|
+
scenarioService.setRunSurface('cli')
|
|
376
|
+
registerStep('clicksBuy', {
|
|
377
|
+
surfaces: ['browser'],
|
|
378
|
+
func: async () => null,
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
const runId = await setup(ws)
|
|
382
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
383
|
+
|
|
384
|
+
await assert.rejects(
|
|
385
|
+
wire.when('shopper buys it', 'clicksBuy'),
|
|
386
|
+
/declares no binding for 'cli'/
|
|
387
|
+
)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test('a then step runs every witness, surface first', async () => {
|
|
391
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
392
|
+
scenarioService.setRunSurface('browser')
|
|
393
|
+
scenarioService.setScenarioBrowserProvider({
|
|
394
|
+
sessionFor: async () => ({ actor: 'shopper' }) as any,
|
|
395
|
+
} as any)
|
|
396
|
+
const shopper = fakeActor('shopper', async () => ({}))
|
|
397
|
+
const ran: ScenarioSurface[] = []
|
|
398
|
+
registerStep('seesTheOrderConfirmed', {
|
|
399
|
+
surfaces: ['browser', 'default'],
|
|
400
|
+
func: async (_services, _data, wire) => {
|
|
401
|
+
ran.push(wire.scenarioStep!.surface)
|
|
402
|
+
return { status: 'paid' }
|
|
403
|
+
},
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
const runId = await setup(ws)
|
|
407
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
408
|
+
await wire.then(
|
|
409
|
+
'shopper sees the order confirmed',
|
|
410
|
+
'seesTheOrderConfirmed',
|
|
411
|
+
undefined,
|
|
412
|
+
{ actor: shopper }
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
assert.deepEqual(ran, ['browser', 'default'])
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
test('the page saying something else than the database is a failure', async () => {
|
|
419
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
420
|
+
scenarioService.setRunSurface('browser')
|
|
421
|
+
scenarioService.setScenarioBrowserProvider({
|
|
422
|
+
sessionFor: async () => ({ actor: 'shopper' }) as any,
|
|
423
|
+
} as any)
|
|
424
|
+
const shopper = fakeActor('shopper', async () => ({}))
|
|
425
|
+
registerStep('seesTheOrderConfirmed', {
|
|
426
|
+
surfaces: ['browser', 'default'],
|
|
427
|
+
func: async (_services, _data, wire) =>
|
|
428
|
+
wire.scenarioStep!.surface === 'browser'
|
|
429
|
+
? { status: 'pending' }
|
|
430
|
+
: { status: 'paid' },
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
const runId = await setup(ws)
|
|
434
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
435
|
+
|
|
436
|
+
await assert.rejects(
|
|
437
|
+
wire.then(
|
|
438
|
+
'shopper sees the order confirmed',
|
|
439
|
+
'seesTheOrderConfirmed',
|
|
440
|
+
undefined,
|
|
441
|
+
{ actor: shopper }
|
|
442
|
+
),
|
|
443
|
+
/observed different things on different surfaces/
|
|
444
|
+
)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
test('a default run pays for only one witness', async () => {
|
|
448
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
449
|
+
const ran: ScenarioSurface[] = []
|
|
450
|
+
registerStep('seesTheOrderConfirmed', {
|
|
451
|
+
surfaces: ['browser', 'default'],
|
|
452
|
+
func: async (_services, _data, wire) => {
|
|
453
|
+
ran.push(wire.scenarioStep!.surface)
|
|
454
|
+
return { status: 'paid' }
|
|
455
|
+
},
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
const runId = await setup(ws)
|
|
459
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
460
|
+
await wire.then('shopper sees the order confirmed', 'seesTheOrderConfirmed')
|
|
461
|
+
|
|
462
|
+
assert.deepEqual(ran, ['default'])
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
test('an assertion with no witness for the run fails instead of passing having checked nothing', async () => {
|
|
466
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
467
|
+
scenarioService.setRunSurface('cli')
|
|
468
|
+
let ran = 0
|
|
469
|
+
registerStep('seesTheBanner', {
|
|
470
|
+
surfaces: ['browser'],
|
|
471
|
+
func: async () => {
|
|
472
|
+
ran++
|
|
473
|
+
return { visible: true }
|
|
474
|
+
},
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
const runId = await setup(ws)
|
|
478
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
479
|
+
|
|
480
|
+
await assert.rejects(
|
|
481
|
+
wire.then('shopper sees the banner', 'seesTheBanner'),
|
|
482
|
+
/has no witness to run on 'cli'/
|
|
483
|
+
)
|
|
484
|
+
assert.equal(ran, 0, 'nothing ran — which is exactly why it must not pass')
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
test('a browser-only assertion is not silently skipped by the fast suite', async () => {
|
|
488
|
+
// The fast path is where this is most dangerous: `--run default` prints no
|
|
489
|
+
// coverage line, so a skipped assertion would leave no trace at all.
|
|
490
|
+
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
491
|
+
registerStep('seesTheBanner', {
|
|
492
|
+
surfaces: ['browser'],
|
|
493
|
+
func: async () => ({ visible: true }),
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
const runId = await setup(ws)
|
|
497
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
498
|
+
|
|
499
|
+
await assert.rejects(
|
|
500
|
+
wire.then('shopper sees the banner', 'seesTheBanner'),
|
|
501
|
+
/has no witness to run on 'default'/
|
|
502
|
+
)
|
|
503
|
+
})
|
|
504
|
+
|
|
357
505
|
test('a non-string step target is rejected instead of silently dispatching', async () => {
|
|
358
506
|
const { workflowService: ws, scenarioService } = createScenarioRunner()
|
|
359
507
|
const runId = await setup(ws)
|
|
360
508
|
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
361
509
|
|
|
362
510
|
await assert.rejects(
|
|
363
|
-
(wire.
|
|
511
|
+
(wire.when as any)('a name', async () => 'inline'),
|
|
364
512
|
/string/i
|
|
365
513
|
)
|
|
366
514
|
})
|
|
@@ -446,8 +594,6 @@ describe('scenario step input is recorded on the run', () => {
|
|
|
446
594
|
packageName: '@pikku/addon-todos',
|
|
447
595
|
})
|
|
448
596
|
|
|
449
|
-
// A reporter renders each step's prose from this, so two calls to one step
|
|
450
|
-
// must be distinguishable by what they were asked to check.
|
|
451
597
|
const steps = await ws.getRunSteps(runId)
|
|
452
598
|
assert.deepEqual(
|
|
453
599
|
steps.map((step) => step.data),
|
|
@@ -496,8 +642,6 @@ describe('scenario step names its function on the run', () => {
|
|
|
496
642
|
|
|
497
643
|
const runId = await setup(ws)
|
|
498
644
|
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
499
|
-
// What a loop produces: the durable name is the interpolated one, so it
|
|
500
|
-
// matches no statically recorded step name.
|
|
501
645
|
for (const packageName of ['console', 'todos']) {
|
|
502
646
|
await wire.then(`sees ${packageName}`, 'seesAddon', { packageName })
|
|
503
647
|
}
|
|
@@ -1,28 +1,105 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ScenarioPersona } from '../../services/personas-service.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Scenario steps: named, typed units of scenario behaviour.
|
|
5
5
|
*
|
|
6
6
|
* A step's body is an ordinary pikku function, so it may drive a browser, call
|
|
7
|
-
* an RPC as its actor, or run a workflow. `given
|
|
8
|
-
*
|
|
7
|
+
* an RPC as its actor, or run a workflow. `given` and `when` differ only in the
|
|
8
|
+
* prose the reporter renders; `then` also changes what runs, because its
|
|
9
|
+
* bindings are witnesses — every declared surface is observed and the
|
|
10
|
+
* observations must agree. See {@link ScenarioSurfaceResolution}.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
* Which Gherkin-style keyword the reporter prefixes this step with.
|
|
13
|
-
*
|
|
14
|
+
* Which Gherkin-style keyword the reporter prefixes this step with.
|
|
15
|
+
*
|
|
16
|
+
* Every step takes one. A scenario is read by people deciding whether it
|
|
17
|
+
* describes the behaviour they wanted, and a step that says only what it does
|
|
18
|
+
* without saying whether it is setup, action or claim is the one nobody can
|
|
19
|
+
* check — which is also why PKU680 can tell a scenario that never asserts.
|
|
20
|
+
*/
|
|
21
|
+
export type ScenarioStepPhase = 'given' | 'when' | 'then'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Who acts in a step.
|
|
25
|
+
*
|
|
26
|
+
* `persona` is a person driving the system through one of its surfaces — the
|
|
27
|
+
* ordinary `pikkuScenarioStep`. `platform` is the app acting on itself ("the
|
|
28
|
+
* platform has expired the trial"), and `addon` is a third-party system acting
|
|
29
|
+
* on it ("Stripe's webhook arrives"), contributed by the addon that wraps that
|
|
30
|
+
* service.
|
|
31
|
+
*
|
|
32
|
+
* The three are separate declarations rather than a field on one, so the
|
|
33
|
+
* inspector verifies a step's kind by which function was called rather than by
|
|
34
|
+
* trusting a string literal — and so an addon package exports only addon steps,
|
|
35
|
+
* which is checkable.
|
|
14
36
|
*/
|
|
15
|
-
export type
|
|
37
|
+
export type ScenarioStepKind = 'persona' | 'platform' | 'addon'
|
|
16
38
|
|
|
17
39
|
/**
|
|
18
|
-
*
|
|
40
|
+
* How an actor drives the system for one step.
|
|
41
|
+
*
|
|
42
|
+
* A step declares one implementation per surface it supports, and the runner
|
|
43
|
+
* picks between them — so the same ladder can run through a real browser, over
|
|
44
|
+
* the websocket, or entirely server-side.
|
|
45
|
+
*
|
|
46
|
+
* `default` is the floor: it is what every other surface falls back to, so it
|
|
47
|
+
* can never itself fall back.
|
|
48
|
+
*/
|
|
49
|
+
export type ScenarioSurface = 'browser' | 'cli' | 'default'
|
|
50
|
+
|
|
51
|
+
export const SCENARIO_SURFACES: readonly ScenarioSurface[] = [
|
|
52
|
+
'browser',
|
|
53
|
+
'cli',
|
|
54
|
+
'default',
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* How a step's declared surfaces resolve for one run.
|
|
59
|
+
*
|
|
60
|
+
* `given`/`when` bindings are **alternatives** — clicking Buy and calling
|
|
61
|
+
* `createOrder` are two ways to cause one effect, so exactly one runs.
|
|
62
|
+
*
|
|
63
|
+
* `then` bindings are **witnesses** — "the order row says paid" and "the
|
|
64
|
+
* confirmation panel says paid" are different claims that share a name, and the
|
|
65
|
+
* gap between them is the bug nobody catches. So every declared witness runs and
|
|
66
|
+
* they must agree.
|
|
67
|
+
*/
|
|
68
|
+
export type ScenarioSurfaceResolution =
|
|
69
|
+
| {
|
|
70
|
+
kind: 'action'
|
|
71
|
+
surface: ScenarioSurface
|
|
72
|
+
/** The run asked for a surface this step does not implement. */
|
|
73
|
+
fellBack: boolean
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
kind: 'witness'
|
|
77
|
+
/**
|
|
78
|
+
* Every witness to run, surface first. Empty means the assertion has no
|
|
79
|
+
* binding this run can execute at all — nothing would check anything, so
|
|
80
|
+
* the step fails rather than reporting a pass it never earned.
|
|
81
|
+
*/
|
|
82
|
+
surfaces: ScenarioSurface[]
|
|
83
|
+
/**
|
|
84
|
+
* Something checked the assertion, but not on the surface its prose
|
|
85
|
+
* claims — so nobody looked at the UI. Reported as its own state and
|
|
86
|
+
* counted against coverage: not being in the UI *is* the finding.
|
|
87
|
+
*
|
|
88
|
+
* Disjoint from an empty `surfaces`; an assertion that ran nowhere is a
|
|
89
|
+
* failure, not a coverage statistic.
|
|
90
|
+
*/
|
|
91
|
+
unwitnessed: boolean
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Options accepted by `scenario.given/when/then`.
|
|
19
96
|
*
|
|
20
97
|
* Note the retry default differs from an ordinary workflow step: retrying a
|
|
21
98
|
* failed assertion is the wrong behaviour for a test primitive, so steps
|
|
22
99
|
* default to no retries.
|
|
23
100
|
*/
|
|
24
101
|
export interface ScenarioStepOptions {
|
|
25
|
-
/** The actor this step runs as. Required for steps declaring `browser
|
|
102
|
+
/** The actor this step runs as. Required for steps declaring a `browser` binding. */
|
|
26
103
|
actor?: unknown
|
|
27
104
|
/** Overrides the step's own `description` for this call site only. */
|
|
28
105
|
description?: string
|
|
@@ -33,7 +110,7 @@ export interface ScenarioStepOptions {
|
|
|
33
110
|
|
|
34
111
|
/**
|
|
35
112
|
* The environment a scenario run targets, as declared in pikku.config.json
|
|
36
|
-
* under `
|
|
113
|
+
* under `environments`.
|
|
37
114
|
*/
|
|
38
115
|
export interface ScenarioEnvironment {
|
|
39
116
|
/** Base API URL of the target app, INCLUDING the HTTP prefix. */
|
|
@@ -46,16 +123,21 @@ export interface ScenarioEnvironment {
|
|
|
46
123
|
* The `scenarioStep` wire, present on every scenario step invocation.
|
|
47
124
|
*
|
|
48
125
|
* `TActor` is the project's own actor type, so a step reaches only the RPCs its
|
|
49
|
-
* actors can actually call. It defaults to the open `
|
|
126
|
+
* actors can actually call. It defaults to the open `ScenarioPersona` for a
|
|
50
127
|
* project that declares no registry.
|
|
51
128
|
*/
|
|
52
|
-
export interface PikkuScenarioStepWire<TActor =
|
|
129
|
+
export interface PikkuScenarioStepWire<TActor = ScenarioPersona> {
|
|
53
130
|
/** Registered step name (also its pikkuFuncId) */
|
|
54
131
|
name: string
|
|
55
132
|
/** Durable key within the run; may carry an `#ordinal` suffix when repeated */
|
|
56
133
|
stepName: string
|
|
57
134
|
runId: string
|
|
58
135
|
phase: ScenarioStepPhase
|
|
136
|
+
/**
|
|
137
|
+
* Which of the step's bindings is currently executing. A `then` step runs once
|
|
138
|
+
* per witness, so its body sees this change between invocations.
|
|
139
|
+
*/
|
|
140
|
+
surface: ScenarioSurface
|
|
59
141
|
/**
|
|
60
142
|
* The actor this step runs as, when one was given. Call RPCs through it
|
|
61
143
|
* (`actor.invoke(...)`) so they run against the target environment as that
|
|
@@ -96,7 +178,7 @@ export interface TestIdSelector {
|
|
|
96
178
|
|
|
97
179
|
/**
|
|
98
180
|
* Structural browser handle, present only when the runner provisioned a
|
|
99
|
-
* browser for this step (`browser
|
|
181
|
+
* browser for this step (a `browser` binding on the step config).
|
|
100
182
|
*
|
|
101
183
|
* `@pikku/core` deliberately never imports playwright — it must stay
|
|
102
184
|
* dependency-free for edge runtimes. `@pikku/playwright` augments this
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js'
|
|
4
|
+
import type { ScenarioSurface } from './scenario-step.types.js'
|
|
5
|
+
|
|
6
|
+
const ALL: ScenarioSurface[] = ['browser', 'cli', 'default']
|
|
7
|
+
|
|
8
|
+
describe('action steps pick exactly one binding', () => {
|
|
9
|
+
test('runs the binding the run asked for', () => {
|
|
10
|
+
assert.deepEqual(resolveScenarioSurfaces('when', ALL, 'browser'), {
|
|
11
|
+
kind: 'action',
|
|
12
|
+
surface: 'browser',
|
|
13
|
+
fellBack: false,
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('falls back to default and says so when the surface is not implemented', () => {
|
|
18
|
+
assert.deepEqual(resolveScenarioSurfaces('when', ['default'], 'browser'), {
|
|
19
|
+
kind: 'action',
|
|
20
|
+
surface: 'default',
|
|
21
|
+
fellBack: true,
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('given resolves like when, not like then', () => {
|
|
26
|
+
assert.deepEqual(resolveScenarioSurfaces('given', ALL, 'cli'), {
|
|
27
|
+
kind: 'action',
|
|
28
|
+
surface: 'cli',
|
|
29
|
+
fellBack: false,
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('a default run never counts as a fallback', () => {
|
|
34
|
+
assert.deepEqual(resolveScenarioSurfaces('when', ['default'], 'default'), {
|
|
35
|
+
kind: 'action',
|
|
36
|
+
surface: 'default',
|
|
37
|
+
fellBack: false,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('then steps run every witness', () => {
|
|
43
|
+
test('a browser run checks the page AND the system of record', () => {
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
resolveScenarioSurfaces('then', ['browser', 'default'], 'browser'),
|
|
46
|
+
{ kind: 'witness', surfaces: ['browser', 'default'], unwitnessed: false }
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('the surface witness comes first, so its failure is the one reported', () => {
|
|
51
|
+
const resolution = resolveScenarioSurfaces(
|
|
52
|
+
'then',
|
|
53
|
+
['default', 'browser'],
|
|
54
|
+
'browser'
|
|
55
|
+
)
|
|
56
|
+
assert.equal(resolution.kind, 'witness')
|
|
57
|
+
assert.deepEqual(
|
|
58
|
+
resolution.kind === 'witness' ? resolution.surfaces[0] : null,
|
|
59
|
+
'browser'
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('a default run is unchanged — one witness, no extra cost', () => {
|
|
64
|
+
assert.deepEqual(
|
|
65
|
+
resolveScenarioSurfaces('then', ['browser', 'default'], 'default'),
|
|
66
|
+
{ kind: 'witness', surfaces: ['default'], unwitnessed: false }
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('no witness for the run surface is flagged rather than excused', () => {
|
|
71
|
+
assert.deepEqual(resolveScenarioSurfaces('then', ['default'], 'browser'), {
|
|
72
|
+
kind: 'witness',
|
|
73
|
+
surfaces: ['default'],
|
|
74
|
+
unwitnessed: true,
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('a browser-only assertion has nothing to run on a default run', () => {
|
|
79
|
+
// No witness at all is not a coverage statistic — nothing checked anything,
|
|
80
|
+
// so `unwitnessed` stays false and the caller is expected to fail the step.
|
|
81
|
+
assert.deepEqual(resolveScenarioSurfaces('then', ['browser'], 'default'), {
|
|
82
|
+
kind: 'witness',
|
|
83
|
+
surfaces: [],
|
|
84
|
+
unwitnessed: false,
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('an assertion that runs nowhere is not counted as merely unwitnessed', () => {
|
|
89
|
+
assert.deepEqual(resolveScenarioSurfaces('then', ['browser'], 'cli'), {
|
|
90
|
+
kind: 'witness',
|
|
91
|
+
surfaces: [],
|
|
92
|
+
unwitnessed: false,
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('unwitnessed means checked somewhere else, never checked nowhere', () => {
|
|
97
|
+
const ranElsewhere = resolveScenarioSurfaces('then', ['default'], 'cli')
|
|
98
|
+
assert.equal(ranElsewhere.kind, 'witness')
|
|
99
|
+
assert.ok(
|
|
100
|
+
ranElsewhere.kind === 'witness' && ranElsewhere.surfaces.length > 0,
|
|
101
|
+
'the two states are disjoint: unwitnessed always has something that ran'
|
|
102
|
+
)
|
|
103
|
+
assert.equal(
|
|
104
|
+
ranElsewhere.kind === 'witness' && ranElsewhere.unwitnessed,
|
|
105
|
+
true
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('witness agreement', () => {
|
|
111
|
+
test('key order does not make two observations disagree', () => {
|
|
112
|
+
assert.equal(
|
|
113
|
+
witnessesAgree(
|
|
114
|
+
{ status: 'paid', total: 500 },
|
|
115
|
+
{ total: 500, status: 'paid' }
|
|
116
|
+
),
|
|
117
|
+
true
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('a page showing something else is a disagreement', () => {
|
|
122
|
+
assert.equal(
|
|
123
|
+
witnessesAgree({ status: 'paid' }, { status: 'pending' }),
|
|
124
|
+
false
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('a witness that asserted by throwing is not compared', () => {
|
|
129
|
+
assert.equal(witnessesAgree(undefined, { status: 'paid' }), true)
|
|
130
|
+
assert.equal(witnessesAgree({ status: 'paid' }, undefined), true)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('nested observations compare structurally', () => {
|
|
134
|
+
assert.equal(
|
|
135
|
+
witnessesAgree(
|
|
136
|
+
{ order: { items: [{ sku: 'a' }, { sku: 'b' }] } },
|
|
137
|
+
{ order: { items: [{ sku: 'a' }, { sku: 'b' }] } }
|
|
138
|
+
),
|
|
139
|
+
true
|
|
140
|
+
)
|
|
141
|
+
assert.equal(
|
|
142
|
+
witnessesAgree({ items: ['a', 'b'] }, { items: ['b', 'a'] }),
|
|
143
|
+
false
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScenarioStepPhase,
|
|
3
|
+
ScenarioSurface,
|
|
4
|
+
ScenarioSurfaceResolution,
|
|
5
|
+
} from './scenario-step.types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve which of a step's declared bindings run, for one step, in one run.
|
|
9
|
+
*
|
|
10
|
+
* The asymmetry between phases is the whole point — see
|
|
11
|
+
* {@link ScenarioSurfaceResolution}.
|
|
12
|
+
*/
|
|
13
|
+
export const resolveScenarioSurfaces = (
|
|
14
|
+
phase: ScenarioStepPhase,
|
|
15
|
+
declared: readonly ScenarioSurface[],
|
|
16
|
+
runSurface: ScenarioSurface
|
|
17
|
+
): ScenarioSurfaceResolution => {
|
|
18
|
+
const has = (surface: ScenarioSurface) => declared.includes(surface)
|
|
19
|
+
|
|
20
|
+
if (phase !== 'then') {
|
|
21
|
+
if (has(runSurface)) {
|
|
22
|
+
return { kind: 'action', surface: runSurface, fellBack: false }
|
|
23
|
+
}
|
|
24
|
+
return { kind: 'action', surface: 'default', fellBack: true }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const surfaces: ScenarioSurface[] = []
|
|
28
|
+
if (runSurface !== 'default' && has(runSurface)) {
|
|
29
|
+
surfaces.push(runSurface)
|
|
30
|
+
}
|
|
31
|
+
if (has('default')) {
|
|
32
|
+
surfaces.push('default')
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
kind: 'witness',
|
|
36
|
+
surfaces,
|
|
37
|
+
// Only meaningful once something ran: an assertion checked server-side
|
|
38
|
+
// under `--run browser` is a coverage gap, but one checked nowhere at all
|
|
39
|
+
// is a step that did not happen, which the caller fails rather than counts.
|
|
40
|
+
unwitnessed:
|
|
41
|
+
surfaces.length > 0 && runSurface !== 'default' && !has(runSurface),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Structural comparison of two witnesses' observations.
|
|
47
|
+
*
|
|
48
|
+
* A witness that returns `undefined` asserted by throwing rather than by
|
|
49
|
+
* reporting what it saw, so it is not compared — that keeps assertion-style
|
|
50
|
+
* steps working without forcing every one of them to return a value.
|
|
51
|
+
*/
|
|
52
|
+
export const witnessesAgree = (a: unknown, b: unknown): boolean => {
|
|
53
|
+
if (a === undefined || b === undefined) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
return stableStringify(a) === stableStringify(b)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stableStringify = (value: unknown): string => {
|
|
60
|
+
if (value === null || typeof value !== 'object') {
|
|
61
|
+
return JSON.stringify(value) ?? 'undefined'
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return `[${value.map(stableStringify).join(',')}]`
|
|
65
|
+
}
|
|
66
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
67
|
+
.filter(([, v]) => v !== undefined)
|
|
68
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
69
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)
|
|
70
|
+
return `{${entries.join(',')}}`
|
|
71
|
+
}
|