@pikku/core 0.12.141 → 0.12.142
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 +33 -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/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/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/role/define-system-role.d.ts +1 -1
- package/dist/wirings/role/define-system-role.js +1 -1
- 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-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/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 +46 -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 +34 -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
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { VirtualUserBudget, VirtualUserDisposition } from './virtual-user.types.js';
|
|
2
2
|
/**
|
|
3
|
-
* One
|
|
3
|
+
* One virtual user's standing instruction to keep using the app.
|
|
4
4
|
*
|
|
5
5
|
* A virtual user that runs once tells you about one afternoon. What an app
|
|
6
|
-
* actually wants to know is what a
|
|
6
|
+
* actually wants to know is what a virtual user hits over a fortnight, and that is
|
|
7
7
|
* a cadence, not a longer run — a budget already caps how far a single run
|
|
8
8
|
* goes, and raising it only buys a more tired user.
|
|
9
9
|
*
|
|
10
10
|
* The row is the schedule. There is deliberately no timer, interval or
|
|
11
11
|
* in-memory loop anywhere near it: a process that holds the next run in its own
|
|
12
|
-
* heap forgets it on the next deploy, and a
|
|
12
|
+
* heap forgets it on the next deploy, and a virtual user silently stops. Something
|
|
13
13
|
* outside asks which rows are due; the answer survives restarts because it is
|
|
14
14
|
* written down.
|
|
15
15
|
*/
|
|
16
16
|
export interface VirtualUserScheduleRecord {
|
|
17
|
-
|
|
17
|
+
virtualUserId: string;
|
|
18
18
|
/**
|
|
19
19
|
* Off by default. A schedule that ran the moment it was written would start
|
|
20
20
|
* spending an app's model budget as a side effect of a migration.
|
|
@@ -24,20 +24,20 @@ export interface VirtualUserScheduleRecord {
|
|
|
24
24
|
goals: string[];
|
|
25
25
|
budget: VirtualUserBudget | null;
|
|
26
26
|
/**
|
|
27
|
-
* The gap to the next run is drawn between these, not fixed. A
|
|
27
|
+
* The gap to the next run is drawn between these, not fixed. A virtual user that
|
|
28
28
|
* appears at exactly 09:00 every day exercises one cache state and one cron
|
|
29
29
|
* neighbourhood; a real one does not keep an appointment.
|
|
30
30
|
*/
|
|
31
31
|
minIntervalMs: number;
|
|
32
32
|
maxIntervalMs: number;
|
|
33
|
-
/** When this
|
|
33
|
+
/** When this virtual user is next allowed to run. The whole schedule, in one field. */
|
|
34
34
|
nextRunAt: Date;
|
|
35
35
|
lastRunId: string | null;
|
|
36
36
|
lastRunAt: Date | null;
|
|
37
37
|
}
|
|
38
38
|
/** A partial write — anything left out keeps whatever the row already had. */
|
|
39
39
|
export interface VirtualUserScheduleInput {
|
|
40
|
-
|
|
40
|
+
virtualUserId: string;
|
|
41
41
|
enabled?: boolean;
|
|
42
42
|
disposition?: VirtualUserDisposition;
|
|
43
43
|
goals?: readonly string[];
|
|
@@ -57,33 +57,33 @@ export interface VirtualUserScheduleInput {
|
|
|
57
57
|
* smaller permission than deciding they should keep going.
|
|
58
58
|
*/
|
|
59
59
|
export interface VirtualUserScheduleStore {
|
|
60
|
-
/** Creates or updates one
|
|
60
|
+
/** Creates or updates one virtual user's cadence. Returns the row as it now stands. */
|
|
61
61
|
set(schedule: VirtualUserScheduleInput): Promise<VirtualUserScheduleRecord>;
|
|
62
|
-
get(
|
|
62
|
+
get(virtualUserId: string): Promise<VirtualUserScheduleRecord | null>;
|
|
63
63
|
list(): Promise<VirtualUserScheduleRecord[]>;
|
|
64
64
|
/** Enabled rows whose `nextRunAt` has passed. */
|
|
65
65
|
due(now: Date): Promise<VirtualUserScheduleRecord[]>;
|
|
66
66
|
/**
|
|
67
|
-
* Pushes the
|
|
67
|
+
* Pushes the virtual user's next run out, and records which run this was.
|
|
68
68
|
*
|
|
69
69
|
* Called *before* the run is dispatched, so a tick that dies halfway does not
|
|
70
70
|
* leave a row due and get re-dispatched by the next one. The cost is that a
|
|
71
71
|
* dispatch which throws waits a full interval rather than retrying, which is
|
|
72
|
-
* the right way round: a
|
|
72
|
+
* the right way round: a virtual user that is failing to start should not be
|
|
73
73
|
* retried every minute for a week.
|
|
74
74
|
*
|
|
75
75
|
* `from` is the `nextRunAt` the caller read, and the write must match it to
|
|
76
|
-
* land — the claim is how a tick wins the
|
|
76
|
+
* land — the claim is how a tick wins the virtual user, not just how it records
|
|
77
77
|
* winning. Two processes on the same cron read the same due row, and without
|
|
78
78
|
* the compare-and-set both would dispatch: the same user acting twice over,
|
|
79
79
|
* at twice the budget, producing findings neither run can reproduce. The
|
|
80
|
-
* loser is told `false` and leaves the
|
|
80
|
+
* loser is told `false` and leaves the virtual user to whoever got there first.
|
|
81
81
|
*/
|
|
82
|
-
claim(
|
|
82
|
+
claim(virtualUserId: string, claim: {
|
|
83
83
|
from: Date;
|
|
84
84
|
nextRunAt: Date;
|
|
85
85
|
runId: string | null;
|
|
86
86
|
at: Date;
|
|
87
87
|
}): Promise<boolean>;
|
|
88
|
-
remove(
|
|
88
|
+
remove(virtualUserId: string): Promise<void>;
|
|
89
89
|
}
|
|
@@ -6,7 +6,7 @@ import type { VirtualUserScheduleRecord, VirtualUserScheduleStore } from './virt
|
|
|
6
6
|
*
|
|
7
7
|
* A run holds no process across a restart — see {@link VirtualUserRunRecord} —
|
|
8
8
|
* so a deploy mid-run strands the record, and a stranded record would block its
|
|
9
|
-
*
|
|
9
|
+
* virtual user's schedule for good. Twice the longest duration budget anyone sets in
|
|
10
10
|
* practice, because the failure this guards against is cheap to recover from
|
|
11
11
|
* and expensive to trigger early: reaping a run that was still working loses
|
|
12
12
|
* its findings.
|
|
@@ -25,7 +25,7 @@ export declare const DEFAULT_MAX_INTERVAL_MS: number;
|
|
|
25
25
|
/** Whether a row is the tick's business, for stores that cannot ask in a query. */
|
|
26
26
|
export declare const isDue: (schedule: VirtualUserScheduleRecord, now: Date) => boolean;
|
|
27
27
|
/**
|
|
28
|
-
* When this
|
|
28
|
+
* When this virtual user should next appear, drawn from its own interval.
|
|
29
29
|
*
|
|
30
30
|
* Uniform between the two bounds. Reversed bounds are read as a range rather
|
|
31
31
|
* than rejected, because a schedule is configuration and a swapped pair is a
|
|
@@ -35,11 +35,11 @@ export declare const nextRunAt: (schedule: Pick<VirtualUserScheduleRecord, 'minI
|
|
|
35
35
|
export type VirtualUserSkipReason = 'in-flight' | 'dispatch-failed' | 'claimed-elsewhere';
|
|
36
36
|
export interface VirtualUserTickResult {
|
|
37
37
|
dispatched: {
|
|
38
|
-
|
|
38
|
+
virtualUserId: string;
|
|
39
39
|
runId: string;
|
|
40
40
|
}[];
|
|
41
41
|
skipped: {
|
|
42
|
-
|
|
42
|
+
virtualUserId: string;
|
|
43
43
|
reason: VirtualUserSkipReason;
|
|
44
44
|
}[];
|
|
45
45
|
/** Runs found stranded at `running` and marked failed. */
|
|
@@ -55,14 +55,14 @@ export interface VirtualUserTickParams {
|
|
|
55
55
|
staleAfterMs?: number;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Acts on whichever
|
|
58
|
+
* Acts on whichever virtual users are due, once.
|
|
59
59
|
*
|
|
60
60
|
* The whole cadence lives in this one call, so what schedules it is the host's
|
|
61
61
|
* choice — a cron wiring, a platform scheduler, or a person clicking a button.
|
|
62
62
|
* Pikku does not start a timer on an app's behalf; a scaffold that did would
|
|
63
63
|
* begin spending model budget the moment a project ran `pikku all`.
|
|
64
64
|
*
|
|
65
|
-
* A
|
|
65
|
+
* A virtual user is skipped, not queued, while its previous run is still going. Two
|
|
66
66
|
* copies of the same user acting at once is not a heavier test, it is a
|
|
67
67
|
* different one, and every finding it produces is unreproducible. Two ticks
|
|
68
68
|
* running at once are held to the same rule by the claim, which only lands for
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A run holds no process across a restart — see {@link VirtualUserRunRecord} —
|
|
6
6
|
* so a deploy mid-run strands the record, and a stranded record would block its
|
|
7
|
-
*
|
|
7
|
+
* virtual user's schedule for good. Twice the longest duration budget anyone sets in
|
|
8
8
|
* practice, because the failure this guards against is cheap to recover from
|
|
9
9
|
* and expensive to trigger early: reaping a run that was still working loses
|
|
10
10
|
* its findings.
|
|
@@ -23,7 +23,7 @@ export const DEFAULT_MAX_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
|
23
23
|
/** Whether a row is the tick's business, for stores that cannot ask in a query. */
|
|
24
24
|
export const isDue = (schedule, now) => schedule.enabled && schedule.nextRunAt.getTime() <= now.getTime();
|
|
25
25
|
/**
|
|
26
|
-
* When this
|
|
26
|
+
* When this virtual user should next appear, drawn from its own interval.
|
|
27
27
|
*
|
|
28
28
|
* Uniform between the two bounds. Reversed bounds are read as a range rather
|
|
29
29
|
* than rejected, because a schedule is configuration and a swapped pair is a
|
|
@@ -35,14 +35,14 @@ export const nextRunAt = (schedule, now, random) => {
|
|
|
35
35
|
return new Date(now.getTime() + low + random() * (high - low));
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
|
-
* Acts on whichever
|
|
38
|
+
* Acts on whichever virtual users are due, once.
|
|
39
39
|
*
|
|
40
40
|
* The whole cadence lives in this one call, so what schedules it is the host's
|
|
41
41
|
* choice — a cron wiring, a platform scheduler, or a person clicking a button.
|
|
42
42
|
* Pikku does not start a timer on an app's behalf; a scaffold that did would
|
|
43
43
|
* begin spending model budget the moment a project ran `pikku all`.
|
|
44
44
|
*
|
|
45
|
-
* A
|
|
45
|
+
* A virtual user is skipped, not queued, while its previous run is still going. Two
|
|
46
46
|
* copies of the same user acting at once is not a heavier test, it is a
|
|
47
47
|
* different one, and every finding it produces is unreproducible. Two ticks
|
|
48
48
|
* running at once are held to the same rule by the claim, which only lands for
|
|
@@ -55,17 +55,17 @@ export const tickVirtualUserSchedules = async ({ schedules, runs, dispatch, now
|
|
|
55
55
|
reaped: [],
|
|
56
56
|
};
|
|
57
57
|
for (const schedule of await schedules.due(now)) {
|
|
58
|
-
const [latest] = await runs.list({
|
|
58
|
+
const [latest] = await runs.list({ virtualUserId: schedule.virtualUserId, limit: 1 });
|
|
59
59
|
if (latest?.status === 'running') {
|
|
60
60
|
if (now.getTime() - latest.createdAt.getTime() < staleAfterMs) {
|
|
61
|
-
result.skipped.push({
|
|
61
|
+
result.skipped.push({ virtualUserId: schedule.virtualUserId, reason: 'in-flight' });
|
|
62
62
|
continue;
|
|
63
63
|
}
|
|
64
64
|
await runs.fail(latest.runId, `Abandoned: still running ${Math.round((now.getTime() - latest.createdAt.getTime()) / 60000)}m after it started, which is longer than any budget allows.`);
|
|
65
65
|
result.reaped.push(latest.runId);
|
|
66
66
|
}
|
|
67
67
|
const due = nextRunAt(schedule, now, random);
|
|
68
|
-
const acquired = await schedules.claim(schedule.
|
|
68
|
+
const acquired = await schedules.claim(schedule.virtualUserId, {
|
|
69
69
|
from: schedule.nextRunAt,
|
|
70
70
|
nextRunAt: due,
|
|
71
71
|
runId: null,
|
|
@@ -73,7 +73,7 @@ export const tickVirtualUserSchedules = async ({ schedules, runs, dispatch, now
|
|
|
73
73
|
});
|
|
74
74
|
if (!acquired) {
|
|
75
75
|
result.skipped.push({
|
|
76
|
-
|
|
76
|
+
virtualUserId: schedule.virtualUserId,
|
|
77
77
|
reason: 'claimed-elsewhere',
|
|
78
78
|
});
|
|
79
79
|
continue;
|
|
@@ -84,18 +84,18 @@ export const tickVirtualUserSchedules = async ({ schedules, runs, dispatch, now
|
|
|
84
84
|
}
|
|
85
85
|
catch {
|
|
86
86
|
result.skipped.push({
|
|
87
|
-
|
|
87
|
+
virtualUserId: schedule.virtualUserId,
|
|
88
88
|
reason: 'dispatch-failed',
|
|
89
89
|
});
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
92
|
-
await schedules.claim(schedule.
|
|
92
|
+
await schedules.claim(schedule.virtualUserId, {
|
|
93
93
|
from: due,
|
|
94
94
|
nextRunAt: due,
|
|
95
95
|
runId,
|
|
96
96
|
at: now,
|
|
97
97
|
});
|
|
98
|
-
result.dispatched.push({
|
|
98
|
+
result.dispatched.push({ virtualUserId: schedule.virtualUserId, runId });
|
|
99
99
|
}
|
|
100
100
|
return result;
|
|
101
101
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ScenarioVirtualUser } from '../../services/virtual-users-service.js';
|
|
2
2
|
import type { VirtualUserTarget } from './virtual-user.types.js';
|
|
3
|
-
export interface
|
|
4
|
-
/** Model the
|
|
3
|
+
export interface VirtualUserTargetOptions {
|
|
4
|
+
/** Model the virtual user uses for its side of a conversation with an agent. */
|
|
5
5
|
model?: string;
|
|
6
6
|
/** Agents this user may talk to. Omit to leave `talkTo` unavailable. */
|
|
7
7
|
agents?: readonly string[];
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Drive a virtual user through
|
|
10
|
+
* Drive a virtual user's run through its signed-in session.
|
|
11
11
|
*
|
|
12
|
-
* This is the only place the engine meets the network. A
|
|
12
|
+
* This is the only place the engine meets the network. A virtual user already signs
|
|
13
13
|
* in as a real user over the app's own auth, keeps its cookies, re-logs-in when
|
|
14
14
|
* a session expires mid-run and reports a status as data rather than throwing —
|
|
15
15
|
* everything a user imitating a user needs, and none of it worth building twice.
|
|
@@ -18,4 +18,4 @@ export interface PersonaTargetOptions {
|
|
|
18
18
|
* error, it is the system saying no, which is information it should be allowed
|
|
19
19
|
* to act on.
|
|
20
20
|
*/
|
|
21
|
-
export declare const
|
|
21
|
+
export declare const virtualUserTarget: (virtualUser: ScenarioVirtualUser, { model, agents }?: VirtualUserTargetOptions) => VirtualUserTarget;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Drive a virtual user through
|
|
2
|
+
* Drive a virtual user's run through its signed-in session.
|
|
3
3
|
*
|
|
4
|
-
* This is the only place the engine meets the network. A
|
|
4
|
+
* This is the only place the engine meets the network. A virtual user already signs
|
|
5
5
|
* in as a real user over the app's own auth, keeps its cookies, re-logs-in when
|
|
6
6
|
* a session expires mid-run and reports a status as data rather than throwing —
|
|
7
7
|
* everything a user imitating a user needs, and none of it worth building twice.
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* error, it is the system saying no, which is information it should be allowed
|
|
11
11
|
* to act on.
|
|
12
12
|
*/
|
|
13
|
-
export const
|
|
14
|
-
call: (rpcName, args) =>
|
|
13
|
+
export const virtualUserTarget = (virtualUser, { model, agents } = {}) => ({
|
|
14
|
+
call: (rpcName, args) => virtualUser.invokeRaw(rpcName, args),
|
|
15
15
|
talkTo: agents?.length
|
|
16
16
|
? async (agent, task) => {
|
|
17
17
|
if (!agents.includes(agent)) {
|
|
@@ -21,7 +21,7 @@ export const personaVirtualUserTarget = (persona, { model, agents } = {}) => ({
|
|
|
21
21
|
transcript: [],
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
return
|
|
24
|
+
return virtualUser.converse({
|
|
25
25
|
agent,
|
|
26
26
|
task,
|
|
27
27
|
// A user asking for something judges the answer by whether they got
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ScenarioHttpResponse } from '../../services/
|
|
2
|
-
import type {
|
|
1
|
+
import type { ScenarioHttpResponse } from '../../services/virtual-users-service.js';
|
|
2
|
+
import type { ConversationVerdict } from './conversation/conversation.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* How a virtual user behaves, mechanically. A disposition changes the loop, the
|
|
5
5
|
* input generation, the memory and the oracle — anything that only changes how
|
|
6
|
-
* the user *talks* is
|
|
6
|
+
* the user *talks* is virtual user prose (`ResolvedVirtualUser.personality`) and does
|
|
7
7
|
* not belong here.
|
|
8
8
|
*
|
|
9
9
|
* - `realistic` — schema-first, goal-directed, plausible values (default).
|
|
@@ -17,11 +17,11 @@ import type { ActorFlowVerdict } from '../actor-flow/actor-flow.types.js';
|
|
|
17
17
|
* `accountable` sits opposite `adversarial` on an *intent* axis — bad faith
|
|
18
18
|
* against good faith — where `careless` and `thorough` are a *care* axis. It is
|
|
19
19
|
* the one disposition whose actions carry consequences: no oracle, no rollback,
|
|
20
|
-
* and every call attributed to the
|
|
20
|
+
* and every call attributed to the virtual user in the audit log like anyone else's.
|
|
21
21
|
*/
|
|
22
22
|
export type VirtualUserDisposition = 'realistic' | 'careless' | 'newcomer' | 'stale' | 'auditor' | 'adversarial' | 'accountable';
|
|
23
23
|
/**
|
|
24
|
-
* The one disposition a production environment
|
|
24
|
+
* The one disposition a declaration may name for a production environment. Virtual users never run against production, so this only keeps the declaration check honest.
|
|
25
25
|
*
|
|
26
26
|
* Every other disposition exists to find out what the product does wrong, which
|
|
27
27
|
* is not a thing to do to real customers' data. Named here so the declaration
|
|
@@ -79,7 +79,7 @@ export interface ApiCatalogueEntry {
|
|
|
79
79
|
/**
|
|
80
80
|
* Scopes the session must hold to call this, all of them.
|
|
81
81
|
*
|
|
82
|
-
* Splits the catalogue in two: what a
|
|
82
|
+
* Splits the catalogue in two: what a virtual user's roles entitle it to reach,
|
|
83
83
|
* and what they do not. Scopes rather than permission-function names because
|
|
84
84
|
* a scope is declared and checkable, whereas a permission is arbitrary code
|
|
85
85
|
* whose verdict nobody can predict without running it.
|
|
@@ -105,8 +105,8 @@ export interface IntentSource {
|
|
|
105
105
|
/** Step prose, as a person would be told it. Never rpc names. */
|
|
106
106
|
steps?: readonly string[];
|
|
107
107
|
tags?: readonly string[];
|
|
108
|
-
/**
|
|
109
|
-
|
|
108
|
+
/** Virtual user ids this intent is available to. Empty means everyone. */
|
|
109
|
+
virtualUsers?: readonly string[];
|
|
110
110
|
}
|
|
111
111
|
/** Why the engine flagged something. */
|
|
112
112
|
export type VirtualUserFindingKind = 'server-error' | 'transport-error' | 'schema-violation' | 'unexpected-success' | 'custom';
|
|
@@ -122,14 +122,14 @@ export interface VirtualUserFinding {
|
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
124
|
* The transport the virtual user acts through. HTTP in production (an
|
|
125
|
-
* `
|
|
125
|
+
* `HttpVirtualUser`), but the engine only sees this contract — which is what
|
|
126
126
|
* lets the whole loop be tested without a network.
|
|
127
127
|
*/
|
|
128
128
|
export interface VirtualUserTarget {
|
|
129
129
|
/** Invoke an RPC as this user, reporting the status as data rather than throwing. */
|
|
130
130
|
call(rpcName: string, args: unknown): Promise<ScenarioHttpResponse>;
|
|
131
|
-
/** Converse with an AI agent in
|
|
132
|
-
talkTo?(agent: string, task: string): Promise<
|
|
131
|
+
/** Converse with an AI agent in virtual user. Omitted by apps that have no agents. */
|
|
132
|
+
talkTo?(agent: string, task: string): Promise<ConversationVerdict>;
|
|
133
133
|
/** Send a fixture file. Omitted by apps with no upload surface. */
|
|
134
134
|
upload?(file: string): Promise<ScenarioHttpResponse>;
|
|
135
135
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The one disposition a production environment
|
|
2
|
+
* The one disposition a declaration may name for a production environment. Virtual users never run against production, so this only keeps the declaration check honest.
|
|
3
3
|
*
|
|
4
4
|
* Every other disposition exists to find out what the product does wrong, which
|
|
5
5
|
* is not a thing to do to real customers' data. Named here so the declaration
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
6
6
|
import type { SerializedError } from '../../../errors/serialized-error.js';
|
|
7
7
|
import type { WorkflowRun } from '../workflow.types.js';
|
|
8
|
-
import type {
|
|
8
|
+
import type { ScenarioVirtualUser } from '../../../services/virtual-users-service.js';
|
|
9
9
|
import type { ScenarioStepOptions, ScenarioStepPhase, ScenarioSurface } from '../scenario-step.types.js';
|
|
10
10
|
/**
|
|
11
11
|
* Workflow step options
|
|
@@ -24,12 +24,12 @@ export interface WorkflowStepOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
compensate?: false;
|
|
26
26
|
/**
|
|
27
|
-
* Run this step as
|
|
28
|
-
*
|
|
27
|
+
* Run this step as a virtual user (scenarios). The RPC is sent through the
|
|
28
|
+
* virtual user's authenticated client over the REAL transport — never dispatched
|
|
29
29
|
* internally — so auth middleware and permissions are exercised end-to-end.
|
|
30
30
|
* The step is recorded durably like any RPC step.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
virtualUser?: ScenarioVirtualUser;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Options for workflow.expectEventually() — a durable polling step used by
|
|
@@ -240,8 +240,8 @@ export interface RpcStepMeta {
|
|
|
240
240
|
inputs?: Record<string, InputSource> | 'passthrough';
|
|
241
241
|
/** Step options */
|
|
242
242
|
options?: WorkflowStepOptions;
|
|
243
|
-
/** Scenario
|
|
244
|
-
|
|
243
|
+
/** Scenario virtual user name this step runs as ({ virtualUser: virtualUsers.x }) */
|
|
244
|
+
virtualUser?: string;
|
|
245
245
|
/** True for workflow.expectEventually polling steps */
|
|
246
246
|
expectEventually?: boolean;
|
|
247
247
|
}
|
|
@@ -267,8 +267,8 @@ export interface ScenarioStepMeta {
|
|
|
267
267
|
inputs?: Record<string, InputSource> | 'passthrough';
|
|
268
268
|
/** Step options */
|
|
269
269
|
options?: WorkflowStepOptions;
|
|
270
|
-
/** Scenario
|
|
271
|
-
|
|
270
|
+
/** Scenario virtual user name this step runs as ({ virtualUser: virtualUsers.x }) */
|
|
271
|
+
virtualUser?: string;
|
|
272
272
|
/** Mirrors the surfaces the step function declares a binding for */
|
|
273
273
|
surfaces?: ScenarioSurface[];
|
|
274
274
|
}
|
|
@@ -595,7 +595,7 @@ export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
|
|
|
595
595
|
*/
|
|
596
596
|
context: ScenarioContext<Out>;
|
|
597
597
|
/**
|
|
598
|
-
* Durable polling step: invoke `rpcName` (as
|
|
598
|
+
* Durable polling step: invoke `rpcName` (as a virtual user when `options.virtualUser`
|
|
599
599
|
* is set) until `predicate` passes or `options.within` elapses.
|
|
600
600
|
*/
|
|
601
601
|
expectEventually: <TOutput = any, TInput = any>(stepName: string, rpcName: string, data: TInput, predicate: (output: TOutput) => boolean, options?: WorkflowExpectEventuallyOptions) => Promise<TOutput>;
|
|
@@ -2,7 +2,7 @@ import { PikkuError } from '../../errors/error-handler.js';
|
|
|
2
2
|
import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js';
|
|
3
3
|
import type { RunLifecycleContext, WorkflowRunEngine, WorkflowRunExtension } from './workflow-run-engine.types.js';
|
|
4
4
|
import type { PikkuRawWire } from '../../types/core.types.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ScenarioVirtualUsers } from '../../services/virtual-users-service.js';
|
|
6
6
|
import type { ScenarioStepVideoOffset } from './scenario-run.types.js';
|
|
7
7
|
import type { ScenarioBrowserProvider, ScenarioEnvironment, ScenarioSurface } from './scenario-step.types.js';
|
|
8
8
|
import type { PikkuWorkflowWire, WorkflowQueueOptions } from './workflow.types.js';
|
|
@@ -12,11 +12,11 @@ export type { ScenarioArtifact, ScenarioFailureDetail, ScenarioResult, ScenarioR
|
|
|
12
12
|
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
13
13
|
export { resolveScenarioSurfaces } from './scenario-surface.js';
|
|
14
14
|
export { pollUntil, type PollOptions } from './scenario-poll.js';
|
|
15
|
-
export { createCookieJar } from '
|
|
16
|
-
export type { ScenarioCookieJar } from '
|
|
15
|
+
export { createCookieJar } from '../../services/scenario-cookie-jar.js';
|
|
16
|
+
export type { ScenarioCookieJar } from '../../services/scenario-cookie-jar.js';
|
|
17
17
|
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
18
18
|
export { requireScenarioEnv } from './scenario-step-guards.js';
|
|
19
|
-
export type { ScenarioHttpResponse, ScenarioJsonRequest, } from '../../services/
|
|
19
|
+
export type { ScenarioHttpResponse, ScenarioJsonRequest, } from '../../services/virtual-users-service.js';
|
|
20
20
|
/**
|
|
21
21
|
* A workflow service with the scenario capability attached — the two lines
|
|
22
22
|
* `pikku scenario run` needs, in one call so no caller has to remember that the
|
|
@@ -44,7 +44,7 @@ export declare class ScenarioHookError extends PikkuError {
|
|
|
44
44
|
* Two witnesses of the same `then` disagreed.
|
|
45
45
|
*
|
|
46
46
|
* This is the failure the whole additive-witness design exists to produce: the
|
|
47
|
-
* system of record is right and the surface the
|
|
47
|
+
* system of record is right and the surface the virtual user was looking at is not, or
|
|
48
48
|
* the other way round. Both observations go in the message, because either one
|
|
49
49
|
* alone reads as a passing assertion.
|
|
50
50
|
*/
|
|
@@ -74,14 +74,14 @@ export declare class ScenarioBrowserUnavailable extends PikkuError {
|
|
|
74
74
|
constructor(stepFunc: string);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
* A step that is driven by a
|
|
77
|
+
* A step that is driven by a virtual user was dispatched without one.
|
|
78
78
|
*
|
|
79
79
|
* Raised before the body runs rather than partway through it: the step declared
|
|
80
|
-
* that it needs
|
|
81
|
-
* provisioned without a window to open, or by `
|
|
80
|
+
* that it needs a virtual user — by having a `browser` binding, which cannot be
|
|
81
|
+
* provisioned without a window to open, or by `virtualUser: true` — so a call site
|
|
82
82
|
* that omits one is a mistake at the call site.
|
|
83
83
|
*/
|
|
84
|
-
export declare class
|
|
84
|
+
export declare class ScenarioVirtualUserRequired extends PikkuError {
|
|
85
85
|
readonly stepFunc: string;
|
|
86
86
|
constructor(stepFunc: string);
|
|
87
87
|
}
|
|
@@ -122,7 +122,7 @@ export declare class ScenarioNoWitness extends PikkuError {
|
|
|
122
122
|
* An assertion ran, but not on the surface the run targets.
|
|
123
123
|
*
|
|
124
124
|
* The sibling of {@link ScenarioNoWitness}: that one checked nothing anywhere,
|
|
125
|
-
* this one checked the system of record while the prose claims the
|
|
125
|
+
* this one checked the system of record while the prose claims the virtual user saw it
|
|
126
126
|
* on the page. Ordinary runs count it as a coverage gap; a strict run refuses
|
|
127
127
|
* it, because a flow that cannot be observed end to end on the run surface is
|
|
128
128
|
* not a flow that surface can be documented from.
|
|
@@ -138,7 +138,7 @@ export declare class ScenarioUnwitnessedAssertion extends PikkuError {
|
|
|
138
138
|
* The scenario capability, layered onto a workflow service rather than being
|
|
139
139
|
* one.
|
|
140
140
|
*
|
|
141
|
-
* Every scenario affordance — steps,
|
|
141
|
+
* Every scenario affordance — steps, virtual users, lifecycle hooks, the browser
|
|
142
142
|
* provider, the assertion wire members — lives here rather than on
|
|
143
143
|
* `PikkuWorkflowService`, because a bundler drops an unused *module* but never
|
|
144
144
|
* an unused class member: anything declared on the workflow service ships in
|
|
@@ -160,7 +160,7 @@ export declare class ScenarioUnwitnessedAssertion extends PikkuError {
|
|
|
160
160
|
*/
|
|
161
161
|
export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
162
162
|
private readonly engine;
|
|
163
|
-
private
|
|
163
|
+
private runVirtualUsers;
|
|
164
164
|
private runContexts;
|
|
165
165
|
private runVideoOffsets;
|
|
166
166
|
private scenarioBrowserProvider?;
|
|
@@ -169,7 +169,7 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
169
169
|
private strictSurface;
|
|
170
170
|
constructor(engine: WorkflowRunEngine);
|
|
171
171
|
/**
|
|
172
|
-
* The surface every
|
|
172
|
+
* The surface every virtual user drives the system through for this run, set once by
|
|
173
173
|
* the runner from `--run`. `default` is the server-side path — the fast suite.
|
|
174
174
|
*
|
|
175
175
|
* `strict` removes both routes off that surface: an action may not fall back
|
|
@@ -196,7 +196,7 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
196
196
|
setScenarioEnvironment(env: ScenarioEnvironment | undefined): void;
|
|
197
197
|
getScenarioEnvironment(): ScenarioEnvironment | undefined;
|
|
198
198
|
/**
|
|
199
|
-
* Where each of a run's browser steps fell in its
|
|
199
|
+
* Where each of a run's browser steps fell in its virtual user's video, keyed by the
|
|
200
200
|
* durable step name, handed over and forgotten in one call.
|
|
201
201
|
*
|
|
202
202
|
* Taken rather than read because the runner joins these onto the step rows
|
|
@@ -204,18 +204,18 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
204
204
|
* clear them, and the only moment they are still wanted.
|
|
205
205
|
*/
|
|
206
206
|
takeStepVideoOffsets(runId: string): Map<string, ScenarioStepVideoOffset[]>;
|
|
207
|
-
/** Where a step starting now falls in this
|
|
207
|
+
/** Where a step starting now falls in this virtual user's recording, if anywhere. */
|
|
208
208
|
private videoOffsetFor;
|
|
209
209
|
/**
|
|
210
|
-
* Stamp where a step began inside one
|
|
210
|
+
* Stamp where a step began inside one virtual user's recording.
|
|
211
211
|
*
|
|
212
|
-
* First write per
|
|
212
|
+
* First write per virtual user wins: a `then` step runs once per witness, and the
|
|
213
213
|
* moment the reader wants is when the sentence started, not when its last
|
|
214
214
|
* witness got around to the browser.
|
|
215
215
|
*/
|
|
216
216
|
private recordVideoOffset;
|
|
217
217
|
attachRunContext(runId: string, workflowMeta: any, options?: {
|
|
218
|
-
|
|
218
|
+
virtualUsers?: ScenarioVirtualUsers;
|
|
219
219
|
}): Promise<void>;
|
|
220
220
|
detachRunContext(runId: string): void;
|
|
221
221
|
/**
|
|
@@ -249,20 +249,20 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
249
249
|
*
|
|
250
250
|
* A hook is not a pikku function: it has no id, no meta and no schema, so it
|
|
251
251
|
* cannot go through `runPikkuFunc` and the runner records nothing for it. It
|
|
252
|
-
* gets exactly what the scenario body gets — the same wire (so `
|
|
252
|
+
* gets exactly what the scenario body gets — the same wire (so `virtualUsers` is
|
|
253
253
|
* how it reaches the app), and singleton services composed with this
|
|
254
254
|
* invocation's wire services — and nothing else.
|
|
255
255
|
*/
|
|
256
256
|
private runScenarioHook;
|
|
257
257
|
/**
|
|
258
|
-
* Build HTTP scenario
|
|
259
|
-
*
|
|
258
|
+
* Build HTTP scenario virtual users for a run started without them; undefined when
|
|
259
|
+
* the app has no auth service or the API URL is missing.
|
|
260
260
|
*
|
|
261
|
-
* The
|
|
262
|
-
* for the AI
|
|
263
|
-
* signs
|
|
261
|
+
* The virtual user client is imported lazily so that even a runner bundle only pays
|
|
262
|
+
* for the AI virtual user conversation loop it pulls in when a scenario actually
|
|
263
|
+
* signs a virtual user in.
|
|
264
264
|
*/
|
|
265
|
-
|
|
265
|
+
resolveVirtualUsers(): Promise<ScenarioVirtualUsers | undefined>;
|
|
266
266
|
decorateWorkflowWire(wire: PikkuWorkflowWire, context: {
|
|
267
267
|
name: string;
|
|
268
268
|
runId: string;
|
|
@@ -278,8 +278,8 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
278
278
|
*/
|
|
279
279
|
private declaredSurfaces;
|
|
280
280
|
/**
|
|
281
|
-
* Whether a step is driven by a
|
|
282
|
-
* the definer from a `browser` binding or an explicit `
|
|
281
|
+
* Whether a step is driven by a virtual user, and so must be given one. Stamped by
|
|
282
|
+
* the definer from a `browser` binding or an explicit `virtualUser: true`.
|
|
283
283
|
*/
|
|
284
|
-
private
|
|
284
|
+
private requiresVirtualUser;
|
|
285
285
|
}
|