@pikku/core 0.12.141 → 0.12.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/errors/errors.d.ts +1 -1
  3. package/dist/errors/errors.js +1 -1
  4. package/dist/function/function-meta.types.d.ts +6 -6
  5. package/dist/function/functions.types.d.ts +2 -2
  6. package/dist/services/audit-service.d.ts +4 -4
  7. package/dist/services/feature-flag-service.d.ts +3 -3
  8. package/dist/services/{http-personas.d.ts → http-virtual-users.d.ts} +25 -47
  9. package/dist/services/{http-personas.js → http-virtual-users.js} +29 -53
  10. package/dist/services/in-memory-queue-service.d.ts +8 -1
  11. package/dist/services/in-memory-queue-service.js +67 -0
  12. package/dist/services/incoming-webhook-service.d.ts +2 -0
  13. package/dist/services/incoming-webhook-service.js +4 -0
  14. package/dist/services/index.d.ts +2 -2
  15. package/dist/services/index.js +1 -1
  16. package/dist/services/meta-service.d.ts +6 -6
  17. package/dist/services/meta-service.js +8 -8
  18. package/dist/{wirings/workflow → services}/scenario-cookie-jar.d.ts +2 -0
  19. package/dist/{wirings/workflow → services}/scenario-cookie-jar.js +18 -14
  20. package/dist/services/scheduler-service.d.ts +3 -0
  21. package/dist/services/scheduler-service.js +4 -0
  22. package/dist/services/system-role-guard.d.ts +1 -1
  23. package/dist/services/virtual-user-sign-in.d.ts +52 -0
  24. package/dist/services/virtual-user-sign-in.js +80 -0
  25. package/dist/services/virtual-user-statement.d.ts +31 -0
  26. package/dist/services/virtual-user-statement.js +24 -0
  27. package/dist/services/{personas-service.d.ts → virtual-users-service.d.ts} +25 -23
  28. package/dist/services/{personas-service.js → virtual-users-service.js} +3 -3
  29. package/dist/types/core.types.d.ts +11 -11
  30. package/dist/wirings/queue/index.d.ts +1 -1
  31. package/dist/wirings/queue/queue.types.d.ts +21 -0
  32. package/dist/wirings/queue/signed-queue-service.d.ts +4 -1
  33. package/dist/wirings/queue/signed-queue-service.js +3 -0
  34. package/dist/wirings/role/define-system-role.d.ts +1 -1
  35. package/dist/wirings/role/define-system-role.js +1 -1
  36. package/dist/wirings/scheduler/index.d.ts +1 -1
  37. package/dist/wirings/scheduler/scheduler.types.d.ts +12 -0
  38. package/dist/wirings/{actor-flow/actor-flow.types.d.ts → virtual-user/conversation/conversation.types.d.ts} +5 -5
  39. package/dist/wirings/virtual-user/conversation/index.d.ts +2 -0
  40. package/dist/wirings/virtual-user/conversation/run-conversation.d.ts +36 -0
  41. package/dist/wirings/{actor-flow → virtual-user/conversation}/run-conversation.js +30 -30
  42. package/dist/wirings/virtual-user/define-virtual-users.d.ts +32 -0
  43. package/dist/wirings/{persona/define-personas.js → virtual-user/define-virtual-users.js} +9 -9
  44. package/dist/wirings/virtual-user/index.d.ts +30 -7
  45. package/dist/wirings/virtual-user/index.js +23 -4
  46. package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +8 -8
  47. package/dist/wirings/virtual-user/prepare-virtual-user-run.js +9 -9
  48. package/dist/wirings/virtual-user/run-virtual-user.d.ts +6 -6
  49. package/dist/wirings/virtual-user/run-virtual-user.js +10 -10
  50. package/dist/wirings/{persona/validate-personas.d.ts → virtual-user/validate-virtual-users.d.ts} +11 -11
  51. package/dist/wirings/{persona/validate-personas.js → virtual-user/validate-virtual-users.js} +24 -24
  52. package/dist/wirings/virtual-user/virtual-user-agents.d.ts +7 -7
  53. package/dist/wirings/virtual-user/virtual-user-agents.js +2 -2
  54. package/dist/wirings/{persona/persona-app-scopes.d.ts → virtual-user/virtual-user-app-scopes.d.ts} +10 -10
  55. package/dist/wirings/{persona/persona-app-scopes.js → virtual-user/virtual-user-app-scopes.js} +12 -12
  56. package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +3 -3
  57. package/dist/wirings/virtual-user/virtual-user-catalogue.js +2 -2
  58. package/dist/wirings/{persona/persona.types.d.ts → virtual-user/virtual-user-declaration.types.d.ts} +24 -24
  59. package/dist/wirings/virtual-user/virtual-user-derive.js +13 -7
  60. package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +1 -1
  61. package/dist/wirings/{persona/persona-email.d.ts → virtual-user/virtual-user-email.d.ts} +11 -11
  62. package/dist/wirings/{persona/persona-email.js → virtual-user/virtual-user-email.js} +19 -19
  63. package/dist/wirings/{persona/persona-environments.d.ts → virtual-user/virtual-user-environments.d.ts} +10 -10
  64. package/dist/wirings/{persona/persona-environments.js → virtual-user/virtual-user-environments.js} +21 -21
  65. package/dist/wirings/virtual-user/virtual-user-intents.d.ts +3 -3
  66. package/dist/wirings/virtual-user/virtual-user-intents.js +3 -3
  67. package/dist/wirings/{persona/persona-mailbox.d.ts → virtual-user/virtual-user-mailbox.d.ts} +4 -4
  68. package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +4 -4
  69. package/dist/wirings/virtual-user/virtual-user-scaffold.d.ts +42 -61
  70. package/dist/wirings/virtual-user/virtual-user-scaffold.js +69 -117
  71. package/dist/wirings/virtual-user/virtual-user-schedule-store.d.ts +15 -15
  72. package/dist/wirings/virtual-user/virtual-user-schedule.d.ts +6 -6
  73. package/dist/wirings/virtual-user/virtual-user-schedule.js +11 -11
  74. package/dist/wirings/virtual-user/virtual-user-target.d.ts +6 -6
  75. package/dist/wirings/virtual-user/virtual-user-target.js +5 -5
  76. package/dist/wirings/virtual-user/virtual-user.types.d.ts +11 -11
  77. package/dist/wirings/virtual-user/virtual-user.types.js +1 -1
  78. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +9 -9
  79. package/dist/wirings/workflow/pikku-scenario-service.d.ts +28 -28
  80. package/dist/wirings/workflow/pikku-scenario-service.js +77 -77
  81. package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -2
  82. package/dist/wirings/workflow/pikku-workflow-service.js +4 -4
  83. package/dist/wirings/workflow/scenario-prose.d.ts +8 -8
  84. package/dist/wirings/workflow/scenario-prose.js +12 -8
  85. package/dist/wirings/workflow/scenario-run.types.d.ts +13 -13
  86. package/dist/wirings/workflow/scenario-step.types.d.ts +23 -23
  87. package/dist/wirings/workflow/workflow.types.d.ts +2 -2
  88. package/knowledge/decisions/index.md +0 -2
  89. package/knowledge/decisions/internals/a-credential-name-declared-twice-must-agree-on-schema-and-type.md +29 -0
  90. package/knowledge/decisions/internals/a-grant-can-name-a-scope-declared-without-its-addon.md +29 -0
  91. package/knowledge/decisions/internals/a-latency-budget-is-asserted-by-its-cause.md +32 -0
  92. package/knowledge/decisions/internals/a-scheduled-task-writes-its-caller-session-before-it-runs.md +22 -0
  93. package/knowledge/decisions/internals/a-secret-set-in-memory-shadows-the-variable-of-the-same-name.md +28 -0
  94. package/knowledge/decisions/internals/a-virtual-user-acts-through-invokeraw-so-a-refusal-is-data.md +32 -0
  95. package/knowledge/decisions/internals/a-virtual-user-cadence-is-a-row-not-a-timer.md +12 -12
  96. package/knowledge/decisions/internals/a-virtual-user-converse-signs-in-before-its-first-turn.md +24 -0
  97. package/knowledge/decisions/internals/a-virtual-user-email-is-computed-with-the-run-id-as-a-sub-address.md +34 -0
  98. package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-but-it-needs-a-trigger.md +2 -2
  99. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +3 -0
  100. package/knowledge/decisions/internals/an-agent-is-offered-under-its-registry-key-and-only-within-the-virtual-users-scopes.md +27 -0
  101. package/knowledge/decisions/internals/an-oauth2-credential-implies-an-optional-app-secret.md +26 -0
  102. package/knowledge/decisions/internals/analytics-identity-resolvers-run-in-declared-order.md +22 -0
  103. package/knowledge/decisions/internals/feature-flag-declarations-refuse-an-empty-anyof-and-conflicting-scopes.md +22 -0
  104. package/knowledge/decisions/internals/feature-flags-fail-open-and-never-enforce-access.md +28 -0
  105. package/knowledge/decisions/internals/flag-sync-rewrites-only-the-declared-text.md +24 -0
  106. package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +3 -3
  107. package/knowledge/decisions/internals/index.md +47 -31
  108. package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +10 -10
  109. package/knowledge/decisions/internals/role-names-cannot-contain-the-scope-separator.md +30 -0
  110. package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +23 -19
  111. package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +2 -2
  112. package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +12 -12
  113. package/knowledge/decisions/internals/the-core-scope-validator-refuses-separator-wildcard-and-empty-segments.md +22 -0
  114. package/knowledge/decisions/internals/the-pikku-user-id-resolves-in-a-fixed-order.md +24 -0
  115. 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
  116. 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
  117. package/knowledge/decisions/internals/validate-checks-virtual-users-through-a-shared-module.md +43 -0
  118. 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
  119. package/knowledge/decisions/internals/virtual-user-conversation-verdicts-are-llm-self-evaluations.md +25 -0
  120. package/knowledge/decisions/internals/virtual-user-conversations-seed-a-hidden-kickoff-message.md +23 -0
  121. package/knowledge/decisions/internals/virtual-users-are-declared-in-one-definevirtualusers-call-per-codebase.md +30 -0
  122. package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +5 -5
  123. package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +2 -2
  124. package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +4 -4
  125. package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
  126. package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +2 -2
  127. package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +1 -1
  128. package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +1 -1
  129. package/knowledge/decisions/security/a-bearer-token-checked-against-a-secret-reads-it-per-request.md +26 -0
  130. package/knowledge/decisions/security/a-call-without-a-session-raises-missing-session-not-forbidden.md +20 -0
  131. package/knowledge/decisions/security/a-caller-cookie-wins-over-the-jar-for-the-same-name.md +23 -0
  132. package/knowledge/decisions/security/a-channel-lifecycle-acts-as-the-socket-upgrade-session.md +16 -0
  133. package/knowledge/decisions/security/a-channel-requires-a-session-unless-it-says-auth-false.md +26 -0
  134. package/knowledge/decisions/security/a-credential-read-path-follows-its-declared-type-not-absence.md +32 -0
  135. package/knowledge/decisions/security/a-credential-type-is-a-literal-the-inspector-checks-at-declaration.md +27 -0
  136. package/knowledge/decisions/security/a-first-party-cookie-is-written-only-after-its-purposes-consent.md +33 -0
  137. package/knowledge/decisions/security/a-readonly-session-reaches-only-readonly-functions.md +25 -0
  138. package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md +10 -5
  139. package/knowledge/decisions/security/a-scheduled-virtual-user-run-needs-no-admin-at-run-time.md +27 -0
  140. package/knowledge/decisions/security/a-scoped-credential-view-is-limited-to-declared-names.md +26 -0
  141. package/knowledge/decisions/security/a-scoped-secret-view-reads-only-declared-keys-and-never-writes.md +25 -0
  142. package/knowledge/decisions/security/a-secret-is-sent-only-to-the-hosts-it-declares.md +29 -0
  143. package/knowledge/decisions/security/a-step-can-name-an-identity-no-virtual-user-holds-through-per-call-headers.md +25 -0
  144. package/knowledge/decisions/security/a-stopped-run-by-id-skips-the-ownership-check.md +24 -0
  145. package/knowledge/decisions/security/a-vault-secret-redacts-on-serialization-and-throws-on-coercion.md +29 -0
  146. package/knowledge/decisions/security/a-virtual-user-catalogue-entry-is-filtered-by-scope-and-approval-is-denied-by-default.md +30 -0
  147. package/knowledge/decisions/security/a-virtual-user-environment-fails-closed.md +38 -0
  148. package/knowledge/decisions/security/a-virtual-user-never-runs-against-production.md +23 -0
  149. package/knowledge/decisions/security/a-virtual-user-reads-its-roles-from-the-apps-own-rpc-before-better-auth.md +27 -0
  150. package/knowledge/decisions/security/a-virtual-user-run-treats-an-unresolved-environment-as-production.md +41 -0
  151. package/knowledge/decisions/security/a-virtual-user-scopes-are-expanded-from-its-roles-once-at-run-preparation.md +25 -0
  152. package/knowledge/decisions/security/a-virtual-user-whose-roles-drift-from-the-declaration-refuses-to-run.md +32 -0
  153. package/knowledge/decisions/security/addon-screen-scopes-hide-and-do-not-enforce.md +20 -0
  154. package/knowledge/decisions/security/an-addon-header-can-only-narrow-the-session.md +26 -0
  155. package/knowledge/decisions/security/an-addon-instance-reads-only-the-secrets-and-credentials-it-is-named.md +33 -0
  156. package/knowledge/decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md +16 -0
  157. package/knowledge/decisions/security/an-api-key-is-jwt-decoded-into-the-session.md +26 -0
  158. package/knowledge/decisions/security/an-external-virtual-user-caller-presents-one-signed-statement.md +38 -0
  159. package/knowledge/decisions/security/an-inbound-webhook-source-dispatches-only-verified-requests.md +31 -0
  160. package/knowledge/decisions/security/an-mcp-call-with-no-http-request-stays-anonymous.md +18 -0
  161. package/knowledge/decisions/security/an-omitted-user-id-addresses-the-platform-credential.md +32 -0
  162. package/knowledge/decisions/security/analytics-identity-is-stamped-from-the-session-not-the-event.md +24 -0
  163. package/knowledge/decisions/security/app-access-is-a-scope-tree-synthesised-from-virtual-users.md +26 -0
  164. package/knowledge/decisions/security/approval-policy-decides-who-answers-a-tool-call.md +27 -0
  165. package/knowledge/decisions/security/auth-false-on-a-session-function-warns-and-still-requires-one.md +25 -0
  166. package/knowledge/decisions/security/bearer-token-hashes-are-unpeppered-sha256.md +18 -0
  167. package/knowledge/decisions/security/consent-is-read-from-a-cookie-and-a-recognised-denial-counts-as-refusal.md +29 -0
  168. package/knowledge/decisions/security/cors-defaults-to-any-origin-and-refuses-the-wildcard-with-credentials.md +22 -0
  169. package/knowledge/decisions/security/credential-backends-share-one-conformance-suite-for-per-user-isolation.md +29 -0
  170. package/knowledge/decisions/security/each-stage-has-one-virtual-user-key-pair.md +27 -0
  171. package/knowledge/decisions/security/hmac-verification-fails-closed-on-empty-inputs.md +25 -0
  172. package/knowledge/decisions/security/index.md +88 -24
  173. package/knowledge/decisions/security/local-content-refuses-private-buckets-under-the-public-segment.md +21 -0
  174. package/knowledge/decisions/security/mail-is-allowlisted-before-a-virtual-user-run-sees-it.md +38 -0
  175. package/knowledge/decisions/security/mcp-resources-tools-and-prompts-take-sessionless-functions.md +18 -0
  176. package/knowledge/decisions/security/origin-locked-routes-match-the-origin-exactly.md +22 -0
  177. package/knowledge/decisions/security/oss-auth-works-without-pikku-studio-cloud.md +26 -0
  178. package/knowledge/decisions/security/outbound-webhooks-use-an-allowlist-when-configured.md +24 -0
  179. package/knowledge/decisions/security/pikku-carries-virtual-user-scopes-as-data-and-the-app-grants-them.md +35 -0
  180. package/knowledge/decisions/security/scoped-content-access-is-a-root-folder-with-longest-prefix-grants.md +31 -0
  181. package/knowledge/decisions/security/services-without-secrets-get-a-throwing-secrets-accessor.md +22 -0
  182. package/knowledge/decisions/security/shared-guards-refuse-to-mutate-or-shadow-a-system-role.md +34 -0
  183. package/knowledge/decisions/security/signed-uploads-are-verified-by-the-same-check-as-signed-reads.md +26 -0
  184. package/knowledge/decisions/security/system-role-scopes-must-be-declared-with-define-scope.md +24 -0
  185. package/knowledge/decisions/security/system-roles-are-additive-and-immutable.md +16 -0
  186. package/knowledge/decisions/security/the-channel-rpc-does-not-send-the-callers-session.md +21 -0
  187. package/knowledge/decisions/security/the-remote-jobs-inbox-fails-closed-without-a-secret.md +24 -0
  188. package/knowledge/decisions/security/the-remote-rpc-gate-fails-closed-and-binds-the-token-to-its-function.md +34 -0
  189. package/knowledge/decisions/security/the-workflow-status-stream-rechecks-ownership-on-every-poll.md +16 -0
  190. package/knowledge/decisions/security/two-credentials-on-one-key-must-share-a-read-mode.md +29 -0
  191. package/knowledge/decisions/security/verified-bearer-claims-are-pass-through-data-that-core-never-fills.md +23 -0
  192. package/knowledge/decisions/security/virtual-user-catalogue-excludes-the-machinery-that-runs-virtual-users.md +25 -0
  193. 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
  194. package/knowledge/decisions/security/virtual-user-run-records-are-gated-behind-a-read-scope.md +18 -0
  195. package/knowledge/decisions/security/virtual-user-sign-in-does-not-exist-in-production.md +25 -0
  196. package/knowledge/decisions/security/virtual-user-sign-in-has-one-door.md +42 -0
  197. 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
  198. package/knowledge/decisions/security/virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md +29 -0
  199. package/knowledge/decisions/security/workflow-helper-functions-are-sessionless-by-declaration.md +18 -0
  200. 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
  201. package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +5 -5
  202. 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
  203. package/knowledge/decisions/security/writing-a-virtual-user-schedule-is-gated-by-its-own-scope.md +18 -0
  204. package/knowledge/index.md +0 -2
  205. package/knowledge/questions/a-bearer-jwt-with-no-identity-becomes-a-session.md +24 -0
  206. package/knowledge/questions/dev-quick-login-as-impersonation.md +25 -0
  207. package/knowledge/questions/does-a-delayed-rpc-run-as-the-session-it-was-scheduled-with.md +26 -0
  208. package/knowledge/questions/does-a-user-id-with-a-colon-read-another-users-credentials.md +27 -0
  209. package/knowledge/questions/does-a-wirecli-command-need-a-session-by-default.md +26 -0
  210. package/knowledge/questions/does-an-adversarial-in-scope-success-count-as-a-finding.md +26 -0
  211. package/knowledge/questions/does-approve-step-need-the-run-owners-session-or-the-gates-approvers.md +34 -0
  212. package/knowledge/questions/does-cors-origin-true-reflect-any-origin-with-credentials.md +20 -0
  213. package/knowledge/questions/does-skip-user-session-do-anything-on-a-channel.md +24 -0
  214. package/knowledge/questions/does-the-store-session-match-the-other-two.md +19 -0
  215. package/knowledge/questions/index.md +25 -2
  216. package/knowledge/questions/is-the-agent-score-worker-reachable-without-a-session.md +21 -0
  217. package/knowledge/questions/live-scheduled-tests-may-write.md +19 -0
  218. package/knowledge/questions/provision-virtual-users-has-no-caller-in-this-repo.md +23 -0
  219. package/knowledge/questions/remote-secret-refuses-requests-without-a-remote-token-on-every-path.md +32 -0
  220. package/knowledge/questions/should-a-middleware-set-session-persist-to-the-store.md +25 -0
  221. package/knowledge/questions/should-a-scoped-secret-view-throw-from-has-secret-for-a-disallowed-key.md +23 -0
  222. package/knowledge/questions/should-a-wrong-bearer-token-be-anonymous-or-refused.md +35 -0
  223. package/knowledge/questions/should-an-empty-webhook-signing-secret-send-unsigned.md +23 -0
  224. package/knowledge/questions/should-http-virtual-user-sessions-reset-between-scenarios-in-one-run.md +27 -0
  225. package/knowledge/questions/should-the-cookie-jar-scope-cookies-to-the-api-origin.md +26 -0
  226. package/knowledge/questions/should-voice-input-fetch-audio-urls-without-a-host-allowlist.md +20 -0
  227. package/knowledge/questions/staging-impersonation-rules.md +27 -0
  228. package/knowledge/questions/virtual-user-keys-on-cloud-stages-are-pikku-studios-to-build.md +33 -0
  229. package/knowledge/questions/virtual-user-statement-replay-is-per-process.md +20 -0
  230. package/knowledge/questions/what-an-impersonated-session-carries.md +22 -0
  231. package/package.json +2 -3
  232. package/src/public-surface.json +38 -47
  233. package/dist/services/persona-actor-secret.d.ts +0 -38
  234. package/dist/services/persona-actor-secret.js +0 -39
  235. package/dist/services/persona-sign-in.d.ts +0 -117
  236. package/dist/services/persona-sign-in.js +0 -154
  237. package/dist/wirings/actor-flow/index.d.ts +0 -2
  238. package/dist/wirings/actor-flow/run-conversation.d.ts +0 -36
  239. package/dist/wirings/persona/define-personas.d.ts +0 -32
  240. package/dist/wirings/persona/index.d.ts +0 -23
  241. package/dist/wirings/persona/index.js +0 -19
  242. package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +0 -23
  243. package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +0 -25
  244. package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +0 -43
  245. package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +0 -31
  246. package/knowledge/decisions/security/an-actor-credential-is-derived-per-persona.md +0 -42
  247. package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +0 -26
  248. /package/dist/wirings/{actor-flow/actor-flow.types.js → virtual-user/conversation/conversation.types.js} +0 -0
  249. /package/dist/wirings/{actor-flow → virtual-user/conversation}/index.js +0 -0
  250. /package/dist/wirings/{persona/persona.types.js → virtual-user/virtual-user-declaration.types.js} +0 -0
  251. /package/dist/wirings/{persona/persona-mailbox.js → virtual-user/virtual-user-mailbox.js} +0 -0
