@pikku/core 0.12.141 → 0.12.143
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 +50 -0
- package/dist/errors/errors.d.ts +1 -1
- package/dist/errors/errors.js +1 -1
- package/dist/function/function-meta.types.d.ts +6 -6
- package/dist/function/functions.types.d.ts +2 -2
- package/dist/services/audit-service.d.ts +4 -4
- package/dist/services/feature-flag-service.d.ts +3 -3
- package/dist/services/{http-personas.d.ts → http-virtual-users.d.ts} +25 -47
- package/dist/services/{http-personas.js → http-virtual-users.js} +29 -53
- package/dist/services/in-memory-queue-service.d.ts +8 -1
- package/dist/services/in-memory-queue-service.js +67 -0
- package/dist/services/incoming-webhook-service.d.ts +2 -0
- package/dist/services/incoming-webhook-service.js +4 -0
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +1 -1
- package/dist/services/meta-service.d.ts +6 -6
- package/dist/services/meta-service.js +8 -8
- package/dist/{wirings/workflow → services}/scenario-cookie-jar.d.ts +2 -0
- package/dist/{wirings/workflow → services}/scenario-cookie-jar.js +18 -14
- package/dist/services/scheduler-service.d.ts +3 -0
- package/dist/services/scheduler-service.js +4 -0
- package/dist/services/system-role-guard.d.ts +1 -1
- package/dist/services/virtual-user-sign-in.d.ts +52 -0
- package/dist/services/virtual-user-sign-in.js +80 -0
- package/dist/services/virtual-user-statement.d.ts +31 -0
- package/dist/services/virtual-user-statement.js +24 -0
- package/dist/services/{personas-service.d.ts → virtual-users-service.d.ts} +25 -23
- package/dist/services/{personas-service.js → virtual-users-service.js} +3 -3
- package/dist/types/core.types.d.ts +11 -11
- package/dist/wirings/queue/index.d.ts +1 -1
- package/dist/wirings/queue/queue.types.d.ts +21 -0
- package/dist/wirings/queue/signed-queue-service.d.ts +4 -1
- package/dist/wirings/queue/signed-queue-service.js +3 -0
- package/dist/wirings/role/define-system-role.d.ts +1 -1
- package/dist/wirings/role/define-system-role.js +1 -1
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/scheduler/scheduler.types.d.ts +12 -0
- package/dist/wirings/{actor-flow/actor-flow.types.d.ts → virtual-user/conversation/conversation.types.d.ts} +5 -5
- package/dist/wirings/virtual-user/conversation/index.d.ts +2 -0
- package/dist/wirings/virtual-user/conversation/run-conversation.d.ts +36 -0
- package/dist/wirings/{actor-flow → virtual-user/conversation}/run-conversation.js +30 -30
- package/dist/wirings/virtual-user/define-virtual-users.d.ts +32 -0
- package/dist/wirings/{persona/define-personas.js → virtual-user/define-virtual-users.js} +9 -9
- package/dist/wirings/virtual-user/index.d.ts +30 -7
- package/dist/wirings/virtual-user/index.js +23 -4
- package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +8 -8
- package/dist/wirings/virtual-user/prepare-virtual-user-run.js +9 -9
- package/dist/wirings/virtual-user/run-virtual-user.d.ts +6 -6
- package/dist/wirings/virtual-user/run-virtual-user.js +10 -10
- package/dist/wirings/{persona/validate-personas.d.ts → virtual-user/validate-virtual-users.d.ts} +11 -11
- package/dist/wirings/{persona/validate-personas.js → virtual-user/validate-virtual-users.js} +24 -24
- package/dist/wirings/virtual-user/virtual-user-agents.d.ts +7 -7
- package/dist/wirings/virtual-user/virtual-user-agents.js +2 -2
- package/dist/wirings/{persona/persona-app-scopes.d.ts → virtual-user/virtual-user-app-scopes.d.ts} +10 -10
- package/dist/wirings/{persona/persona-app-scopes.js → virtual-user/virtual-user-app-scopes.js} +12 -12
- package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +3 -3
- package/dist/wirings/virtual-user/virtual-user-catalogue.js +2 -2
- package/dist/wirings/{persona/persona.types.d.ts → virtual-user/virtual-user-declaration.types.d.ts} +24 -24
- package/dist/wirings/virtual-user/virtual-user-derive.js +13 -7
- package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +1 -1
- package/dist/wirings/{persona/persona-email.d.ts → virtual-user/virtual-user-email.d.ts} +11 -11
- package/dist/wirings/{persona/persona-email.js → virtual-user/virtual-user-email.js} +19 -19
- package/dist/wirings/{persona/persona-environments.d.ts → virtual-user/virtual-user-environments.d.ts} +10 -10
- package/dist/wirings/{persona/persona-environments.js → virtual-user/virtual-user-environments.js} +21 -21
- package/dist/wirings/virtual-user/virtual-user-intents.d.ts +3 -3
- package/dist/wirings/virtual-user/virtual-user-intents.js +3 -3
- package/dist/wirings/{persona/persona-mailbox.d.ts → virtual-user/virtual-user-mailbox.d.ts} +4 -4
- package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +4 -4
- package/dist/wirings/virtual-user/virtual-user-scaffold.d.ts +42 -61
- package/dist/wirings/virtual-user/virtual-user-scaffold.js +69 -117
- package/dist/wirings/virtual-user/virtual-user-schedule-store.d.ts +15 -15
- package/dist/wirings/virtual-user/virtual-user-schedule.d.ts +6 -6
- package/dist/wirings/virtual-user/virtual-user-schedule.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-target.d.ts +6 -6
- package/dist/wirings/virtual-user/virtual-user-target.js +5 -5
- package/dist/wirings/virtual-user/virtual-user.types.d.ts +11 -11
- package/dist/wirings/virtual-user/virtual-user.types.js +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +9 -9
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +28 -28
- package/dist/wirings/workflow/pikku-scenario-service.js +77 -77
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +4 -4
- package/dist/wirings/workflow/scenario-prose.d.ts +8 -8
- package/dist/wirings/workflow/scenario-prose.js +12 -8
- package/dist/wirings/workflow/scenario-run.types.d.ts +13 -13
- package/dist/wirings/workflow/scenario-step.types.d.ts +23 -23
- package/dist/wirings/workflow/workflow.types.d.ts +2 -2
- package/knowledge/decisions/index.md +0 -2
- package/knowledge/decisions/internals/a-credential-name-declared-twice-must-agree-on-schema-and-type.md +29 -0
- package/knowledge/decisions/internals/a-grant-can-name-a-scope-declared-without-its-addon.md +29 -0
- package/knowledge/decisions/internals/a-latency-budget-is-asserted-by-its-cause.md +32 -0
- package/knowledge/decisions/internals/a-scheduled-task-writes-its-caller-session-before-it-runs.md +22 -0
- package/knowledge/decisions/internals/a-secret-set-in-memory-shadows-the-variable-of-the-same-name.md +28 -0
- package/knowledge/decisions/internals/a-virtual-user-acts-through-invokeraw-so-a-refusal-is-data.md +32 -0
- package/knowledge/decisions/internals/a-virtual-user-cadence-is-a-row-not-a-timer.md +12 -12
- package/knowledge/decisions/internals/a-virtual-user-converse-signs-in-before-its-first-turn.md +24 -0
- package/knowledge/decisions/internals/a-virtual-user-email-is-computed-with-the-run-id-as-a-sub-address.md +34 -0
- package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-but-it-needs-a-trigger.md +2 -2
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +3 -0
- package/knowledge/decisions/internals/an-agent-is-offered-under-its-registry-key-and-only-within-the-virtual-users-scopes.md +27 -0
- package/knowledge/decisions/internals/an-oauth2-credential-implies-an-optional-app-secret.md +26 -0
- package/knowledge/decisions/internals/analytics-identity-resolvers-run-in-declared-order.md +22 -0
- package/knowledge/decisions/internals/feature-flag-declarations-refuse-an-empty-anyof-and-conflicting-scopes.md +22 -0
- package/knowledge/decisions/internals/feature-flags-fail-open-and-never-enforce-access.md +28 -0
- package/knowledge/decisions/internals/flag-sync-rewrites-only-the-declared-text.md +24 -0
- package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +3 -3
- package/knowledge/decisions/internals/index.md +47 -31
- package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +10 -10
- package/knowledge/decisions/internals/role-names-cannot-contain-the-scope-separator.md +30 -0
- package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +23 -19
- package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +2 -2
- package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +12 -12
- package/knowledge/decisions/internals/the-core-scope-validator-refuses-separator-wildcard-and-empty-segments.md +22 -0
- package/knowledge/decisions/internals/the-pikku-user-id-resolves-in-a-fixed-order.md +24 -0
- package/knowledge/decisions/internals/{the-actor-prompt-says-json-because-of-json-object-mode.md → the-virtual-user-prompt-says-json-because-of-json-object-mode.md} +4 -4
- package/knowledge/decisions/internals/{the-persona-runtime-is-exported-from-the-persona-entry-point.md → the-virtual-user-runtime-is-exported-from-the-virtual-user-entry-point.md} +8 -8
- package/knowledge/decisions/internals/validate-checks-virtual-users-through-a-shared-module.md +43 -0
- package/knowledge/decisions/internals/{actor-flow-drives-the-target-through-a-transport-seam.md → virtual-user-conversation-drives-the-target-through-a-transport-seam.md} +6 -6
- package/knowledge/decisions/internals/virtual-user-conversation-verdicts-are-llm-self-evaluations.md +25 -0
- package/knowledge/decisions/internals/virtual-user-conversations-seed-a-hidden-kickoff-message.md +23 -0
- package/knowledge/decisions/internals/virtual-users-are-declared-in-one-definevirtualusers-call-per-codebase.md +30 -0
- package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +5 -5
- package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +2 -2
- package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +4 -4
- package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
- package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +2 -2
- package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +1 -1
- package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +1 -1
- package/knowledge/decisions/security/a-bearer-token-checked-against-a-secret-reads-it-per-request.md +26 -0
- package/knowledge/decisions/security/a-call-without-a-session-raises-missing-session-not-forbidden.md +20 -0
- package/knowledge/decisions/security/a-caller-cookie-wins-over-the-jar-for-the-same-name.md +23 -0
- package/knowledge/decisions/security/a-channel-lifecycle-acts-as-the-socket-upgrade-session.md +16 -0
- package/knowledge/decisions/security/a-channel-requires-a-session-unless-it-says-auth-false.md +26 -0
- package/knowledge/decisions/security/a-credential-read-path-follows-its-declared-type-not-absence.md +32 -0
- package/knowledge/decisions/security/a-credential-type-is-a-literal-the-inspector-checks-at-declaration.md +27 -0
- package/knowledge/decisions/security/a-first-party-cookie-is-written-only-after-its-purposes-consent.md +33 -0
- package/knowledge/decisions/security/a-readonly-session-reaches-only-readonly-functions.md +25 -0
- package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md +10 -5
- package/knowledge/decisions/security/a-scheduled-virtual-user-run-needs-no-admin-at-run-time.md +27 -0
- package/knowledge/decisions/security/a-scoped-credential-view-is-limited-to-declared-names.md +26 -0
- package/knowledge/decisions/security/a-scoped-secret-view-reads-only-declared-keys-and-never-writes.md +25 -0
- package/knowledge/decisions/security/a-secret-is-sent-only-to-the-hosts-it-declares.md +29 -0
- package/knowledge/decisions/security/a-step-can-name-an-identity-no-virtual-user-holds-through-per-call-headers.md +25 -0
- package/knowledge/decisions/security/a-stopped-run-by-id-skips-the-ownership-check.md +24 -0
- package/knowledge/decisions/security/a-vault-secret-redacts-on-serialization-and-throws-on-coercion.md +29 -0
- package/knowledge/decisions/security/a-virtual-user-catalogue-entry-is-filtered-by-scope-and-approval-is-denied-by-default.md +30 -0
- package/knowledge/decisions/security/a-virtual-user-environment-fails-closed.md +38 -0
- package/knowledge/decisions/security/a-virtual-user-never-runs-against-production.md +23 -0
- package/knowledge/decisions/security/a-virtual-user-reads-its-roles-from-the-apps-own-rpc-before-better-auth.md +27 -0
- package/knowledge/decisions/security/a-virtual-user-run-treats-an-unresolved-environment-as-production.md +41 -0
- package/knowledge/decisions/security/a-virtual-user-scopes-are-expanded-from-its-roles-once-at-run-preparation.md +25 -0
- package/knowledge/decisions/security/a-virtual-user-whose-roles-drift-from-the-declaration-refuses-to-run.md +32 -0
- package/knowledge/decisions/security/addon-screen-scopes-hide-and-do-not-enforce.md +20 -0
- package/knowledge/decisions/security/an-addon-header-can-only-narrow-the-session.md +26 -0
- package/knowledge/decisions/security/an-addon-instance-reads-only-the-secrets-and-credentials-it-is-named.md +33 -0
- package/knowledge/decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md +16 -0
- package/knowledge/decisions/security/an-api-key-is-jwt-decoded-into-the-session.md +26 -0
- package/knowledge/decisions/security/an-external-virtual-user-caller-presents-one-signed-statement.md +38 -0
- package/knowledge/decisions/security/an-inbound-webhook-source-dispatches-only-verified-requests.md +31 -0
- package/knowledge/decisions/security/an-mcp-call-with-no-http-request-stays-anonymous.md +18 -0
- package/knowledge/decisions/security/an-omitted-user-id-addresses-the-platform-credential.md +32 -0
- package/knowledge/decisions/security/analytics-identity-is-stamped-from-the-session-not-the-event.md +24 -0
- package/knowledge/decisions/security/app-access-is-a-scope-tree-synthesised-from-virtual-users.md +26 -0
- package/knowledge/decisions/security/approval-policy-decides-who-answers-a-tool-call.md +27 -0
- package/knowledge/decisions/security/auth-false-on-a-session-function-warns-and-still-requires-one.md +25 -0
- package/knowledge/decisions/security/bearer-token-hashes-are-unpeppered-sha256.md +18 -0
- package/knowledge/decisions/security/consent-is-read-from-a-cookie-and-a-recognised-denial-counts-as-refusal.md +29 -0
- package/knowledge/decisions/security/cors-defaults-to-any-origin-and-refuses-the-wildcard-with-credentials.md +22 -0
- package/knowledge/decisions/security/credential-backends-share-one-conformance-suite-for-per-user-isolation.md +29 -0
- package/knowledge/decisions/security/each-stage-has-one-virtual-user-key-pair.md +27 -0
- package/knowledge/decisions/security/hmac-verification-fails-closed-on-empty-inputs.md +25 -0
- package/knowledge/decisions/security/index.md +88 -24
- package/knowledge/decisions/security/local-content-refuses-private-buckets-under-the-public-segment.md +21 -0
- package/knowledge/decisions/security/mail-is-allowlisted-before-a-virtual-user-run-sees-it.md +38 -0
- package/knowledge/decisions/security/mcp-resources-tools-and-prompts-take-sessionless-functions.md +18 -0
- package/knowledge/decisions/security/origin-locked-routes-match-the-origin-exactly.md +22 -0
- package/knowledge/decisions/security/oss-auth-works-without-pikku-studio-cloud.md +26 -0
- package/knowledge/decisions/security/outbound-webhooks-use-an-allowlist-when-configured.md +24 -0
- package/knowledge/decisions/security/pikku-carries-virtual-user-scopes-as-data-and-the-app-grants-them.md +35 -0
- package/knowledge/decisions/security/scoped-content-access-is-a-root-folder-with-longest-prefix-grants.md +31 -0
- package/knowledge/decisions/security/services-without-secrets-get-a-throwing-secrets-accessor.md +22 -0
- package/knowledge/decisions/security/shared-guards-refuse-to-mutate-or-shadow-a-system-role.md +34 -0
- package/knowledge/decisions/security/signed-uploads-are-verified-by-the-same-check-as-signed-reads.md +26 -0
- package/knowledge/decisions/security/system-role-scopes-must-be-declared-with-define-scope.md +24 -0
- package/knowledge/decisions/security/system-roles-are-additive-and-immutable.md +16 -0
- package/knowledge/decisions/security/the-channel-rpc-does-not-send-the-callers-session.md +21 -0
- package/knowledge/decisions/security/the-remote-jobs-inbox-fails-closed-without-a-secret.md +24 -0
- package/knowledge/decisions/security/the-remote-rpc-gate-fails-closed-and-binds-the-token-to-its-function.md +34 -0
- package/knowledge/decisions/security/the-workflow-status-stream-rechecks-ownership-on-every-poll.md +16 -0
- package/knowledge/decisions/security/two-credentials-on-one-key-must-share-a-read-mode.md +29 -0
- package/knowledge/decisions/security/verified-bearer-claims-are-pass-through-data-that-core-never-fills.md +23 -0
- package/knowledge/decisions/security/virtual-user-catalogue-excludes-the-machinery-that-runs-virtual-users.md +25 -0
- package/knowledge/decisions/security/{actor-flow-missing-approval-decisions-default-to-denied.md → virtual-user-conversation-missing-approval-decisions-default-to-denied.md} +7 -7
- package/knowledge/decisions/security/virtual-user-run-records-are-gated-behind-a-read-scope.md +18 -0
- package/knowledge/decisions/security/virtual-user-sign-in-does-not-exist-in-production.md +25 -0
- package/knowledge/decisions/security/virtual-user-sign-in-has-one-door.md +42 -0
- package/knowledge/decisions/security/{actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md → virtual-user-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md} +10 -8
- package/knowledge/decisions/security/virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md +29 -0
- package/knowledge/decisions/security/workflow-helper-functions-are-sessionless-by-declaration.md +18 -0
- package/knowledge/decisions/security/{workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md → workflow-scenario-sessions-are-isolated-per-virtual-user-and-per-scenario.md} +6 -6
- package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +5 -5
- package/knowledge/decisions/security/{workflow-actor-steps-always-use-the-real-transport.md → workflow-virtual-user-steps-always-use-the-real-transport.md} +11 -11
- package/knowledge/decisions/security/writing-a-virtual-user-schedule-is-gated-by-its-own-scope.md +18 -0
- package/knowledge/index.md +0 -2
- package/knowledge/questions/a-bearer-jwt-with-no-identity-becomes-a-session.md +24 -0
- package/knowledge/questions/dev-quick-login-as-impersonation.md +25 -0
- package/knowledge/questions/does-a-delayed-rpc-run-as-the-session-it-was-scheduled-with.md +26 -0
- package/knowledge/questions/does-a-user-id-with-a-colon-read-another-users-credentials.md +27 -0
- package/knowledge/questions/does-a-wirecli-command-need-a-session-by-default.md +26 -0
- package/knowledge/questions/does-an-adversarial-in-scope-success-count-as-a-finding.md +26 -0
- package/knowledge/questions/does-approve-step-need-the-run-owners-session-or-the-gates-approvers.md +34 -0
- package/knowledge/questions/does-cors-origin-true-reflect-any-origin-with-credentials.md +20 -0
- package/knowledge/questions/does-skip-user-session-do-anything-on-a-channel.md +24 -0
- package/knowledge/questions/does-the-store-session-match-the-other-two.md +19 -0
- package/knowledge/questions/index.md +25 -2
- package/knowledge/questions/is-the-agent-score-worker-reachable-without-a-session.md +21 -0
- package/knowledge/questions/live-scheduled-tests-may-write.md +19 -0
- package/knowledge/questions/provision-virtual-users-has-no-caller-in-this-repo.md +23 -0
- package/knowledge/questions/remote-secret-refuses-requests-without-a-remote-token-on-every-path.md +32 -0
- package/knowledge/questions/should-a-middleware-set-session-persist-to-the-store.md +25 -0
- package/knowledge/questions/should-a-scoped-secret-view-throw-from-has-secret-for-a-disallowed-key.md +23 -0
- package/knowledge/questions/should-a-wrong-bearer-token-be-anonymous-or-refused.md +35 -0
- package/knowledge/questions/should-an-empty-webhook-signing-secret-send-unsigned.md +23 -0
- package/knowledge/questions/should-http-virtual-user-sessions-reset-between-scenarios-in-one-run.md +27 -0
- package/knowledge/questions/should-the-cookie-jar-scope-cookies-to-the-api-origin.md +26 -0
- package/knowledge/questions/should-voice-input-fetch-audio-urls-without-a-host-allowlist.md +20 -0
- package/knowledge/questions/staging-impersonation-rules.md +27 -0
- package/knowledge/questions/virtual-user-keys-on-cloud-stages-are-pikku-studios-to-build.md +33 -0
- package/knowledge/questions/virtual-user-statement-replay-is-per-process.md +20 -0
- package/knowledge/questions/what-an-impersonated-session-carries.md +22 -0
- package/package.json +2 -3
- package/src/public-surface.json +38 -47
- package/dist/services/persona-actor-secret.d.ts +0 -38
- package/dist/services/persona-actor-secret.js +0 -39
- package/dist/services/persona-sign-in.d.ts +0 -117
- package/dist/services/persona-sign-in.js +0 -154
- package/dist/wirings/actor-flow/index.d.ts +0 -2
- package/dist/wirings/actor-flow/run-conversation.d.ts +0 -36
- package/dist/wirings/persona/define-personas.d.ts +0 -32
- package/dist/wirings/persona/index.d.ts +0 -23
- package/dist/wirings/persona/index.js +0 -19
- package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +0 -23
- package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +0 -25
- package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +0 -43
- package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +0 -31
- package/knowledge/decisions/security/an-actor-credential-is-derived-per-persona.md +0 -42
- package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +0 -26
- /package/dist/wirings/{actor-flow/actor-flow.types.js → virtual-user/conversation/conversation.types.js} +0 -0
- /package/dist/wirings/{actor-flow → virtual-user/conversation}/index.js +0 -0
- /package/dist/wirings/{persona/persona.types.js → virtual-user/virtual-user-declaration.types.js} +0 -0
- /package/dist/wirings/{persona/persona-mailbox.js → virtual-user/virtual-user-mailbox.js} +0 -0
|
@@ -27,10 +27,10 @@ export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
|
27
27
|
// Which of a step's bindings run: one for an action, every witness for a `then`
|
|
28
28
|
export { resolveScenarioSurfaces } from './scenario-surface.js';
|
|
29
29
|
export { pollUntil } from './scenario-poll.js';
|
|
30
|
-
export { createCookieJar } from '
|
|
30
|
+
export { createCookieJar } from '../../services/scenario-cookie-jar.js';
|
|
31
31
|
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
32
32
|
export { requireScenarioEnv } from './scenario-step-guards.js';
|
|
33
|
-
// The readers themselves live on `@pikku/core/
|
|
33
|
+
// The readers themselves live on `@pikku/core/virtual-user`
|
|
34
34
|
/**
|
|
35
35
|
* A workflow service with the scenario capability attached — the two lines
|
|
36
36
|
* `pikku scenario run` needs, in one call so no caller has to remember that the
|
|
@@ -68,7 +68,7 @@ addError(ScenarioHookError, {
|
|
|
68
68
|
* Two witnesses of the same `then` disagreed.
|
|
69
69
|
*
|
|
70
70
|
* This is the failure the whole additive-witness design exists to produce: the
|
|
71
|
-
* system of record is right and the surface the
|
|
71
|
+
* system of record is right and the surface the virtual user was looking at is not, or
|
|
72
72
|
* the other way round. Both observations go in the message, because either one
|
|
73
73
|
* alone reads as a passing assertion.
|
|
74
74
|
*/
|
|
@@ -105,24 +105,24 @@ addError(ScenarioBrowserUnavailable, {
|
|
|
105
105
|
message: 'No browser provider is registered.',
|
|
106
106
|
});
|
|
107
107
|
/**
|
|
108
|
-
* A step that is driven by a
|
|
108
|
+
* A step that is driven by a virtual user was dispatched without one.
|
|
109
109
|
*
|
|
110
110
|
* Raised before the body runs rather than partway through it: the step declared
|
|
111
|
-
* that it needs
|
|
112
|
-
* provisioned without a window to open, or by `
|
|
111
|
+
* that it needs a virtual user — by having a `browser` binding, which cannot be
|
|
112
|
+
* provisioned without a window to open, or by `virtualUser: true` — so a call site
|
|
113
113
|
* that omits one is a mistake at the call site.
|
|
114
114
|
*/
|
|
115
|
-
export class
|
|
115
|
+
export class ScenarioVirtualUserRequired extends PikkuError {
|
|
116
116
|
stepFunc;
|
|
117
117
|
constructor(stepFunc) {
|
|
118
|
-
super(`[scenario] step '${stepFunc}' is driven by a
|
|
119
|
-
`Pass {
|
|
118
|
+
super(`[scenario] step '${stepFunc}' is driven by a virtual user but was called without a virtual user. ` +
|
|
119
|
+
`Pass { virtualUser: virtualUsers.<name> } so it runs as that virtual user.`);
|
|
120
120
|
this.stepFunc = stepFunc;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
addError(
|
|
123
|
+
addError(ScenarioVirtualUserRequired, {
|
|
124
124
|
status: 500,
|
|
125
|
-
message: 'This step needs
|
|
125
|
+
message: 'This step needs a virtual user.',
|
|
126
126
|
});
|
|
127
127
|
/**
|
|
128
128
|
* A step declares no binding the run can execute.
|
|
@@ -186,7 +186,7 @@ addError(ScenarioNoWitness, {
|
|
|
186
186
|
* An assertion ran, but not on the surface the run targets.
|
|
187
187
|
*
|
|
188
188
|
* The sibling of {@link ScenarioNoWitness}: that one checked nothing anywhere,
|
|
189
|
-
* this one checked the system of record while the prose claims the
|
|
189
|
+
* this one checked the system of record while the prose claims the virtual user saw it
|
|
190
190
|
* on the page. Ordinary runs count it as a coverage gap; a strict run refuses
|
|
191
191
|
* it, because a flow that cannot be observed end to end on the run surface is
|
|
192
192
|
* not a flow that surface can be documented from.
|
|
@@ -198,7 +198,7 @@ export class ScenarioUnwitnessedAssertion extends PikkuError {
|
|
|
198
198
|
witnessedOn;
|
|
199
199
|
constructor(stepFunc, declared, runSurface, witnessedOn) {
|
|
200
200
|
super(`[scenario] assertion '${stepFunc}' was checked on ${witnessedOn.join(', ')}, not on '${runSurface}'. ` +
|
|
201
|
-
`It held there — but nothing looked at '${runSurface}', which is what the step's prose claims the
|
|
201
|
+
`It held there — but nothing looked at '${runSurface}', which is what the step's prose claims the virtual user saw. ` +
|
|
202
202
|
`Add a '${runSurface}' witness (declares: ${declared.join(', ') || 'nothing'}), or drop --strict.`);
|
|
203
203
|
this.stepFunc = stepFunc;
|
|
204
204
|
this.declared = declared;
|
|
@@ -214,7 +214,7 @@ addError(ScenarioUnwitnessedAssertion, {
|
|
|
214
214
|
* The scenario capability, layered onto a workflow service rather than being
|
|
215
215
|
* one.
|
|
216
216
|
*
|
|
217
|
-
* Every scenario affordance — steps,
|
|
217
|
+
* Every scenario affordance — steps, virtual users, lifecycle hooks, the browser
|
|
218
218
|
* provider, the assertion wire members — lives here rather than on
|
|
219
219
|
* `PikkuWorkflowService`, because a bundler drops an unused *module* but never
|
|
220
220
|
* an unused class member: anything declared on the workflow service ships in
|
|
@@ -236,14 +236,14 @@ addError(ScenarioUnwitnessedAssertion, {
|
|
|
236
236
|
*/
|
|
237
237
|
export class PikkuScenarioService {
|
|
238
238
|
engine;
|
|
239
|
-
// Scenario
|
|
239
|
+
// Scenario virtual users per run: live authenticated clients (cookie jars) are
|
|
240
240
|
// process-local by nature, so they ride this map, never the persisted wire.
|
|
241
|
-
|
|
242
|
-
// What each run has accumulated so far — process-local like the
|
|
241
|
+
runVirtualUsers = new Map();
|
|
242
|
+
// What each run has accumulated so far — process-local like the virtual users above,
|
|
243
243
|
// so the body and its hooks share one object rather than reading it back off
|
|
244
244
|
// the persisted wire.
|
|
245
245
|
runContexts = new Map();
|
|
246
|
-
// Where each browser step landed in its
|
|
246
|
+
// Where each browser step landed in its virtual user's recording, per run. Held
|
|
247
247
|
// apart from the run context because the runner reads it once the run has
|
|
248
248
|
// ended, which is exactly when `detachRunContext` has cleared that.
|
|
249
249
|
runVideoOffsets = new Map();
|
|
@@ -255,7 +255,7 @@ export class PikkuScenarioService {
|
|
|
255
255
|
this.engine = engine;
|
|
256
256
|
}
|
|
257
257
|
/**
|
|
258
|
-
* The surface every
|
|
258
|
+
* The surface every virtual user drives the system through for this run, set once by
|
|
259
259
|
* the runner from `--run`. `default` is the server-side path — the fast suite.
|
|
260
260
|
*
|
|
261
261
|
* `strict` removes both routes off that surface: an action may not fall back
|
|
@@ -297,7 +297,7 @@ export class PikkuScenarioService {
|
|
|
297
297
|
return this.scenarioEnvironment;
|
|
298
298
|
}
|
|
299
299
|
/**
|
|
300
|
-
* Where each of a run's browser steps fell in its
|
|
300
|
+
* Where each of a run's browser steps fell in its virtual user's video, keyed by the
|
|
301
301
|
* durable step name, handed over and forgotten in one call.
|
|
302
302
|
*
|
|
303
303
|
* Taken rather than read because the runner joins these onto the step rows
|
|
@@ -309,51 +309,51 @@ export class PikkuScenarioService {
|
|
|
309
309
|
this.runVideoOffsets.delete(runId);
|
|
310
310
|
return offsets ?? new Map();
|
|
311
311
|
}
|
|
312
|
-
/** Where a step starting now falls in this
|
|
313
|
-
videoOffsetFor(
|
|
312
|
+
/** Where a step starting now falls in this virtual user's recording, if anywhere. */
|
|
313
|
+
videoOffsetFor(virtualUser) {
|
|
314
314
|
const provider = this.scenarioBrowserProvider;
|
|
315
315
|
if (provider?.markVideoStep) {
|
|
316
|
-
return provider.markVideoStep(
|
|
316
|
+
return provider.markVideoStep(virtualUser);
|
|
317
317
|
}
|
|
318
|
-
const startedAt = provider?.videoStartedAt?.(
|
|
318
|
+
const startedAt = provider?.videoStartedAt?.(virtualUser);
|
|
319
319
|
return startedAt === undefined
|
|
320
320
|
? undefined
|
|
321
321
|
: Math.max(0, Date.now() - startedAt);
|
|
322
322
|
}
|
|
323
323
|
/**
|
|
324
|
-
* Stamp where a step began inside one
|
|
324
|
+
* Stamp where a step began inside one virtual user's recording.
|
|
325
325
|
*
|
|
326
|
-
* First write per
|
|
326
|
+
* First write per virtual user wins: a `then` step runs once per witness, and the
|
|
327
327
|
* moment the reader wants is when the sentence started, not when its last
|
|
328
328
|
* witness got around to the browser.
|
|
329
329
|
*/
|
|
330
|
-
recordVideoOffset(runId, stepName,
|
|
330
|
+
recordVideoOffset(runId, stepName, virtualUser, offsetMs) {
|
|
331
331
|
let byStep = this.runVideoOffsets.get(runId);
|
|
332
332
|
if (!byStep) {
|
|
333
333
|
byStep = new Map();
|
|
334
334
|
this.runVideoOffsets.set(runId, byStep);
|
|
335
335
|
}
|
|
336
336
|
const offsets = byStep.get(stepName) ?? [];
|
|
337
|
-
if (offsets.some((offset) => offset.
|
|
337
|
+
if (offsets.some((offset) => offset.virtualUser === virtualUser)) {
|
|
338
338
|
return;
|
|
339
339
|
}
|
|
340
|
-
offsets.push({
|
|
340
|
+
offsets.push({ virtualUser, offsetMs });
|
|
341
341
|
byStep.set(stepName, offsets);
|
|
342
342
|
}
|
|
343
343
|
async attachRunContext(runId, workflowMeta, options) {
|
|
344
|
-
const
|
|
344
|
+
const virtualUsers = options?.virtualUsers ??
|
|
345
345
|
(workflowMeta.source === 'scenario'
|
|
346
|
-
? await this.
|
|
346
|
+
? await this.resolveVirtualUsers()
|
|
347
347
|
: undefined);
|
|
348
|
-
if (
|
|
349
|
-
this.
|
|
348
|
+
if (virtualUsers) {
|
|
349
|
+
this.runVirtualUsers.set(runId, virtualUsers);
|
|
350
350
|
}
|
|
351
351
|
if (workflowMeta.source === 'scenario') {
|
|
352
352
|
this.runContexts.set(runId, {});
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
detachRunContext(runId) {
|
|
356
|
-
this.
|
|
356
|
+
this.runVirtualUsers.delete(runId);
|
|
357
357
|
this.runContexts.delete(runId);
|
|
358
358
|
}
|
|
359
359
|
/**
|
|
@@ -383,7 +383,7 @@ export class PikkuScenarioService {
|
|
|
383
383
|
context.workflowMeta?.source === 'scenario'
|
|
384
384
|
? context.workflowWire
|
|
385
385
|
: undefined;
|
|
386
|
-
wire.
|
|
386
|
+
wire.virtualUsers = this.runVirtualUsers.get(context.runId);
|
|
387
387
|
}
|
|
388
388
|
async onBeforeRunFunc(context) {
|
|
389
389
|
const hooks = this.scenarioHooks(context);
|
|
@@ -436,7 +436,7 @@ export class PikkuScenarioService {
|
|
|
436
436
|
*
|
|
437
437
|
* A hook is not a pikku function: it has no id, no meta and no schema, so it
|
|
438
438
|
* cannot go through `runPikkuFunc` and the runner records nothing for it. It
|
|
439
|
-
* gets exactly what the scenario body gets — the same wire (so `
|
|
439
|
+
* gets exactly what the scenario body gets — the same wire (so `virtualUsers` is
|
|
440
440
|
* how it reaches the app), and singleton services composed with this
|
|
441
441
|
* invocation's wire services — and nothing else.
|
|
442
442
|
*/
|
|
@@ -467,44 +467,42 @@ export class PikkuScenarioService {
|
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
469
|
/**
|
|
470
|
-
* Build HTTP scenario
|
|
471
|
-
*
|
|
470
|
+
* Build HTTP scenario virtual users for a run started without them; undefined when
|
|
471
|
+
* the app has no auth service or the API URL is missing.
|
|
472
472
|
*
|
|
473
|
-
* The
|
|
474
|
-
* for the AI
|
|
475
|
-
* signs
|
|
473
|
+
* The virtual user client is imported lazily so that even a runner bundle only pays
|
|
474
|
+
* for the AI virtual user conversation loop it pulls in when a scenario actually
|
|
475
|
+
* signs a virtual user in.
|
|
476
476
|
*/
|
|
477
|
-
async
|
|
477
|
+
async resolveVirtualUsers() {
|
|
478
478
|
const services = getSingletonServices();
|
|
479
479
|
const variables = services?.variables;
|
|
480
480
|
const metaService = services?.metaService;
|
|
481
481
|
if (!variables || !metaService) {
|
|
482
482
|
return undefined;
|
|
483
483
|
}
|
|
484
|
-
const
|
|
484
|
+
const auth = await services?.auth?.();
|
|
485
485
|
const apiUrl = await variables.get('API_URL');
|
|
486
|
-
if (!
|
|
487
|
-
services?.logger?.warn('A scenario was started without
|
|
486
|
+
if (!auth || !apiUrl) {
|
|
487
|
+
services?.logger?.warn('A scenario was started without virtual users but the auth service / API_URL is not configured — running without them.');
|
|
488
488
|
return undefined;
|
|
489
489
|
}
|
|
490
|
-
const
|
|
491
|
-
if (!
|
|
490
|
+
const virtualUsers = await metaService.getVirtualUsersMeta();
|
|
491
|
+
if (!virtualUsers || Object.keys(virtualUsers).length === 0) {
|
|
492
492
|
return undefined;
|
|
493
493
|
}
|
|
494
|
-
const signInPath = (await variables.get('SCENARIO_SIGN_IN_PATH')) ??
|
|
495
|
-
'/api/auth/sign-in/actor';
|
|
496
494
|
const rpcPath = (await variables.get('SCENARIO_RPC_PATH')) ?? '/rpc';
|
|
497
495
|
// A run started outside the CLI still targets an environment — its own.
|
|
498
496
|
this.scenarioEnvironment ??= {
|
|
499
497
|
apiUrl,
|
|
500
498
|
appUrl: (await variables.get('APP_URL')) ?? undefined,
|
|
501
499
|
};
|
|
502
|
-
const {
|
|
503
|
-
|
|
500
|
+
const { createHttpVirtualUsers } = await import('../../services/http-virtual-users.js');
|
|
501
|
+
const { inProcessSignIn } = await import('../../services/virtual-user-sign-in.js');
|
|
502
|
+
return createHttpVirtualUsers({
|
|
504
503
|
apiUrl,
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
signInPath,
|
|
504
|
+
signIn: inProcessSignIn(auth),
|
|
505
|
+
virtualUsers,
|
|
508
506
|
rpcPath,
|
|
509
507
|
});
|
|
510
508
|
}
|
|
@@ -520,7 +518,7 @@ export class PikkuScenarioService {
|
|
|
520
518
|
});
|
|
521
519
|
Object.assign(workflowWire, {
|
|
522
520
|
context: this.contextForRun(runId),
|
|
523
|
-
// Durable polling step: invoke an RPC (as
|
|
521
|
+
// Durable polling step: invoke an RPC (as a virtual user when options.as is
|
|
524
522
|
// set) until the predicate passes or `within` elapses. The whole poll is
|
|
525
523
|
// ONE recorded step, so replay returns the cached outcome.
|
|
526
524
|
expectEventually: async (stepName, rpcName, data, predicate, options) => {
|
|
@@ -534,14 +532,14 @@ export class PikkuScenarioService {
|
|
|
534
532
|
const deadline = Date.now() + within;
|
|
535
533
|
let last;
|
|
536
534
|
while (true) {
|
|
537
|
-
last = options?.
|
|
538
|
-
? await options.
|
|
535
|
+
last = options?.virtualUser
|
|
536
|
+
? await options.virtualUser.invoke(resolvedRpcName, data)
|
|
539
537
|
: await rpcService.rpcWithWire(resolvedRpcName, data, {});
|
|
540
538
|
if (predicate(last))
|
|
541
539
|
return last;
|
|
542
540
|
if (Date.now() + interval > deadline) {
|
|
543
541
|
throw new Error(`[workflow] expectEventually '${stepName}' ('${resolvedRpcName}'` +
|
|
544
|
-
`${options?.
|
|
542
|
+
`${options?.virtualUser ? ` as '${options.virtualUser.name}'` : ''}) did not pass within ${within}ms; ` +
|
|
545
543
|
`last result: ${JSON.stringify(last)?.slice(0, 300)}`);
|
|
546
544
|
}
|
|
547
545
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
@@ -556,8 +554,8 @@ export class PikkuScenarioService {
|
|
|
556
554
|
return await this.engine.inlineStep(runId, stepName, async () => {
|
|
557
555
|
let result;
|
|
558
556
|
try {
|
|
559
|
-
result = options?.
|
|
560
|
-
? await options.
|
|
557
|
+
result = options?.virtualUser
|
|
558
|
+
? await options.virtualUser.invoke(resolvedRpcName, data)
|
|
561
559
|
: await rpcService.rpcWithWire(resolvedRpcName, data, {});
|
|
562
560
|
}
|
|
563
561
|
catch (e) {
|
|
@@ -583,8 +581,8 @@ export class PikkuScenarioService {
|
|
|
583
581
|
}
|
|
584
582
|
await this.engine.inlineStep(runId, stepName, async () => {
|
|
585
583
|
const rpcName = 'pikkuScenarioGetStubCalls';
|
|
586
|
-
const calls = options?.
|
|
587
|
-
? await options.
|
|
584
|
+
const calls = options?.virtualUser
|
|
585
|
+
? await options.virtualUser.invoke(rpcName, { service })
|
|
588
586
|
: await rpcService.rpcWithWire(rpcName, { service }, {});
|
|
589
587
|
const matching = (calls ?? []).filter((c) => c.service === service &&
|
|
590
588
|
c.method === method &&
|
|
@@ -615,8 +613,8 @@ export class PikkuScenarioService {
|
|
|
615
613
|
? { reference: options.reference }
|
|
616
614
|
: {}),
|
|
617
615
|
};
|
|
618
|
-
const grade = options?.
|
|
619
|
-
? await options.
|
|
616
|
+
const grade = options?.virtualUser
|
|
617
|
+
? await options.virtualUser.invoke(rpcName, data)
|
|
620
618
|
: await rpcService.rpcWithWire(rpcName, data, {});
|
|
621
619
|
// A scorer that graded is a scorer that answered, so an unstated
|
|
622
620
|
// bound still fails a zero rather than passing anything at all.
|
|
@@ -655,12 +653,13 @@ export class PikkuScenarioService {
|
|
|
655
653
|
const resolvedStepFunc = addonNamespace && !stepFunc.includes(':')
|
|
656
654
|
? `${addonNamespace}:${stepFunc}`
|
|
657
655
|
: stepFunc;
|
|
658
|
-
const
|
|
656
|
+
const virtualUser = options?.virtualUser;
|
|
659
657
|
const description = options?.description ??
|
|
660
658
|
this.scenarioStepDescription(packageName, resolvedStepFunc) ??
|
|
661
659
|
stepName;
|
|
662
|
-
if (!
|
|
663
|
-
|
|
660
|
+
if (!virtualUser &&
|
|
661
|
+
this.requiresVirtualUser(packageName, resolvedStepFunc)) {
|
|
662
|
+
throw new ScenarioVirtualUserRequired(resolvedStepFunc);
|
|
664
663
|
}
|
|
665
664
|
const declared = this.declaredSurfaces(packageName, resolvedStepFunc);
|
|
666
665
|
const resolution = resolveScenarioSurfaces(phase, declared, this.runSurface);
|
|
@@ -671,8 +670,8 @@ export class PikkuScenarioService {
|
|
|
671
670
|
workflow: workflowWire,
|
|
672
671
|
scenario: workflowWire,
|
|
673
672
|
pikkuUserId: workflowWire.pikkuUserId,
|
|
674
|
-
|
|
675
|
-
...(
|
|
673
|
+
virtualUsers: this.runVirtualUsers.get(runId),
|
|
674
|
+
...(virtualUser ? { virtualUser } : {}),
|
|
676
675
|
scenarioStep: {
|
|
677
676
|
name: resolvedStepFunc,
|
|
678
677
|
stepName,
|
|
@@ -686,12 +685,12 @@ export class PikkuScenarioService {
|
|
|
686
685
|
if (!this.scenarioBrowserProvider) {
|
|
687
686
|
throw new ScenarioBrowserUnavailable(resolvedStepFunc);
|
|
688
687
|
}
|
|
689
|
-
// The dispatch guard above already refused an
|
|
688
|
+
// The dispatch guard above already refused an anonymous call, and a
|
|
690
689
|
// browser binding always requires one.
|
|
691
|
-
wire.browser = await this.scenarioBrowserProvider.sessionFor(
|
|
692
|
-
const offsetMs = this.videoOffsetFor(
|
|
690
|
+
wire.browser = await this.scenarioBrowserProvider.sessionFor(virtualUser.name);
|
|
691
|
+
const offsetMs = this.videoOffsetFor(virtualUser.name);
|
|
693
692
|
if (offsetMs !== undefined) {
|
|
694
|
-
this.recordVideoOffset(runId, stepName,
|
|
693
|
+
this.recordVideoOffset(runId, stepName, virtualUser.name, offsetMs);
|
|
695
694
|
}
|
|
696
695
|
}
|
|
697
696
|
const result = await runPikkuFunc('workflow', workflowName, resolvedStepFunc, {
|
|
@@ -765,10 +764,11 @@ export class PikkuScenarioService {
|
|
|
765
764
|
return surfaces?.length ? surfaces : ['default'];
|
|
766
765
|
}
|
|
767
766
|
/**
|
|
768
|
-
* Whether a step is driven by a
|
|
769
|
-
* the definer from a `browser` binding or an explicit `
|
|
767
|
+
* Whether a step is driven by a virtual user, and so must be given one. Stamped by
|
|
768
|
+
* the definer from a `browser` binding or an explicit `virtualUser: true`.
|
|
770
769
|
*/
|
|
771
|
-
|
|
772
|
-
return (this.scenarioStepConfig(packageName, stepFunc)?.
|
|
770
|
+
requiresVirtualUser(packageName, stepFunc) {
|
|
771
|
+
return (this.scenarioStepConfig(packageName, stepFunc)?.requiresVirtualUser ===
|
|
772
|
+
true);
|
|
773
773
|
}
|
|
774
774
|
}
|
|
@@ -3,7 +3,7 @@ import type { SerializedError } from '../../errors/serialized-error.js';
|
|
|
3
3
|
import type { GroupConcurrencyConfig, JobGroup, JobOptions } from '../queue/queue.types.js';
|
|
4
4
|
import type { PikkuWorkflowWire, StepState, StepStatus, WorkflowPlannedStep, WorkflowRun, WorkflowRunMirror, WorkflowRunStatus, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, WorkflowQueueOptions, WorkflowStepOptions } from './workflow.types.js';
|
|
5
5
|
import type { WorkflowService } from '../../services/workflow-service.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ScenarioVirtualUsers } from '../../services/virtual-users-service.js';
|
|
7
7
|
import type { PikkuRPC } from '../rpc/rpc-types.js';
|
|
8
8
|
import { type RunTimeline, type ReconstructedRunState } from './run-timeline.js';
|
|
9
9
|
import type { WorkflowRunEngine, WorkflowRunExtension } from './workflow-run-engine.types.js';
|
|
@@ -194,7 +194,7 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
194
194
|
startWorkflow<I>(name: string, input: I, wire: WorkflowRunWire, rpcService: PikkuRPC, options?: {
|
|
195
195
|
inline?: boolean;
|
|
196
196
|
startNode?: string;
|
|
197
|
-
|
|
197
|
+
virtualUsers?: ScenarioVirtualUsers;
|
|
198
198
|
onRunCreated?: (runId: string) => void;
|
|
199
199
|
}): Promise<{
|
|
200
200
|
runId: string;
|
|
@@ -818,7 +818,7 @@ export class PikkuWorkflowService {
|
|
|
818
818
|
const resolvedStepOptions = {
|
|
819
819
|
retries: stepOptions?.retries ?? DEFAULT_STEP_RETRIES,
|
|
820
820
|
retryDelay: stepOptions?.retryDelay,
|
|
821
|
-
|
|
821
|
+
virtualUser: stepOptions?.virtualUser,
|
|
822
822
|
};
|
|
823
823
|
const stepState = await this.loadOrCreateStep(runId, stepName, async () => {
|
|
824
824
|
if (stepOptions?.compensate === false) {
|
|
@@ -844,7 +844,7 @@ export class PikkuWorkflowService {
|
|
|
844
844
|
if (lease === 'held') {
|
|
845
845
|
throw new WorkflowAsyncException(runId, stepName);
|
|
846
846
|
}
|
|
847
|
-
const dispatched = resolvedStepOptions.
|
|
847
|
+
const dispatched = resolvedStepOptions.virtualUser
|
|
848
848
|
? false
|
|
849
849
|
: await this.dispatchStep(runId, stepName, rpcName, data, resolvedStepOptions, fromStepName);
|
|
850
850
|
if (dispatched) {
|
|
@@ -856,8 +856,8 @@ export class PikkuWorkflowService {
|
|
|
856
856
|
const retries = resolvedStepOptions.retries ?? this.getConfig().retries;
|
|
857
857
|
const retryDelay = resolvedStepOptions.retryDelay;
|
|
858
858
|
return runInlineRetryLoop(this, stepState, retries, retryDelay, async (currentStepState) => {
|
|
859
|
-
if (resolvedStepOptions.
|
|
860
|
-
return resolvedStepOptions.
|
|
859
|
+
if (resolvedStepOptions.virtualUser) {
|
|
860
|
+
return resolvedStepOptions.virtualUser.invoke(rpcName, data);
|
|
861
861
|
}
|
|
862
862
|
if (pikkuState(null, 'workflows', 'meta')[rpcName]) {
|
|
863
863
|
return runInlineChildStep({
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import type { ScenarioStepPhase } from './scenario-step.types.js';
|
|
2
2
|
export declare const renderStepTemplate: (template: string, input: unknown) => string;
|
|
3
|
-
export declare const composeStepProse: ({ phase, description, template, input,
|
|
3
|
+
export declare const composeStepProse: ({ phase, description, template, input, virtualUser, virtualUserRole, continuesPhase, continuesVirtualUser, keywordWidth, }: {
|
|
4
4
|
phase: ScenarioStepPhase;
|
|
5
5
|
description: string;
|
|
6
6
|
template?: string;
|
|
7
7
|
input?: unknown;
|
|
8
|
-
|
|
8
|
+
virtualUser?: string;
|
|
9
9
|
/**
|
|
10
|
-
* What this
|
|
11
|
-
* (the founder)". Only pass it where the
|
|
12
|
-
* ordinary run repeats one
|
|
10
|
+
* What this virtual user is, rendered as an apposition after their key — "yasser
|
|
11
|
+
* (the founder)". Only pass it where the virtual user has not been named yet: an
|
|
12
|
+
* ordinary run repeats one virtual user for a dozen steps, and repeating the role
|
|
13
13
|
* with them turns the one piece of context into the noise around it.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
virtualUserRole?: string;
|
|
16
16
|
/**
|
|
17
17
|
* This step repeats the phase of the one before it, so it reads as `And`
|
|
18
18
|
* rather than saying `Given` three times — the same thing Gherkin does.
|
|
19
19
|
*/
|
|
20
20
|
continuesPhase?: boolean;
|
|
21
21
|
/**
|
|
22
|
-
* The step before this one had the same
|
|
22
|
+
* The step before this one had the same virtual user. Combined with `continuesPhase`
|
|
23
23
|
* the subject is dropped, because English drops a repeated subject in a
|
|
24
24
|
* compound predicate: "yasser opens the dashboard / and sees the audit log".
|
|
25
25
|
*
|
|
@@ -27,6 +27,6 @@ export declare const composeStepProse: ({ phase, description, template, input, a
|
|
|
27
27
|
* the dashboard", and a pronoun instead of a name would give "they sees",
|
|
28
28
|
* since step templates are authored in the third person singular.
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
continuesVirtualUser?: boolean;
|
|
31
31
|
keywordWidth?: number;
|
|
32
32
|
}) => string;
|
|
@@ -14,13 +14,15 @@ const formatValue = (value) => {
|
|
|
14
14
|
}
|
|
15
15
|
return String(value);
|
|
16
16
|
};
|
|
17
|
-
export const composeStepProse = ({ phase, description, template, input,
|
|
17
|
+
export const composeStepProse = ({ phase, description, template, input, virtualUser, virtualUserRole, continuesPhase, continuesVirtualUser, keywordWidth, }) => {
|
|
18
18
|
const keyword = capitalise(continuesPhase ? 'and' : phase);
|
|
19
|
-
// The
|
|
20
|
-
// "the ${
|
|
21
|
-
// noun — it turns a
|
|
19
|
+
// The virtual user key is the subject verbatim, with no article in front of it.
|
|
20
|
+
// "the ${virtual user}" only reads as English when the key happens to be a role
|
|
21
|
+
// noun — it turns a virtual user named after a person into "the nadia", which
|
|
22
22
|
// is the reporter quietly imposing a naming convention on the author.
|
|
23
|
-
const subject = continuesPhase &&
|
|
23
|
+
const subject = continuesPhase && continuesVirtualUser
|
|
24
|
+
? ''
|
|
25
|
+
: composeSubject(virtualUser, virtualUserRole);
|
|
24
26
|
const rendered = template ? renderStepTemplate(template, input) : description;
|
|
25
27
|
const sentence = [subject, rendered].filter(Boolean).join(' ');
|
|
26
28
|
if (keywordWidth === undefined) {
|
|
@@ -28,9 +30,11 @@ export const composeStepProse = ({ phase, description, template, input, actor, a
|
|
|
28
30
|
}
|
|
29
31
|
return `${keyword.padEnd(keywordWidth)} ${sentence}`;
|
|
30
32
|
};
|
|
31
|
-
const composeSubject = (
|
|
32
|
-
if (!
|
|
33
|
+
const composeSubject = (virtualUser, virtualUserRole) => {
|
|
34
|
+
if (!virtualUser)
|
|
33
35
|
return '';
|
|
34
|
-
return
|
|
36
|
+
return virtualUserRole
|
|
37
|
+
? `${virtualUser} (the ${virtualUserRole})`
|
|
38
|
+
: virtualUser;
|
|
35
39
|
};
|
|
36
40
|
const capitalise = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
@@ -27,8 +27,8 @@ export interface ScenarioArtifact {
|
|
|
27
27
|
scenario: string;
|
|
28
28
|
kind: ScenarioArtifactKind;
|
|
29
29
|
path: string;
|
|
30
|
-
/** Whose window it was — a two-
|
|
31
|
-
|
|
30
|
+
/** Whose window it was — a two-virtual-user scenario produces two of everything. */
|
|
31
|
+
virtualUser?: string;
|
|
32
32
|
/** The caption the scenario author took a screenshot under. */
|
|
33
33
|
name?: string;
|
|
34
34
|
/**
|
|
@@ -42,9 +42,9 @@ export interface ScenarioArtifact {
|
|
|
42
42
|
showcase?: boolean;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Where a step falls inside one
|
|
45
|
+
* Where a step falls inside one virtual user's recording.
|
|
46
46
|
*
|
|
47
|
-
* Measured from the moment that
|
|
47
|
+
* Measured from the moment that virtual user's browser context opened — which is when
|
|
48
48
|
* Playwright starts the file — rather than from the start of the scenario, so
|
|
49
49
|
* it addresses the video's own clock. The two differ by however long the
|
|
50
50
|
* scenario spent before that window existed, plus every non-browser step since,
|
|
@@ -52,17 +52,17 @@ export interface ScenarioArtifact {
|
|
|
52
52
|
* being summed back out of the ladder afterwards.
|
|
53
53
|
*/
|
|
54
54
|
export interface ScenarioStepVideoOffset {
|
|
55
|
-
/** Whose recording this offset is into: one
|
|
56
|
-
|
|
55
|
+
/** Whose recording this offset is into: one virtual user, one video file. */
|
|
56
|
+
virtualUser: string;
|
|
57
57
|
offsetMs: number;
|
|
58
58
|
}
|
|
59
59
|
/** One step of a run, already joined to the prose that declared it. */
|
|
60
60
|
export interface ScenarioStepRow {
|
|
61
61
|
sentence: string;
|
|
62
62
|
/**
|
|
63
|
-
* The same sentence with the
|
|
64
|
-
* signs in". Set only on the step that first names each
|
|
65
|
-
* when a
|
|
63
|
+
* The same sentence with the virtual user's role in it — "yasser (the founder)
|
|
64
|
+
* signs in". Set only on the step that first names each virtual user, and only
|
|
65
|
+
* when a virtual user declares a job title or a role, so a reader who wants the
|
|
66
66
|
* context picks this and one who wants the bare run picks `sentence`.
|
|
67
67
|
*/
|
|
68
68
|
sentenceWithRole?: string;
|
|
@@ -70,11 +70,11 @@ export interface ScenarioStepRow {
|
|
|
70
70
|
durationMs?: number;
|
|
71
71
|
error?: string;
|
|
72
72
|
/**
|
|
73
|
-
* Where this step lands in each
|
|
74
|
-
* was being recorded when it ran. Absent for a step with no
|
|
73
|
+
* Where this step lands in each virtual user's video, for the virtual users whose window
|
|
74
|
+
* was being recorded when it ran. Absent for a step with no virtual user, a run
|
|
75
75
|
* without video, and a step that never ran at all.
|
|
76
76
|
*
|
|
77
|
-
* A list rather than one number because a video belongs to
|
|
77
|
+
* A list rather than one number because a video belongs to a virtual user, not to
|
|
78
78
|
* the scenario: a step touching two windows falls at a different moment in
|
|
79
79
|
* each, and an offset that does not name its file cannot be seeked to.
|
|
80
80
|
*/
|
|
@@ -126,7 +126,7 @@ export interface ScenarioResult {
|
|
|
126
126
|
title?: string;
|
|
127
127
|
description?: string;
|
|
128
128
|
/** Who the scenario cast, in declaration order. */
|
|
129
|
-
|
|
129
|
+
virtualUsers?: string[];
|
|
130
130
|
tags?: string[];
|
|
131
131
|
/** Images and footage this scenario produced, filed under the run. */
|
|
132
132
|
artifacts?: ScenarioArtifact[];
|