@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
|
@@ -16,7 +16,7 @@ import type { IntentRecord, StepRecord, VirtualUserDisposition, VirtualUserFindi
|
|
|
16
16
|
*/
|
|
17
17
|
export interface VirtualUserRunRecord {
|
|
18
18
|
runId: string;
|
|
19
|
-
|
|
19
|
+
virtualUserId: string;
|
|
20
20
|
disposition: VirtualUserDisposition;
|
|
21
21
|
/** What makes a run replayable at all — a finding without it is an anecdote. */
|
|
22
22
|
seed: number;
|
|
@@ -59,7 +59,7 @@ export interface VirtualUserRunRecord {
|
|
|
59
59
|
}
|
|
60
60
|
/** What a run is created with — everything else is filled in by the outcome. */
|
|
61
61
|
export interface VirtualUserRunStart {
|
|
62
|
-
|
|
62
|
+
virtualUserId: string;
|
|
63
63
|
disposition: VirtualUserDisposition;
|
|
64
64
|
seed: number;
|
|
65
65
|
goals?: readonly string[];
|
|
@@ -101,9 +101,9 @@ export interface VirtualUserRunStore {
|
|
|
101
101
|
/** Marks a run `failed`. The run itself broke; it has no findings. */
|
|
102
102
|
fail(runId: string, error: string): Promise<void>;
|
|
103
103
|
get(runId: string): Promise<VirtualUserRunRecord | null>;
|
|
104
|
-
/** Newest first. `
|
|
104
|
+
/** Newest first. `virtualUserId` narrows to one virtual user's history. */
|
|
105
105
|
list(options?: {
|
|
106
|
-
|
|
106
|
+
virtualUserId?: string;
|
|
107
107
|
limit?: number;
|
|
108
108
|
offset?: number;
|
|
109
109
|
}): Promise<VirtualUserRunRecord[]>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { type InProcessAuth } from '../../services/virtual-user-sign-in.js';
|
|
1
2
|
import type { Logger } from '../../services/logger.js';
|
|
2
3
|
import type { MetaService } from '../../services/meta-service.js';
|
|
3
4
|
import type { VariablesService } from '../../services/variables-service.js';
|
|
4
5
|
import type { AgentRunnerService } from '../../services/agent-runner-service.js';
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
6
|
+
import type { HttpVirtualUsersConfig } from '../../services/http-virtual-users.js';
|
|
7
|
+
import type { ResolvedVirtualUser, ScenarioVirtualUsers } from '../../services/virtual-users-service.js';
|
|
8
|
+
import type { VirtualUserEnvironment } from '../virtual-user/virtual-user-environments.js';
|
|
8
9
|
import type { StepRecord, VirtualUserDisposition } from './virtual-user.types.js';
|
|
9
10
|
import type { VirtualUserRunRecord, VirtualUserRunStore } from './virtual-user-run-store.js';
|
|
10
11
|
import type { VirtualUserScheduleRecord, VirtualUserScheduleStore } from './virtual-user-schedule-store.js';
|
|
@@ -20,11 +21,11 @@ import type { VirtualUserTickResult } from './virtual-user-schedule.js';
|
|
|
20
21
|
* once rather than in every generated copy of it.
|
|
21
22
|
*
|
|
22
23
|
* What an application does supply arrives as a parameter — its declared
|
|
23
|
-
*
|
|
24
|
+
* virtual users, its `createVirtualUsers`, its config — because those are the only
|
|
24
25
|
* things codegen knows that this cannot.
|
|
25
26
|
*/
|
|
26
|
-
/**
|
|
27
|
-
export type
|
|
27
|
+
/** Virtual user id → the declaration, which is what `virtualUserConfigs` is. */
|
|
28
|
+
export type ScaffoldVirtualUsers = Record<string, ResolvedVirtualUser>;
|
|
28
29
|
/**
|
|
29
30
|
* The variables a scaffolded run reads.
|
|
30
31
|
*
|
|
@@ -39,48 +40,27 @@ export declare const VIRTUAL_USER_VARIABLES: {
|
|
|
39
40
|
* somewhere it only assumed was itself.
|
|
40
41
|
*/
|
|
41
42
|
readonly apiUrl: 'VIRTUAL_USER_API_URL';
|
|
42
|
-
readonly secret: 'SCENARIO_ACTOR_SECRET';
|
|
43
43
|
readonly model: 'VIRTUAL_USER_MODEL';
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* auth somewhere other than the root — `/api/auth` is the common one — has no
|
|
48
|
-
* other way to say so, and without them the run signs in against a 404 and
|
|
49
|
-
* spends its whole budget thinking about why nothing works.
|
|
45
|
+
* Where the app serves its exposed RPCs, as a scenario run reads it. An app
|
|
46
|
+
* that mounts them somewhere other than `/rpc` has no other way to say so.
|
|
50
47
|
*/
|
|
51
|
-
readonly signInPath: 'SCENARIO_SIGN_IN_PATH';
|
|
52
48
|
readonly rpcPath: 'SCENARIO_RPC_PATH';
|
|
53
|
-
/**
|
|
54
|
-
* The deployed way in. A Fabric operator token is asymmetric — a stage can
|
|
55
|
-
* verify one and can never mint one — so unlike the actor secret it is safe
|
|
56
|
-
* for a run against a real environment. Read from the environment only as the
|
|
57
|
-
* fallback for a run nobody handed a token to, which is what a schedule is.
|
|
58
|
-
*/
|
|
59
|
-
readonly operatorToken: 'FABRIC_OPERATOR_TOKEN';
|
|
60
49
|
};
|
|
61
50
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* Both are better-auth plugins mounted side by side, so `SCENARIO_SIGN_IN_PATH`
|
|
65
|
-
* names the mount and the last segment is ours to pick — an app that moved auth
|
|
66
|
-
* to `/api/auth` says so once and both paths follow. A path that names neither
|
|
67
|
-
* plugin is left alone, since it was configured deliberately.
|
|
68
|
-
*/
|
|
69
|
-
export declare const signInPathFor: (configured: string | undefined, plugin: 'actor' | 'fabric') => string | undefined;
|
|
70
|
-
/**
|
|
71
|
-
* The declared persona behind an id, refused unless it is one a run may be.
|
|
51
|
+
* The declared virtual user behind an id, refused unless it is one a run may be.
|
|
72
52
|
*
|
|
73
|
-
* An acted-upon
|
|
53
|
+
* An acted-upon virtual user has no session of its own, and running one would race
|
|
74
54
|
* whatever scenario acts on it.
|
|
75
55
|
*/
|
|
76
|
-
export declare const
|
|
56
|
+
export declare const runnableVirtualUser: (virtualUsers: ScaffoldVirtualUsers, virtualUserId: string) => ResolvedVirtualUser;
|
|
77
57
|
/** The store, or the error naming the one to wire. */
|
|
78
58
|
export declare const requireVirtualUserRunStore: (store: VirtualUserRunStore | undefined, reading?: boolean) => VirtualUserRunStore;
|
|
79
59
|
export declare const requireVirtualUserScheduleStore: (store: VirtualUserScheduleStore | undefined) => VirtualUserScheduleStore;
|
|
80
60
|
/** What a caller asked for, before the declaration fills in what it left out. */
|
|
81
61
|
export interface StartVirtualUserRunParams {
|
|
82
62
|
store: VirtualUserRunStore | undefined;
|
|
83
|
-
|
|
63
|
+
virtualUsers: ScaffoldVirtualUsers;
|
|
84
64
|
/**
|
|
85
65
|
* The app's config, read only for `nodeEnv` — structural because an
|
|
86
66
|
* application's Config is its own interface and need not declare it at all.
|
|
@@ -89,11 +69,11 @@ export interface StartVirtualUserRunParams {
|
|
|
89
69
|
config: {
|
|
90
70
|
nodeEnv?: string;
|
|
91
71
|
} | undefined;
|
|
92
|
-
/** `environments` from pikku.config.json, as generated beside the
|
|
93
|
-
environments?: Readonly<Record<string,
|
|
72
|
+
/** `environments` from pikku.config.json, as generated beside the virtual users. */
|
|
73
|
+
environments?: Readonly<Record<string, VirtualUserEnvironment>>;
|
|
94
74
|
/** Which of them this process is. Defaults to `PIKKU_ENV`. */
|
|
95
75
|
environment?: string;
|
|
96
|
-
|
|
76
|
+
virtualUserId: string;
|
|
97
77
|
disposition?: string;
|
|
98
78
|
seed?: number;
|
|
99
79
|
goals?: string[];
|
|
@@ -104,7 +84,7 @@ export interface StartVirtualUserRunParams {
|
|
|
104
84
|
/** The recorded run, and the values the dispatch has to carry unchanged. */
|
|
105
85
|
export interface StartedVirtualUserRun {
|
|
106
86
|
runId: string;
|
|
107
|
-
|
|
87
|
+
virtualUserId: string;
|
|
108
88
|
disposition: VirtualUserDisposition;
|
|
109
89
|
seed: number;
|
|
110
90
|
goals: string[];
|
|
@@ -117,7 +97,7 @@ export interface StartedVirtualUserRun {
|
|
|
117
97
|
* scheduled tick have in common. The dispatch that follows is typed off the
|
|
118
98
|
* app's RPC map, so it stays in the generated wiring.
|
|
119
99
|
*/
|
|
120
|
-
export declare const startVirtualUserRun: ({ store,
|
|
100
|
+
export declare const startVirtualUserRun: ({ store, virtualUsers, config, environments, environment, virtualUserId, disposition: requested, seed: requestedSeed, goals, memory, startedBy, }: StartVirtualUserRunParams) => Promise<StartedVirtualUserRun>;
|
|
121
101
|
/**
|
|
122
102
|
* One run on the wire.
|
|
123
103
|
*
|
|
@@ -127,7 +107,7 @@ export declare const startVirtualUserRun: ({ store, personas, config, environmen
|
|
|
127
107
|
*/
|
|
128
108
|
export declare const serializeVirtualUserRun: (run: VirtualUserRunRecord) => {
|
|
129
109
|
runId: string;
|
|
130
|
-
|
|
110
|
+
virtualUserId: string;
|
|
131
111
|
disposition: VirtualUserDisposition;
|
|
132
112
|
seed: number;
|
|
133
113
|
status: "completed" | "failed" | "running";
|
|
@@ -175,8 +155,8 @@ export declare const serializeVirtualUserSteps: (steps: readonly StepRecord[]) =
|
|
|
175
155
|
* takes; the engine's own duration also accepts `'30m'`, which nothing on this
|
|
176
156
|
* side ever writes.
|
|
177
157
|
*/
|
|
178
|
-
export declare const serializeVirtualUserSchedule: (schedule: VirtualUserScheduleRecord,
|
|
179
|
-
|
|
158
|
+
export declare const serializeVirtualUserSchedule: (schedule: VirtualUserScheduleRecord, virtualUsers: ScaffoldVirtualUsers) => {
|
|
159
|
+
virtualUserId: string;
|
|
180
160
|
enabled: boolean;
|
|
181
161
|
disposition: VirtualUserDisposition;
|
|
182
162
|
goals: string[];
|
|
@@ -197,8 +177,8 @@ export declare const serializeVirtualUserSchedule: (schedule: VirtualUserSchedul
|
|
|
197
177
|
};
|
|
198
178
|
export interface WriteVirtualUserScheduleParams {
|
|
199
179
|
store: VirtualUserScheduleStore | undefined;
|
|
200
|
-
|
|
201
|
-
|
|
180
|
+
virtualUsers: ScaffoldVirtualUsers;
|
|
181
|
+
virtualUserId: string;
|
|
202
182
|
enabled?: boolean;
|
|
203
183
|
disposition?: string;
|
|
204
184
|
goals?: string[];
|
|
@@ -214,25 +194,25 @@ export interface WriteVirtualUserScheduleParams {
|
|
|
214
194
|
config?: {
|
|
215
195
|
nodeEnv?: string;
|
|
216
196
|
};
|
|
217
|
-
environments?: Readonly<Record<string,
|
|
197
|
+
environments?: Readonly<Record<string, VirtualUserEnvironment>>;
|
|
218
198
|
environment?: string;
|
|
219
199
|
}
|
|
220
200
|
/**
|
|
221
|
-
* Writes a
|
|
201
|
+
* Writes a virtual user's cadence.
|
|
222
202
|
*
|
|
223
203
|
* Applies the same rules `startVirtualUserRun` enforces, at the point the row is
|
|
224
|
-
* written rather than every hour afterwards: an acted-upon
|
|
225
|
-
* session, and
|
|
226
|
-
*
|
|
204
|
+
* written rather than every hour afterwards: an acted-upon virtual user has no
|
|
205
|
+
* session, and production refuses every run, so either is a tick that can only
|
|
206
|
+
* ever fail to start, with nobody watching.
|
|
227
207
|
*
|
|
228
|
-
*
|
|
229
|
-
* keeps what it had: enabling a row saved earlier as
|
|
230
|
-
*
|
|
208
|
+
* Whether the row is enabled is the one thing checked, since a field left out
|
|
209
|
+
* keeps what it had: enabling a row saved earlier is refused just as saving a
|
|
210
|
+
* new one would be.
|
|
231
211
|
*/
|
|
232
|
-
export declare const writeVirtualUserSchedule: ({ store,
|
|
212
|
+
export declare const writeVirtualUserSchedule: ({ store, virtualUsers, virtualUserId, enabled, disposition, goals, budget, minIntervalMs, maxIntervalMs, nextRunAt, config, environments, environment, }: WriteVirtualUserScheduleParams) => Promise<VirtualUserScheduleRecord>;
|
|
233
213
|
/** What a due schedule asks `runVirtualUser` for. */
|
|
234
214
|
export declare const virtualUserScheduleRunInput: (schedule: VirtualUserScheduleRecord) => {
|
|
235
|
-
|
|
215
|
+
virtualUserId: string;
|
|
236
216
|
disposition: VirtualUserDisposition;
|
|
237
217
|
goals: string[];
|
|
238
218
|
budget: {
|
|
@@ -245,7 +225,7 @@ export declare const virtualUserScheduleRunInput: (schedule: VirtualUserSchedule
|
|
|
245
225
|
* What a tick did.
|
|
246
226
|
*
|
|
247
227
|
* Logged rather than returned: the caller is a cron, and a run this started is
|
|
248
|
-
* otherwise the only trace that a
|
|
228
|
+
* otherwise the only trace that a virtual user is still out there working.
|
|
249
229
|
*/
|
|
250
230
|
export declare const logVirtualUserTick: (logger: Logger, result: VirtualUserTickResult) => void;
|
|
251
231
|
export interface ExecuteVirtualUserRunParams {
|
|
@@ -254,11 +234,11 @@ export interface ExecuteVirtualUserRunParams {
|
|
|
254
234
|
agentRunner: AgentRunnerService | undefined;
|
|
255
235
|
variables: VariablesService;
|
|
256
236
|
logger: Logger;
|
|
257
|
-
|
|
258
|
-
/** The app's generated `
|
|
259
|
-
|
|
237
|
+
virtualUsers: ScaffoldVirtualUsers;
|
|
238
|
+
/** The app's generated `createVirtualUsers`, which knows its own virtual user ids. */
|
|
239
|
+
createVirtualUsers: (options: Omit<HttpVirtualUsersConfig, 'virtualUsers'>) => ScenarioVirtualUsers;
|
|
260
240
|
runId: string;
|
|
261
|
-
|
|
241
|
+
virtualUserId: string;
|
|
262
242
|
disposition: string;
|
|
263
243
|
goals: string[];
|
|
264
244
|
memory: Record<string, string>;
|
|
@@ -268,16 +248,17 @@ export interface ExecuteVirtualUserRunParams {
|
|
|
268
248
|
mutations?: number;
|
|
269
249
|
durationMs?: number;
|
|
270
250
|
};
|
|
271
|
-
|
|
251
|
+
/** The app's auth instance, which a run signs in through without a credential. */
|
|
252
|
+
auth: InProcessAuth | undefined;
|
|
272
253
|
}
|
|
273
254
|
/**
|
|
274
255
|
* The run itself.
|
|
275
256
|
*
|
|
276
257
|
* Everything it needs is derived through `metaService` and the generated
|
|
277
|
-
*
|
|
258
|
+
* virtual users — the same public surface any consumer has. Nothing reaches into
|
|
278
259
|
* pikku's internals, because an app could not, and a feature built on what only
|
|
279
260
|
* the framework can see would not be this feature.
|
|
280
261
|
*/
|
|
281
|
-
export declare const executeVirtualUserRun: ({ runStore, metaService, agentRunner, variables, logger,
|
|
262
|
+
export declare const executeVirtualUserRun: ({ runStore, metaService, agentRunner, variables, logger, virtualUsers, createVirtualUsers, runId, virtualUserId, disposition, goals, memory, seed, budget, auth, }: ExecuteVirtualUserRunParams) => Promise<{
|
|
282
263
|
findings: number;
|
|
283
264
|
}>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { inProcessSignIn, } from '../../services/virtual-user-sign-in.js';
|
|
1
2
|
import { readEnvVariable } from '../../env.js';
|
|
2
3
|
import { BadRequestError, ForbiddenError } from '../../errors/errors.js';
|
|
3
4
|
import { prepareVirtualUserRun } from './prepare-virtual-user-run.js';
|
|
4
5
|
import { runVirtualUser as runVirtualUserEngine } from './run-virtual-user.js';
|
|
5
|
-
import {
|
|
6
|
-
import { PRODUCTION_DISPOSITION } from './virtual-user.types.js';
|
|
6
|
+
import { virtualUserTarget } from './virtual-user-target.js';
|
|
7
7
|
/**
|
|
8
8
|
* The variables a scaffolded run reads.
|
|
9
9
|
*
|
|
@@ -18,55 +18,28 @@ export const VIRTUAL_USER_VARIABLES = {
|
|
|
18
18
|
* somewhere it only assumed was itself.
|
|
19
19
|
*/
|
|
20
20
|
apiUrl: 'VIRTUAL_USER_API_URL',
|
|
21
|
-
secret: 'SCENARIO_ACTOR_SECRET',
|
|
22
21
|
model: 'VIRTUAL_USER_MODEL',
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* auth somewhere other than the root — `/api/auth` is the common one — has no
|
|
27
|
-
* other way to say so, and without them the run signs in against a 404 and
|
|
28
|
-
* spends its whole budget thinking about why nothing works.
|
|
23
|
+
* Where the app serves its exposed RPCs, as a scenario run reads it. An app
|
|
24
|
+
* that mounts them somewhere other than `/rpc` has no other way to say so.
|
|
29
25
|
*/
|
|
30
|
-
signInPath: 'SCENARIO_SIGN_IN_PATH',
|
|
31
26
|
rpcPath: 'SCENARIO_RPC_PATH',
|
|
32
|
-
/**
|
|
33
|
-
* The deployed way in. A Fabric operator token is asymmetric — a stage can
|
|
34
|
-
* verify one and can never mint one — so unlike the actor secret it is safe
|
|
35
|
-
* for a run against a real environment. Read from the environment only as the
|
|
36
|
-
* fallback for a run nobody handed a token to, which is what a schedule is.
|
|
37
|
-
*/
|
|
38
|
-
operatorToken: 'FABRIC_OPERATOR_TOKEN',
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Which door under the auth mount, given the credential in hand.
|
|
42
|
-
*
|
|
43
|
-
* Both are better-auth plugins mounted side by side, so `SCENARIO_SIGN_IN_PATH`
|
|
44
|
-
* names the mount and the last segment is ours to pick — an app that moved auth
|
|
45
|
-
* to `/api/auth` says so once and both paths follow. A path that names neither
|
|
46
|
-
* plugin is left alone, since it was configured deliberately.
|
|
47
|
-
*/
|
|
48
|
-
export const signInPathFor = (configured, plugin) => {
|
|
49
|
-
if (!configured) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
const mount = configured.replace(/\/sign-in\/(actor|fabric)$/, '');
|
|
53
|
-
return mount === configured ? configured : `${mount}/sign-in/${plugin}`;
|
|
54
27
|
};
|
|
55
28
|
/**
|
|
56
|
-
* The declared
|
|
29
|
+
* The declared virtual user behind an id, refused unless it is one a run may be.
|
|
57
30
|
*
|
|
58
|
-
* An acted-upon
|
|
31
|
+
* An acted-upon virtual user has no session of its own, and running one would race
|
|
59
32
|
* whatever scenario acts on it.
|
|
60
33
|
*/
|
|
61
|
-
export const
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
throw new BadRequestError(`Unknown
|
|
34
|
+
export const runnableVirtualUser = (virtualUsers, virtualUserId) => {
|
|
35
|
+
const virtualUser = virtualUsers[virtualUserId];
|
|
36
|
+
if (!virtualUser) {
|
|
37
|
+
throw new BadRequestError(`Unknown virtual user "${virtualUserId}" — declare it with defineVirtualUsers()`);
|
|
65
38
|
}
|
|
66
|
-
if (!
|
|
67
|
-
throw new BadRequestError(`
|
|
39
|
+
if (!virtualUser.runnable) {
|
|
40
|
+
throw new BadRequestError(`VirtualUser "${virtualUserId}" is declared as acted upon, never run`);
|
|
68
41
|
}
|
|
69
|
-
return
|
|
42
|
+
return virtualUser;
|
|
70
43
|
};
|
|
71
44
|
const MISSING_RUN_STORE = 'No virtualUserRunStore is wired — a run has nowhere to be recorded. ' +
|
|
72
45
|
'Wire KyselyVirtualUserRunStore from @pikku/kysely, or your own implementation of VirtualUserRunStore.';
|
|
@@ -94,7 +67,7 @@ export const requireVirtualUserScheduleStore = (store) => {
|
|
|
94
67
|
* `NODE_ENV=production` there too — keying on it refuses every disposition on
|
|
95
68
|
* the one environment they exist to be used on. `PIKKU_ENV` names which of the
|
|
96
69
|
* configured environments this is, which is the question actually being asked,
|
|
97
|
-
* and it is the same signal `
|
|
70
|
+
* and it is the same signal `virtualUserEnvironmentRefusal` already checks at
|
|
98
71
|
* sign-in.
|
|
99
72
|
*
|
|
100
73
|
* Unresolved is treated as production: an environment nobody can name is one
|
|
@@ -108,12 +81,11 @@ const isProductionRun = (config, environments, environment) => {
|
|
|
108
81
|
}
|
|
109
82
|
return environment ? Boolean(environments[environment]?.production) : true;
|
|
110
83
|
};
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
const refuseInProduction = (
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
throw new ForbiddenError(`Only the '${PRODUCTION_DISPOSITION}' disposition may run against production; "${personaId}" is ${disposition}`);
|
|
84
|
+
// A virtual user is a probe, and probing is not a thing to do to real
|
|
85
|
+
// customers' data, whatever the disposition.
|
|
86
|
+
const refuseInProduction = (virtualUserId, config, environments, environment) => {
|
|
87
|
+
if (isProductionRun(config, environments, environment)) {
|
|
88
|
+
throw new ForbiddenError(`Virtual users do not run against production; "${virtualUserId}" cannot run here`);
|
|
117
89
|
}
|
|
118
90
|
};
|
|
119
91
|
/**
|
|
@@ -123,15 +95,13 @@ const refuseInProduction = (personaId, disposition, config, environments, enviro
|
|
|
123
95
|
* scheduled tick have in common. The dispatch that follows is typed off the
|
|
124
96
|
* app's RPC map, so it stays in the generated wiring.
|
|
125
97
|
*/
|
|
126
|
-
export const startVirtualUserRun = async ({ store,
|
|
98
|
+
export const startVirtualUserRun = async ({ store, virtualUsers, config, environments, environment = readEnvVariable('PIKKU_ENV'), virtualUserId, disposition: requested, seed: requestedSeed, goals, memory, startedBy, }) => {
|
|
127
99
|
const runStore = requireVirtualUserRunStore(store);
|
|
128
|
-
const
|
|
100
|
+
const virtualUser = runnableVirtualUser(virtualUsers, virtualUserId);
|
|
129
101
|
const disposition = (requested ??
|
|
130
|
-
|
|
102
|
+
virtualUser.disposition ??
|
|
131
103
|
'realistic');
|
|
132
|
-
|
|
133
|
-
// one in.
|
|
134
|
-
refuseInProduction(personaId, disposition, config, environments, environment);
|
|
104
|
+
refuseInProduction(virtualUserId, config, environments, environment);
|
|
135
105
|
// Seeded here rather than inside the engine so the record carries the seed
|
|
136
106
|
// even if the run dies before returning — an unreproducible crash costs the
|
|
137
107
|
// most.
|
|
@@ -139,7 +109,7 @@ export const startVirtualUserRun = async ({ store, personas, config, environment
|
|
|
139
109
|
const resolvedGoals = goals ?? [];
|
|
140
110
|
const resolvedMemory = memory ?? {};
|
|
141
111
|
const runId = await runStore.start({
|
|
142
|
-
|
|
112
|
+
virtualUserId: virtualUser.id,
|
|
143
113
|
disposition,
|
|
144
114
|
seed,
|
|
145
115
|
goals: resolvedGoals,
|
|
@@ -148,7 +118,7 @@ export const startVirtualUserRun = async ({ store, personas, config, environment
|
|
|
148
118
|
});
|
|
149
119
|
return {
|
|
150
120
|
runId,
|
|
151
|
-
|
|
121
|
+
virtualUserId: virtualUser.id,
|
|
152
122
|
disposition,
|
|
153
123
|
seed,
|
|
154
124
|
goals: resolvedGoals,
|
|
@@ -164,7 +134,7 @@ export const startVirtualUserRun = async ({ store, personas, config, environment
|
|
|
164
134
|
*/
|
|
165
135
|
export const serializeVirtualUserRun = (run) => ({
|
|
166
136
|
runId: run.runId,
|
|
167
|
-
|
|
137
|
+
virtualUserId: run.virtualUserId,
|
|
168
138
|
disposition: run.disposition,
|
|
169
139
|
seed: run.seed,
|
|
170
140
|
status: run.status,
|
|
@@ -212,10 +182,10 @@ export const serializeVirtualUserSteps = (steps) => steps.map((step) => ({
|
|
|
212
182
|
* takes; the engine's own duration also accepts `'30m'`, which nothing on this
|
|
213
183
|
* side ever writes.
|
|
214
184
|
*/
|
|
215
|
-
export const serializeVirtualUserSchedule = (schedule,
|
|
216
|
-
const
|
|
185
|
+
export const serializeVirtualUserSchedule = (schedule, virtualUsers) => {
|
|
186
|
+
const virtualUser = virtualUsers[schedule.virtualUserId];
|
|
217
187
|
return {
|
|
218
|
-
|
|
188
|
+
virtualUserId: schedule.virtualUserId,
|
|
219
189
|
enabled: schedule.enabled,
|
|
220
190
|
disposition: schedule.disposition,
|
|
221
191
|
goals: schedule.goals,
|
|
@@ -234,36 +204,36 @@ export const serializeVirtualUserSchedule = (schedule, personas) => {
|
|
|
234
204
|
lastRunId: schedule.lastRunId,
|
|
235
205
|
lastRunAt: schedule.lastRunAt ? schedule.lastRunAt.toISOString() : null,
|
|
236
206
|
declared: {
|
|
237
|
-
disposition: (
|
|
207
|
+
disposition: (virtualUser?.disposition ??
|
|
238
208
|
'realistic'),
|
|
239
|
-
goals:
|
|
209
|
+
goals: virtualUser?.goals ?? [],
|
|
240
210
|
},
|
|
241
211
|
};
|
|
242
212
|
};
|
|
243
213
|
/**
|
|
244
|
-
* Writes a
|
|
214
|
+
* Writes a virtual user's cadence.
|
|
245
215
|
*
|
|
246
216
|
* Applies the same rules `startVirtualUserRun` enforces, at the point the row is
|
|
247
|
-
* written rather than every hour afterwards: an acted-upon
|
|
248
|
-
* session, and
|
|
249
|
-
*
|
|
217
|
+
* written rather than every hour afterwards: an acted-upon virtual user has no
|
|
218
|
+
* session, and production refuses every run, so either is a tick that can only
|
|
219
|
+
* ever fail to start, with nobody watching.
|
|
250
220
|
*
|
|
251
|
-
*
|
|
252
|
-
* keeps what it had: enabling a row saved earlier as
|
|
253
|
-
*
|
|
221
|
+
* Whether the row is enabled is the one thing checked, since a field left out
|
|
222
|
+
* keeps what it had: enabling a row saved earlier is refused just as saving a
|
|
223
|
+
* new one would be.
|
|
254
224
|
*/
|
|
255
|
-
export const writeVirtualUserSchedule = async ({ store,
|
|
225
|
+
export const writeVirtualUserSchedule = async ({ store, virtualUsers, virtualUserId, enabled, disposition, goals, budget, minIntervalMs, maxIntervalMs, nextRunAt, config, environments, environment = readEnvVariable('PIKKU_ENV'), }) => {
|
|
256
226
|
const scheduleStore = requireVirtualUserScheduleStore(store);
|
|
257
|
-
const declared =
|
|
227
|
+
const declared = runnableVirtualUser(virtualUsers, virtualUserId);
|
|
258
228
|
const current = enabled === undefined || disposition === undefined
|
|
259
|
-
? await scheduleStore.get(
|
|
229
|
+
? await scheduleStore.get(virtualUserId)
|
|
260
230
|
: null;
|
|
261
231
|
const created = disposition === undefined && !current ? declared.disposition : undefined;
|
|
262
232
|
if (enabled ?? current?.enabled) {
|
|
263
|
-
refuseInProduction(
|
|
233
|
+
refuseInProduction(virtualUserId, config, environments, environment);
|
|
264
234
|
}
|
|
265
235
|
return scheduleStore.set({
|
|
266
|
-
|
|
236
|
+
virtualUserId,
|
|
267
237
|
enabled,
|
|
268
238
|
disposition: (disposition ?? created),
|
|
269
239
|
goals,
|
|
@@ -283,7 +253,7 @@ export const writeVirtualUserSchedule = async ({ store, personas, persona, enabl
|
|
|
283
253
|
};
|
|
284
254
|
/** What a due schedule asks `runVirtualUser` for. */
|
|
285
255
|
export const virtualUserScheduleRunInput = (schedule) => ({
|
|
286
|
-
|
|
256
|
+
virtualUserId: schedule.virtualUserId,
|
|
287
257
|
disposition: schedule.disposition,
|
|
288
258
|
goals: schedule.goals,
|
|
289
259
|
budget: schedule.budget
|
|
@@ -300,28 +270,28 @@ export const virtualUserScheduleRunInput = (schedule) => ({
|
|
|
300
270
|
* What a tick did.
|
|
301
271
|
*
|
|
302
272
|
* Logged rather than returned: the caller is a cron, and a run this started is
|
|
303
|
-
* otherwise the only trace that a
|
|
273
|
+
* otherwise the only trace that a virtual user is still out there working.
|
|
304
274
|
*/
|
|
305
275
|
export const logVirtualUserTick = (logger, result) => {
|
|
306
|
-
for (const {
|
|
307
|
-
logger.info(`Virtual user ${
|
|
276
|
+
for (const { virtualUserId, runId } of result.dispatched) {
|
|
277
|
+
logger.info(`Virtual user ${virtualUserId} started run ${runId} on schedule`);
|
|
308
278
|
}
|
|
309
279
|
for (const runId of result.reaped) {
|
|
310
|
-
logger.warn(`Virtual user run ${runId} was abandoned — marked failed so its
|
|
280
|
+
logger.warn(`Virtual user run ${runId} was abandoned — marked failed so its virtual user can run again`);
|
|
311
281
|
}
|
|
312
|
-
for (const {
|
|
313
|
-
logger.info(`Virtual user ${
|
|
282
|
+
for (const { virtualUserId, reason } of result.skipped) {
|
|
283
|
+
logger.info(`Virtual user ${virtualUserId} skipped this tick: ${reason}`);
|
|
314
284
|
}
|
|
315
285
|
};
|
|
316
286
|
/**
|
|
317
287
|
* The run itself.
|
|
318
288
|
*
|
|
319
289
|
* Everything it needs is derived through `metaService` and the generated
|
|
320
|
-
*
|
|
290
|
+
* virtual users — the same public surface any consumer has. Nothing reaches into
|
|
321
291
|
* pikku's internals, because an app could not, and a feature built on what only
|
|
322
292
|
* the framework can see would not be this feature.
|
|
323
293
|
*/
|
|
324
|
-
export const executeVirtualUserRun = async ({ runStore, metaService, agentRunner, variables, logger,
|
|
294
|
+
export const executeVirtualUserRun = async ({ runStore, metaService, agentRunner, variables, logger, virtualUsers, createVirtualUsers, runId, virtualUserId, disposition, goals, memory, seed, budget, auth, }) => {
|
|
325
295
|
const store = requireVirtualUserRunStore(runStore);
|
|
326
296
|
try {
|
|
327
297
|
if (!metaService) {
|
|
@@ -332,20 +302,11 @@ export const executeVirtualUserRun = async ({ runStore, metaService, agentRunner
|
|
|
332
302
|
}
|
|
333
303
|
const apiUrl = await variables.get(VIRTUAL_USER_VARIABLES.apiUrl);
|
|
334
304
|
if (!apiUrl) {
|
|
335
|
-
throw new Error(`${VIRTUAL_USER_VARIABLES.apiUrl} is not set
|
|
305
|
+
throw new Error(`${VIRTUAL_USER_VARIABLES.apiUrl} is not set, so the virtual user has no address to drive. ` +
|
|
306
|
+
`Set it to the URL this app is reached at, including any HTTP prefix (for example https://my-app.example.com/api). \`pikku dev\` sets it for you.`);
|
|
336
307
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// secret is the local-only fallback, because only `pikku dev` serves the
|
|
340
|
-
// endpoint that accepts it.
|
|
341
|
-
const token = operatorToken ??
|
|
342
|
-
(await variables.get(VIRTUAL_USER_VARIABLES.operatorToken));
|
|
343
|
-
const secret = token
|
|
344
|
-
? undefined
|
|
345
|
-
: await variables.get(VIRTUAL_USER_VARIABLES.secret);
|
|
346
|
-
if (!token && !secret) {
|
|
347
|
-
throw new Error(`Neither an operator token nor ${VIRTUAL_USER_VARIABLES.secret} is available — there is nobody for the virtual user to be. ` +
|
|
348
|
-
`Hand a Fabric operator token in with the run against a deployed stage, or export ${VIRTUAL_USER_VARIABLES.secret} against a local \`pikku dev\` target.`);
|
|
308
|
+
if (!auth) {
|
|
309
|
+
throw new Error('The app has no auth service, so the virtual user has no way to sign in. Register better-auth (with the pikkuVirtualUser plugin) as the auth singleton service.');
|
|
349
310
|
}
|
|
350
311
|
const model = await variables.get(VIRTUAL_USER_VARIABLES.model);
|
|
351
312
|
if (!model) {
|
|
@@ -358,40 +319,31 @@ export const executeVirtualUserRun = async ({ runStore, metaService, agentRunner
|
|
|
358
319
|
...new Set(Object.values(functionsMeta).flatMap((meta) => [meta.inputSchemaName, meta.outputSchemaName].filter((name) => !!name))),
|
|
359
320
|
];
|
|
360
321
|
const schemas = (await metaService.getSchemas(schemaNames));
|
|
361
|
-
const
|
|
362
|
-
if (!
|
|
363
|
-
throw new Error(`
|
|
322
|
+
const virtualUser = virtualUsers[virtualUserId];
|
|
323
|
+
if (!virtualUser) {
|
|
324
|
+
throw new Error(`VirtualUser "${virtualUserId}" is no longer declared`);
|
|
364
325
|
}
|
|
365
326
|
const { catalogue, intents, agents } = prepareVirtualUserRun({
|
|
366
|
-
|
|
327
|
+
virtualUser,
|
|
367
328
|
functionsMeta,
|
|
368
329
|
schemas,
|
|
369
330
|
workflowsMeta: await metaService.getWorkflowMeta(),
|
|
370
331
|
systemRoles: await metaService.getSystemRolesMeta(),
|
|
371
332
|
agentsMeta: await metaService.getAgentsMeta(),
|
|
372
333
|
});
|
|
373
|
-
const
|
|
374
|
-
const signedIn = createPersonas({
|
|
334
|
+
const signedIn = createVirtualUsers({
|
|
375
335
|
apiUrl,
|
|
376
|
-
|
|
377
|
-
? {
|
|
378
|
-
operator: {
|
|
379
|
-
token,
|
|
380
|
-
signInPath: signInPathFor(configuredSignInPath, 'fabric'),
|
|
381
|
-
},
|
|
382
|
-
}
|
|
383
|
-
: { secret }),
|
|
336
|
+
signIn: inProcessSignIn(auth),
|
|
384
337
|
model,
|
|
385
|
-
signInPath: signInPathFor(configuredSignInPath, 'actor'),
|
|
386
338
|
rpcPath: (await variables.get(VIRTUAL_USER_VARIABLES.rpcPath)) ?? undefined,
|
|
387
339
|
});
|
|
388
|
-
const target = signedIn[
|
|
340
|
+
const target = signedIn[virtualUserId];
|
|
389
341
|
if (!target) {
|
|
390
|
-
throw new Error(`
|
|
342
|
+
throw new Error(`VirtualUser "${virtualUserId}" cannot sign in`);
|
|
391
343
|
}
|
|
392
344
|
const result = await runVirtualUserEngine({
|
|
393
|
-
|
|
394
|
-
|
|
345
|
+
virtualUser,
|
|
346
|
+
virtualUserId,
|
|
395
347
|
disposition: disposition,
|
|
396
348
|
catalogue,
|
|
397
349
|
intents,
|
|
@@ -399,11 +351,11 @@ export const executeVirtualUserRun = async ({ runStore, metaService, agentRunner
|
|
|
399
351
|
memory,
|
|
400
352
|
seed,
|
|
401
353
|
agents,
|
|
402
|
-
target:
|
|
354
|
+
target: virtualUserTarget(target, {
|
|
403
355
|
model,
|
|
404
356
|
agents: agents.map((agent) => agent.name),
|
|
405
357
|
}),
|
|
406
|
-
// `AgentRunnerService.run` IS the engine's `
|
|
358
|
+
// `AgentRunnerService.run` IS the engine's `VirtualUserLLM` — same params, same
|
|
407
359
|
// result — so a virtual user thinks through the same runner every agent in
|
|
408
360
|
// the app does, provider quirks and all.
|
|
409
361
|
llm: (params) => agentRunner.run(params),
|
|
@@ -429,7 +381,7 @@ export const executeVirtualUserRun = async ({ runStore, metaService, agentRunner
|
|
|
429
381
|
// record is the only place that distinction survives — leaving it at
|
|
430
382
|
// 'running' forever is what `fail` exists to prevent.
|
|
431
383
|
const message = error instanceof Error ? error.message : String(error);
|
|
432
|
-
logger.error(`Virtual user run ${runId} (${
|
|
384
|
+
logger.error(`Virtual user run ${runId} (${virtualUserId}) failed: ${message}`);
|
|
433
385
|
await store.fail(runId, message);
|
|
434
386
|
throw error;
|
|
435
387
|
}
|