@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,154 +0,0 @@
1
- import { deriveActorSecret } from './persona-actor-secret.js';
2
- import { PikkuError } from '../errors/error-handler.js';
3
- /**
4
- * The header `resolveImpersonatedSession` reads the target user id from.
5
- *
6
- * A wire value rather than a shared import: the reader lives in
7
- * `@pikku/services-better-auth`, which depends on core, so core cannot import
8
- * it back. The two agree by protocol, the way an HTTP header always does.
9
- */
10
- export const IMPERSONATE_USER_ID_HEADER = 'x-pikku-impersonate-user-id';
11
- /**
12
- * A sign-in the target refused. `PikkuError`, not `Error`, so the CLI prints
13
- * this message alone: an expired token or a persona the stage has never seen is
14
- * something to go and fix, and a stack trace through the fetch internals only
15
- * buries the status and the body that say which one it is.
16
- */
17
- const failed = async (what, personaId, res) => {
18
- const body = (await res.text().catch(() => '')).slice(0, 300);
19
- return new PikkuError(`[scenario] ${what} failed for '${personaId}' (${res.status}): ${body}`);
20
- };
21
- /**
22
- * Sign a persona in through the Better Auth actor plugin — the local-development
23
- * path.
24
- *
25
- * `POST /auth/sign-in/actor` upserts an `actor: true` row and mints a session
26
- * for it. Passwordless by design and refused for any row not carrying that flag,
27
- * so the secret can never reach a real user's account; the plugin still declines
28
- * to serve the endpoint at all outside `pikku dev`.
29
- *
30
- * What is presented is the persona's own credential, derived from the root and
31
- * bound to their address. A run driving many personas holds the root and
32
- * derives as it goes; a run entitled to one persona is handed that one value
33
- * through a resolver and can sign in as nobody else.
34
- */
35
- export class ActorSignIn {
36
- apiUrl;
37
- secret;
38
- signInPath;
39
- constructor(apiUrl, secret, signInPath) {
40
- this.apiUrl = apiUrl;
41
- this.secret = secret;
42
- this.signInPath = signInPath;
43
- }
44
- async login(jar, persona) {
45
- const secret = typeof this.secret === 'function'
46
- ? await this.secret(persona)
47
- : await deriveActorSecret(this.secret, persona.email);
48
- const res = await jar.fetch(`${this.apiUrl}${this.signInPath}`, {
49
- method: 'POST',
50
- headers: { 'content-type': 'application/json' },
51
- body: JSON.stringify({
52
- email: persona.email,
53
- name: persona.name,
54
- secret,
55
- }),
56
- });
57
- if (!res.ok) {
58
- throw await failed('persona sign-in', persona.id, res);
59
- }
60
- // What proves a session was established is this response setting a cookie,
61
- // not the jar being non-empty — the target may have set one earlier.
62
- if (res.headers.getSetCookie().length === 0) {
63
- throw new Error(`[scenario] persona sign-in for '${persona.id}' returned no session cookie`);
64
- }
65
- }
66
- headers() {
67
- return {};
68
- }
69
- }
70
- /**
71
- * The auth mount a configured sign-in path sits under, or `undefined` when it
72
- * names nothing recognisable.
73
- *
74
- * better-auth serves sign-in, operator sign-in and `get-session` from one
75
- * prefix, so an app that mounts it at `/api/auth` moves all three together and
76
- * says so once through `signInPath`. Reading the other two from a hardcoded
77
- * `/auth` on such an app 404s — and for `get-session` a 404 reads as "this
78
- * stage does not report roles", which silently turns off the check that tells a
79
- * permissions finding from seed drift.
80
- */
81
- export const authMount = (signInPath) => {
82
- const mount = signInPath ? signInPath.lastIndexOf('/sign-in/') : -1;
83
- return !signInPath || mount === -1 ? undefined : signInPath.slice(0, mount);
84
- };
85
- /**
86
- * Establish a Fabric operator session against `apiUrl` and resolve the target's
87
- * own id for `persona`, which is what the impersonation header names.
88
- *
89
- * Takes the fetch to use rather than making one, because the two callers need
90
- * the cookies to land in different places: an HTTP persona keeps them in its
91
- * jar, a browser run plants them on a Playwright context. Both need the same
92
- * handshake, and it is the kind of sequence that quietly diverges once it is
93
- * written twice.
94
- */
95
- export const establishOperatorSession = async (fetchImpl, apiUrl, persona, options, extraHeaders = {}) => {
96
- const signInPath = options.signInPath ?? '/auth/sign-in/fabric';
97
- const token = typeof options.token === 'function' ? await options.token() : options.token;
98
- const res = await fetchImpl(`${apiUrl}${signInPath}`, {
99
- method: 'POST',
100
- headers: { 'content-type': 'application/json', ...extraHeaders },
101
- body: JSON.stringify({
102
- token,
103
- actAs: { email: persona.email },
104
- }),
105
- });
106
- if (!res.ok) {
107
- throw await failed('operator sign-in', persona.id, res);
108
- }
109
- const setCookies = res.headers.getSetCookie?.() ?? [];
110
- if (setCookies.length === 0) {
111
- throw new PikkuError(`[scenario] operator sign-in for '${persona.id}' returned no session cookie`);
112
- }
113
- const body = (await res.json().catch(() => null));
114
- const userId = body?.actAs?.userId;
115
- if (!userId) {
116
- throw new PikkuError(`[scenario] operator sign-in for '${persona.id}' returned no user to act as — ` +
117
- 'the target is running a @pikku/better-auth too old to resolve one');
118
- }
119
- return { setCookies, userId: String(userId) };
120
- };
121
- /**
122
- * Sign a persona in on a DEPLOYED stage, by having a Fabric operator act as
123
- * them — the path that needs no test credential to exist anywhere.
124
- *
125
- * `POST /auth/sign-in/fabric` verifies an RS256 token against the stage's
126
- * `FABRIC_AUTH_PUBLIC_KEY` and mints a session for a synthetic operator row
127
- * granted the umbrella `admin` scope. Impersonation is then a header on each
128
- * request rather than a second session, and its gate is that scope — not
129
- * `user.role`, which is why this works without touching the app's roles.
130
- *
131
- * Asymmetric throughout: the stage can verify an operator token and never mint
132
- * one, so nothing in a deployed environment is worth stealing. That is the
133
- * property the actor secret cannot have, and the reason these are two classes
134
- * instead of one with a flag.
135
- */
136
- export class OperatorSignIn {
137
- apiUrl;
138
- options;
139
- userId = null;
140
- constructor(apiUrl, options) {
141
- this.apiUrl = apiUrl;
142
- this.options = options;
143
- }
144
- async login(jar, persona) {
145
- const { userId } = await establishOperatorSession(jar.fetch, this.apiUrl, persona, this.options);
146
- this.userId = userId;
147
- }
148
- headers() {
149
- if (!this.userId) {
150
- throw new Error('[scenario] operator session has no persona to act as — login() first');
151
- }
152
- return { [IMPERSONATE_USER_ID_HEADER]: this.userId };
153
- }
154
- }
@@ -1,2 +0,0 @@
1
- export type { ActorFlowVerdict, ConverseOptions, TargetAgentDriver, } from './actor-flow.types.js';
2
- export { runConversation, type RunConversationParams, type ActorLLM, } from './run-conversation.js';
@@ -1,36 +0,0 @@
1
- import type { ActorFlowApprovalPolicy, ActorFlowVerdict, TargetAgentDriver } from './actor-flow.types.js';
2
- import type { ResolvedPersona } from '../../services/personas-service.js';
3
- import type { AgentRunnerParams, AgentStepResult } from '../../services/agent-runner-service.js';
4
- /** The LLM call the actor uses for its own turns/decisions/evaluation. */
5
- export type ActorLLM = (params: AgentRunnerParams) => Promise<AgentStepResult>;
6
- export interface RunConversationParams {
7
- /** The person being played — name, job title, personality. */
8
- persona: ResolvedPersona;
9
- /** The id they were declared under (for transcript labelling). */
10
- personaId: string;
11
- /** What the actor is trying to get the target agent to accomplish. */
12
- task: string;
13
- /** Natural-language success criterion the actor evaluates at the end. */
14
- evaluate: string;
15
- /** How the actor answers the target agent's tool-approval requests. */
16
- approvals?: ActorFlowApprovalPolicy;
17
- /** Model the actor uses for its own turns/decisions. */
18
- model: string;
19
- /** Hard cap on conversation turns. Default 12. */
20
- maxTurns?: number;
21
- /** Hard cap on tool-approval rounds within a single target turn. Default 16. */
22
- maxApprovalRounds?: number;
23
- /** Transport that drives the target agent (HTTP in production). */
24
- target: TargetAgentDriver;
25
- /** The actor's own LLM. */
26
- llm: ActorLLM;
27
- /** Display name of the target agent (transcript labelling). */
28
- agentName: string;
29
- }
30
- /**
31
- * Run a conversation: an LLM-driven actor holds a real multi-turn exchange
32
- * with a target agent (driven via the injected transport), answers the target's
33
- * tool-approval requests in-persona, then evaluates whether the task was met.
34
- * Deterministic checks are the caller's responsibility.
35
- */
36
- export declare function runConversation(params: RunConversationParams): Promise<ActorFlowVerdict>;
@@ -1,32 +0,0 @@
1
- import type { CorePersonas } from './persona.types.js';
2
- /**
3
- * No-op function for declaring personas.
4
- * This exists purely for TypeScript type checking and will be tree-shaken.
5
- * The CLI extracts metadata via AST parsing and generates a `PersonaId` union
6
- * and a `personas.gen.json` the console and `pikku persona run` read off disk —
7
- * describing, listing or running one never has to load the app.
8
- *
9
- * `definePersonas` rather than `defineVirtualUsers` because declaring is not
10
- * running: a persona that exists only to be acted upon (the account an admin
11
- * bans, the colleague a document is shared with) is seeded and never run.
12
- *
13
- * Exactly one `definePersonas(...)` is allowed per codebase, so there is one
14
- * place to read the cast from and one place to add to. A second call — even in
15
- * the same file — fails the build.
16
- *
17
- * @example
18
- * ```typescript
19
- * definePersonas({
20
- * susan: {
21
- * name: 'Susan',
22
- * jobTitle: 'Buys for a small café',
23
- * roles: ['buyer'],
24
- * personality: 'Hunts cheap deals. Tries three coupon codes before giving up.',
25
- * goals: ['Get the weekly order in under five minutes'],
26
- * disposition: 'careless',
27
- * account: {},
28
- * },
29
- * })
30
- * ```
31
- */
32
- export declare const definePersonas: (_config: CorePersonas) => void;
@@ -1,23 +0,0 @@
1
- export { definePersonas } from './define-personas.js';
2
- export { isRunnablePersona, roleMismatchMessage, runnablePersonas, validateAndBuildPersonasMeta, verifyPersonaRoles, } from './validate-personas.js';
3
- export type { RoleVerification } from './validate-personas.js';
4
- export { personaEnvironmentErrors, personaEnvironmentRefusal, } from './persona-environments.js';
5
- export type { PersonaEnvironment, PersonaEnvironmentSubject, } from './persona-environments.js';
6
- export { personaEmail, personaEmails } from './persona-email.js';
7
- export { APP_SCOPE_ROOT, appScopeId, buildAppScopeDefinition, } from './persona-app-scopes.js';
8
- export type { MailboxAllowlist, PersonaMailbox, ReceivedEmail, } from './persona-mailbox.js';
9
- export type { CorePersona, CorePersonas, PersonaAccountMeta, PersonaDefinitions, PersonaMeta, PersonasMeta, } from './persona.types.js';
10
- /**
11
- * Signing in as a persona and talking to a stage over HTTP.
12
- *
13
- * Deliberately reached through this entry point rather than `@pikku/core/services`.
14
- * `HttpPersona` pulls in the actor-flow conversation runner and, through it, the
15
- * agent runner — none of which a production server has any use for. Exporting it
16
- * from the services barrel put that chain in the module graph of every app that
17
- * imports services, which only a bundler could remove and an unbundled Node or
18
- * Lambda deploy would load outright.
19
- */
20
- export { HttpPersona, createHttpPersonas, type HttpPersonasConfig, } from '../../services/http-personas.js';
21
- export { ActorSignIn, OperatorSignIn, establishOperatorSession, IMPERSONATE_USER_ID_HEADER, type ActorSecretResolver, type PersonaSignIn, type OperatorSignInOptions, type OperatorSessionResult, authMount, } from '../../services/persona-sign-in.js';
22
- export { ACTOR_ROOT_SECRET_MIN_LENGTH, ACTOR_SECRET_INFO, ACTOR_SECRET_NAME, actorSecretSubject, deriveActorSecret, verifyActorSecret, } from '../../services/persona-actor-secret.js';
23
- export { postScenarioJson, readScenarioHttpResponse, } from '../../services/personas-service.js';
@@ -1,19 +0,0 @@
1
- export { definePersonas } from './define-personas.js';
2
- export { isRunnablePersona, roleMismatchMessage, runnablePersonas, validateAndBuildPersonasMeta, verifyPersonaRoles, } from './validate-personas.js';
3
- export { personaEnvironmentErrors, personaEnvironmentRefusal, } from './persona-environments.js';
4
- export { personaEmail, personaEmails } from './persona-email.js';
5
- export { APP_SCOPE_ROOT, appScopeId, buildAppScopeDefinition, } from './persona-app-scopes.js';
6
- /**
7
- * Signing in as a persona and talking to a stage over HTTP.
8
- *
9
- * Deliberately reached through this entry point rather than `@pikku/core/services`.
10
- * `HttpPersona` pulls in the actor-flow conversation runner and, through it, the
11
- * agent runner — none of which a production server has any use for. Exporting it
12
- * from the services barrel put that chain in the module graph of every app that
13
- * imports services, which only a bundler could remove and an unbundled Node or
14
- * Lambda deploy would load outright.
15
- */
16
- export { HttpPersona, createHttpPersonas, } from '../../services/http-personas.js';
17
- export { ActorSignIn, OperatorSignIn, establishOperatorSession, IMPERSONATE_USER_ID_HEADER, authMount, } from '../../services/persona-sign-in.js';
18
- export { ACTOR_ROOT_SECRET_MIN_LENGTH, ACTOR_SECRET_INFO, ACTOR_SECRET_NAME, actorSecretSubject, deriveActorSecret, verifyActorSecret, } from '../../services/persona-actor-secret.js';
19
- export { postScenarioJson, readScenarioHttpResponse, } from '../../services/personas-service.js';
@@ -1,23 +0,0 @@
1
- ---
2
- type: decision
3
- title: An actor conversation starts from a seeded kickoff message
4
- description: The actor's first turn needs a non-empty message list because providers reject an empty prompt; the seed is an instruction and stays out of the transcript
5
- tags: actor-flow
6
- ---
7
-
8
- # An actor conversation starts from a seeded kickoff message
9
-
10
- `runConversation` in
11
- `packages/core/src/wirings/actor-flow/run-conversation.ts` primes
12
- `actorMessages` with a single user message telling the persona to begin, before
13
- the first turn is generated.
14
-
15
- The actor speaks first, so at that point there is nothing for it to reply to —
16
- and LLM providers reject a completion request with no messages. The seed is
17
- addressed to the actor rather than to the target agent, which is why it is never
18
- pushed onto `transcript`: it is scaffolding, not something the simulated user
19
- said, and including it would corrupt the transcript the final evaluation reads.
20
-
21
- **What this rules out:** starting from an empty `actorMessages` array, folding
22
- the kickoff into the persona instructions where a provider may weight it
23
- differently, and pushing the seed into the transcript for symmetry.
@@ -1,25 +0,0 @@
1
- ---
2
- type: decision
3
- title: An actor-flow verdict is the persona's self-evaluation, not an assertion
4
- description: The engine returns what the actor judged plus the transcript; deterministic checks stay with the caller
5
- tags: actor-flow
6
- ---
7
-
8
- # An actor-flow verdict is the persona's self-evaluation, not an assertion
9
-
10
- `runConversation` ends by asking the persona LLM whether the `evaluate`
11
- criterion was met and returns that as `ActorFlowVerdict` — `passed`, `reasoning`
12
- and the full `transcript`
13
- (`packages/core/src/wirings/actor-flow/`). Nothing in the engine inspects the
14
- system the agent acted on.
15
-
16
- The verdict is deliberately soft because the thing it judges is soft: whether a
17
- conversation accomplished a natural-language task. Deterministic checks — that a
18
- row was written, that an email went out — belong to the caller, which already
19
- holds the actor and can `actor.invoke(...)` after the conversation returns. The
20
- `reasoning` and `transcript` fields exist so a soft failure is debuggable rather
21
- than merely red.
22
-
23
- **What this rules out:** having `runConversation` assert or throw on a failed
24
- verdict, and adding deterministic post-conditions to `ConverseOptions` instead of
25
- running them at the call site.
@@ -1,43 +0,0 @@
1
- ---
2
- type: decision
3
- title: Both validators check personas through one shared module, and a persona counts from either the meta or the source
4
- description: workspace validate and fabric validate are separate implementations, so the persona/actor/knowledge checks live in persona-checks.ts and are called by both — and a persona is counted from the generated meta or a definePersonas() source scan, whichever answers first
5
- tags: cli, validate, scenarios, personas
6
- ---
7
-
8
- # Both validators check personas through one shared module, and a persona counts from either the meta or the source
9
-
10
- `pikku workspace validate` and `pikku fabric validate` read like one command
11
- with a flag. They are not. They are two implementations that walk the same
12
- project and duplicate about eighteen findings between them verbatim —
13
- `functions-dir-missing` exists twice, character for character, in two files.
14
- Fabric adds the deploy-shaped checks (themes, frontends, the Cloudflare
15
- adapter, the `.gitignore` contract); workspace adds the local-development ones
16
- (`dev.db`, the auth migrations).
17
-
18
- So a check written into either one is a check half the projects never run. An
19
- app that never deploys through fabric only ever sees workspace validate; an app
20
- that does sees fabric validate in CI. The persona checks are the kind that only
21
- pay off when nobody remembers to ask for them, which means they had to be in
22
- both. Rather than duplicate them a nineteenth time, they live in
23
- `persona-checks.ts` and both validators call `runPersonaChecks`. Merging the
24
- two validators outright is the better fix and a much larger one; this does not
25
- block it.
26
-
27
- **A persona counts from either source, and either alone is wrong at a
28
- predictable moment.** The generated `pikku-personas-meta.gen.json` is empty on
29
- a fresh clone where codegen has not run, so trusting only it means a warning
30
- that fires on every new checkout — and a validator that cries wolf gets muted,
31
- after which it never says anything worth hearing. A source scan for
32
- `definePersonas(` cannot see personas an addon contributed. Declared by either
33
- counts as declared.
34
-
35
- **Everything here is `warn`, never `error`.** A project with no personas is
36
- under-tested, not broken, and `validate` gating a deploy on it would make the
37
- first thing anyone does with the check be to remove it. `knowledge-empty` stays
38
- at the `info` the shared `@pikku/knowledge` package already assigned it rather
39
- than being raised to match — the severity belongs to that package.
40
-
41
- **What this rules out:** duplicating the checks into both validators;
42
- adding them to only one; deriving the persona count from the generated meta
43
- alone; failing a build over a missing persona.
@@ -1,31 +0,0 @@
1
- ---
2
- type: decision
3
- title: Actor sign-in only works for actor-flagged users
4
- description: An actor credential mints sessions for user rows flagged actor and nothing else, so holding one never impersonates a real user
5
- tags: services
6
- ---
7
-
8
- # Actor sign-in only works for actor-flagged users
9
-
10
- `HttpPersonasConfig.secret` (`packages/core/src/services/http-personas.ts`) is
11
- what `ActorSignIn.login` presents: it POSTs `{ email, name, secret }` to
12
- `/auth/sign-in/actor` and gets back a session. That looks like a master key, and
13
- it deliberately is not one — for two independent reasons.
14
-
15
- The first is the flag. The Better Auth actor plugin on the other end upserts and
16
- signs in only user rows flagged `actor: true`. Presenting a credential with a
17
- real customer's email does not mint that customer's session — it is refused. The
18
- `actor` flag also flows into the minted session, so audits and analytics can tell
19
- scenario traffic from human traffic after the fact.
20
-
21
- The second is that a credential is not shared. What is presented is derived from
22
- the root `SCENARIO_ACTOR_SECRET` and the address it signs in as — see
23
- [an actor credential is derived per persona](an-actor-credential-is-derived-per-persona.md) —
24
- so the blast radius of a leaked credential is one synthetic account, not the
25
- synthetic actor population.
26
-
27
- **What this rules out:** widening the sign-in endpoint to accept any email "so
28
- scenarios can test as a real user", and treating an actor credential as
29
- equivalent to a session-signing key. It also rules out dropping the `actor` flag
30
- from the minted session — the audit trail's ability to separate synthetic from
31
- real activity depends on it.
@@ -1,42 +0,0 @@
1
- ---
2
- type: decision
3
- title: An actor credential is derived per persona
4
- description: What a caller presents to the actor endpoint is HKDF-derived from the root secret and the address it signs in as, so one credential opens one persona
5
- tags: services
6
- ---
7
-
8
- # An actor credential is derived per persona
9
-
10
- `SCENARIO_ACTOR_SECRET` is a root, not a password. What a caller presents to
11
- `/auth/sign-in/actor` is `deriveActorSecret(root, email)`
12
- (`packages/core/src/services/persona-actor-secret.ts`) — an HKDF-expanded
13
- HMAC-SHA256 over the lowercased address, on the same key-material primitives
14
- everything else in core signs with. The endpoint does not store or look anything
15
- up: it re-derives the expected value for whichever address is being signed in as
16
- and compares. A credential minted for one persona is refused for every other.
17
-
18
- The root itself is not accepted as a credential, and a root shorter than 32
19
- characters refuses the endpoint outright rather than deriving weak credentials
20
- from it. The server-side warning names the problem; what the client is told does
21
- not.
22
-
23
- Derivation rather than a per-persona secrets table because there is then nothing
24
- to store, provision, or keep in sync — the target already holds the root, and
25
- rotating it invalidates every credential at once.
26
-
27
- This is what lets a holder be handed less than everything:
28
-
29
- - The browser switcher gets no credential at all. It used to be handed
30
- `VITE_DEV_ACTOR_SECRETS`, one per persona; it now names a persona by id at
31
- `/sign-in/persona`, which the server gates with the actor opt-in and the
32
- `devSwitcher` flag, so a bundle holds nothing worth taking.
33
- - A run can be given `PIKKU_PERSONA_SECRETS` (`id=secret,…`, minted with
34
- `pikku persona secret`) instead of the root, and then it can sign in as those
35
- personas and no others. Asking for one outside the list throws naming the
36
- persona rather than falling back to the root.
37
-
38
- **What this rules out:** accepting the root as a credential at the endpoint,
39
- putting the root in any client bundle, and comparing a presented credential
40
- against a stored one. It also rules out per-persona secrets that are generated
41
- randomly and written down — the derivation is the reason there is nothing to
42
- provision.
@@ -1,26 +0,0 @@
1
- ---
2
- type: decision
3
- title: Pikku carries actor scopes as data and the app grants them
4
- description: scopes and roles on a ScenarioActorConfig are transported, never applied — the app's own seed reads them back and performs the grant
5
- tags: services
6
- ---
7
-
8
- # Pikku carries actor scopes as data and the app grants them
9
-
10
- `ScenarioActorConfig.scopes` and `.roles`
11
- (`packages/core/src/services/scenario-actors-service.ts`) come from
12
- `pikku.config.json` and are carried through to `scenarioActorConfigs`. Nothing in
13
- core reads them to grant anything. The app's seed reads them back off that config
14
- and performs the grants itself.
15
-
16
- Core cannot do the granting: which scope store exists, whether roles have been
17
- created, and what a role means are all the application's own. Worse, a framework
18
- that granted scopes would be a framework that can escalate an actor's privileges
19
- from a config file — the one place where a scenario author would least expect a
20
- security decision to take effect. Keeping the fields inert makes the grant an act
21
- the app performs deliberately, in code it owns, against a store it wired.
22
-
23
- **What this rules out:** teaching `createHttpScenarioActors` (or any core service)
24
- to call `ScopeService.addScopeToUser` / `addUserToRole` from these fields, and
25
- treating a scope listed on an actor config as evidence that the actor holds it.
26
- If a scenario fails on a missing scope, the fix is in the app's seed, not here.