@@ -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 { personaVirtualUserTarget } from './virtual-user-target.js';
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
- * The same two variables a scenario run reads, because a virtual user signs
25
- * in and calls through exactly the doors a scenario does. An app that mounts
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 persona behind an id, refused unless it is one a run may be.
29
+ * The declared virtual user behind an id, refused unless it is one a run may be.
57
30
  *
58
- * An acted-upon persona has no session of its own, and running one would race
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 runnablePersona = (personas, personaId) => {
62
- const persona = personas[personaId];
63
- if (!persona) {
64
- throw new BadRequestError(`Unknown persona "${personaId}" — declare it with definePersonas()`);
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 (!persona.runnable) {
67
- throw new BadRequestError(`Persona "${personaId}" is declared as acted upon, never run`);
39
+ if (!virtualUser.runnable) {
40
+ throw new BadRequestError(`VirtualUser "${virtualUserId}" is declared as acted upon, never run`);
68
41
  }
69
- return persona;
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 `personaEnvironmentRefusal` already checks at
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
- // Every disposition other than this one exists to find out what the product
112
- // does wrong, which is not a thing to do to real customers' data.
113
- const refuseInProduction = (personaId, disposition, config, environments, environment) => {
114
- if (disposition !== PRODUCTION_DISPOSITION &&
115
- isProductionRun(config, environments, environment)) {
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, personas, config, environments, environment = readEnvVariable('PIKKU_ENV'), persona: personaId, disposition: requested, seed: requestedSeed, goals, memory, startedBy, }) => {
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 persona = runnablePersona(personas, personaId);
100
+ const virtualUser = runnableVirtualUser(virtualUsers, virtualUserId);
129
101
  const disposition = (requested ??
130
- persona.disposition ??
102
+ virtualUser.disposition ??
131
103
  'realistic');
132
- // Checked against the effective disposition, so an override cannot smuggle
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
- persona: persona.id,
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
- persona: persona.id,
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
- persona: run.persona,
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, personas) => {
216
- const persona = personas[schedule.persona];
185
+ export const serializeVirtualUserSchedule = (schedule, virtualUsers) => {
186
+ const virtualUser = virtualUsers[schedule.virtualUserId];
217
187
  return {
218
- persona: schedule.persona,
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: (persona?.disposition ??
207
+ disposition: (virtualUser?.disposition ??
238
208
  'realistic'),
239
- goals: persona?.goals ?? [],
209
+ goals: virtualUser?.goals ?? [],
240
210
  },
241
211
  };
242
212
  };
243
213
  /**
244
- * Writes a persona's cadence.
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 persona has no
248
- * session, and an enabled cadence whose disposition production refuses, so
249
- * either is a tick that can only ever fail to start, with nobody watching.
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
- * The disposition checked is the one the row will hold, since a field left out
252
- * keeps what it had: enabling a row saved earlier as adversarial is refused
253
- * just as saving one would be.
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, personas, persona, enabled, disposition, goals, budget, minIntervalMs, maxIntervalMs, nextRunAt, config, environments, environment = readEnvVariable('PIKKU_ENV'), }) => {
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 = runnablePersona(personas, persona);
227
+ const declared = runnableVirtualUser(virtualUsers, virtualUserId);
258
228
  const current = enabled === undefined || disposition === undefined
259
- ? await scheduleStore.get(persona)
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(persona, disposition ?? current?.disposition ?? created ?? 'realistic', config, environments, environment);
233
+ refuseInProduction(virtualUserId, config, environments, environment);
264
234
  }
265
235
  return scheduleStore.set({
266
- persona,
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
- persona: schedule.persona,
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 persona is still out there working.
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 { persona, runId } of result.dispatched) {
307
- logger.info(`Virtual user ${persona} started run ${runId} on schedule`);
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 persona can run again`);
280
+ logger.warn(`Virtual user run ${runId} was abandoned — marked failed so its virtual user can run again`);
311
281
  }
312
- for (const { persona, reason } of result.skipped) {
313
- logger.info(`Virtual user ${persona} skipped this tick: ${reason}`);
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
- * personas — the same public surface any consumer has. Nothing reaches into
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, personas, createPersonas, runId, persona: personaId, disposition, goals, memory, seed, budget, operatorToken, }) => {
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 — a virtual user has no address to sign in at.`);
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
- // An operator token wins wherever one is available: it is asymmetric, and
338
- // it does not need the target to hold a shared secret at all. The actor
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 persona = personas[personaId];
362
- if (!persona) {
363
- throw new Error(`Persona "${personaId}" is no longer declared`);
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
- persona,
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 configuredSignInPath = (await variables.get(VIRTUAL_USER_VARIABLES.signInPath)) ?? undefined;
374
- const signedIn = createPersonas({
334
+ const signedIn = createVirtualUsers({
375
335
  apiUrl,
376
- ...(token
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[personaId];
340
+ const target = signedIn[virtualUserId];
389
341
  if (!target) {
390
- throw new Error(`Persona "${personaId}" cannot sign in`);
342
+ throw new Error(`VirtualUser "${virtualUserId}" cannot sign in`);
391
343
  }
392
344
  const result = await runVirtualUserEngine({
393
- persona,
394
- personaId,
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: personaVirtualUserTarget(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 `ActorLLM` — same params, same
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} (${personaId}) failed: ${message}`);
384
+ logger.error(`Virtual user run ${runId} (${virtualUserId}) failed: ${message}`);
433
385
  await store.fail(runId, message);
434
386
  throw error;
435
387
  }
@@ -1,20 +1,20 @@
1
1
  import type { VirtualUserBudget, VirtualUserDisposition } from './virtual-user.types.js';
2
2
  /**
3
- * One persona's standing instruction to keep using the app.
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 persona hits over a fortnight, and that is
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 persona silently stops. Something
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
- persona: string;
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 persona that
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 persona is next allowed to run. The whole schedule, in one field. */
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
- persona: string;
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 persona's cadence. Returns the row as it now stands. */
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(persona: string): Promise<VirtualUserScheduleRecord | null>;
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 persona's next run out, and records which run this was.
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 persona that is failing to start should not be
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 persona, not just how it records
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 persona to whoever got there first.
80
+ * loser is told `false` and leaves the virtual user to whoever got there first.
81
81
  */
82
- claim(persona: string, 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(persona: string): Promise<void>;
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
- * persona's schedule for good. Twice the longest duration budget anyone sets in
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 persona should next appear, drawn from its own interval.
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
- persona: string;
38
+ virtualUserId: string;
39
39
  runId: string;
40
40
  }[];
41
41
  skipped: {
42
- persona: string;
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 personas are due, once.
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 persona is skipped, not queued, while its previous run is still going. Two
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
- * persona's schedule for good. Twice the longest duration budget anyone sets in
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 persona should next appear, drawn from its own interval.
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 personas are due, once.
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 persona is skipped, not queued, while its previous run is still going. Two
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({ persona: schedule.persona, limit: 1 });
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({ persona: schedule.persona, reason: 'in-flight' });
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.persona, {
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
- persona: schedule.persona,
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
- persona: schedule.persona,
87
+ virtualUserId: schedule.virtualUserId,
88
88
  reason: 'dispatch-failed',
89
89
  });
90
90
  continue;
91
91
  }
92
- await schedules.claim(schedule.persona, {
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({ persona: schedule.persona, runId });
98
+ result.dispatched.push({ virtualUserId: schedule.virtualUserId, runId });
99
99
  }
100
100
  return result;
101
101
  };
@@ -1,15 +1,15 @@
1
- import type { ScenarioPersona } from '../../services/personas-service.js';
1
+ import type { ScenarioVirtualUser } from '../../services/virtual-users-service.js';
2
2
  import type { VirtualUserTarget } from './virtual-user.types.js';
3
- export interface PersonaTargetOptions {
4
- /** Model the persona uses for its side of a conversation with an agent. */
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 a signed-in persona.
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 persona already signs
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 personaVirtualUserTarget: (persona: ScenarioPersona, { model, agents }?: PersonaTargetOptions) => VirtualUserTarget;
21
+ export declare const virtualUserTarget: (virtualUser: ScenarioVirtualUser, { model, agents }?: VirtualUserTargetOptions) => VirtualUserTarget;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Drive a virtual user through a signed-in persona.
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 persona already signs
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 personaVirtualUserTarget = (persona, { model, agents } = {}) => ({
14
- call: (rpcName, args) => persona.invokeRaw(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 persona.converse({
24
+ return virtualUser.converse({
25
25
  agent,
26
26
  task,
27
27
  // A user asking for something judges the answer by whether they got