@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
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: Open-source auth works without Pikku Studio Cloud
4
+ description: Every virtual-user sign-in path works in an app that runs no Pikku Studio Cloud code; the signing key is the project's own, never a cloud-issued credential
5
+ tags: services, auth
6
+ ---
7
+
8
+ # Open-source auth works without Pikku Studio Cloud
9
+
10
+ Virtual users and staged impersonation must all work in an open-source app with
11
+ no Pikku Studio Cloud service and no cloud-issued token. The cloud may add its
12
+ own way in, but nothing in core, the CLI or the better-auth service may require
13
+ it.
14
+
15
+ The code holds to this. A virtual user signs in either in-process
16
+ (`inProcessSignIn`, no credential at all) or with a signed statement
17
+ (`statementSignIn`), both in `packages/core/src/services/virtual-user-sign-in.ts`.
18
+ The statement is signed with an Ed25519 key the project owns:
19
+ `packages/cli/src/utils/virtual-user-signer.ts` generates it for `pikku dev`, or
20
+ reads `VIRTUAL_USER_SIGNING_KEY` from whoever deploys the stage. The app is given
21
+ only the public half, as `VIRTUAL_USER_VERIFY_KEY`
22
+ (`packages/services/better-auth/src/virtual-user-plugin.ts`).
23
+
24
+ **What this rules out:** any design where Pikku Studio Cloud is the issuer of the
25
+ credential a virtual user presents. A signer that only exists in the cloud cannot
26
+ be the basis for an open-source feature.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: Outbound webhooks use an allowlist when one is configured, and block private hosts otherwise
4
+ description: The webhook delivery reads allowedHosts from config and passes it to safeFetch; without an allowlist safeFetch refuses private hosts, and an allowlist replaces that check
5
+ tags: core, webhooks, ssrf, security
6
+ ---
7
+
8
+ # Outbound webhooks use an allowlist when one is configured, and block private hosts otherwise
9
+
10
+ `WebhookServiceConfig.allowedHosts` in `packages/core/src/services/webhook-service.ts:33-44`
11
+ says that when it is set only those hostnames may be delivered to, and when it is
12
+ omitted private and internal hosts are blocked.
13
+
14
+ `queue-webhook-service.ts:104-110` reads it from `config.webhook`, and falls back
15
+ to no allowlist if the singletons are not initialised. The value is passed to
16
+ `safeFetch` (`:112-122`).
17
+
18
+ In `packages/core/src/utils/safe-fetch.ts:265-273`, an allowlist makes the check an
19
+ exact hostname match, and the private-host refusal is in the `else` branch that only
20
+ runs without an allowlist. So with an allowlist, a listed host is not refused for
21
+ being private.
22
+
23
+ **What this rules out:** delivering to a host outside a configured allowlist, and
24
+ delivering to a private host when no allowlist is configured.
@@ -0,0 +1,35 @@
1
+ ---
2
+ type: decision
3
+ title: Core carries virtual user roles as data and provisioning grants them
4
+ description: roles on a resolved virtual user are transported by core, never applied there — provisionVirtualUsers grants them in the running stage, through the scope service the app wired
5
+ tags: services
6
+ ---
7
+
8
+ # Core carries virtual user roles as data and provisioning grants them
9
+
10
+ A virtual user's `roles` (and the `app` it signs into) are declared in
11
+ `defineVirtualUsers` and carried on `ResolvedVirtualUser`
12
+ (`packages/core/src/services/virtual-users-service.ts`) into the generated
13
+ `virtualUserConfigs`. Nothing in core reads them to grant anything:
14
+ `createHttpVirtualUsers` (`packages/core/src/services/http-virtual-users.ts`) only
15
+ signs the virtual user in.
16
+
17
+ The grant is performed by `provisionVirtualUsers`
18
+ (`packages/services/better-auth/src/provision-virtual-users.ts`), which runs in the
19
+ stage itself and calls `addUserToRole` / `addScopeToUser` on the `scopeService` the
20
+ app passes in. Its doc comment says it is additive — it creates and grants, never
21
+ deletes or revokes — and that it throws rather than grant a virtual user's roles to
22
+ an address a real user already holds.
23
+
24
+ Core cannot do the granting: which scope store exists, whether roles have been
25
+ created, and what a role means are all the application's own. Worse, a framework
26
+ that granted scopes from wherever it was handed a virtual user would be a framework
27
+ that can escalate privileges as a side effect of signing in. Keeping the fields
28
+ inert in core makes the grant an act performed deliberately, against a store the
29
+ app wired.
30
+
31
+ **What this rules out:** teaching `createHttpVirtualUsers` (or any core service)
32
+ to call `ScopeService.addScopeToUser` / `addUserToRole` from these fields, and
33
+ treating a role listed on a virtual user declaration as evidence that the account
34
+ holds it. That evidence comes from reading the roles back
35
+ ([a virtual user whose roles drift from the declaration refuses to run](a-virtual-user-whose-roles-drift-from-the-declaration-refuses-to-run.md)).
@@ -0,0 +1,31 @@
1
+ ---
2
+ type: decision
3
+ title: Scoped content access is confined to a root folder, with longest-prefix grants outside it
4
+ description: ScopedContentService keeps an addon inside its root folder, lets it reach the wider store only through read or write grants matched by longest prefix, and refuses signURL and copyFile
5
+ tags: core, content, scoping, paths
6
+ ---
7
+
8
+ # Scoped content access is confined to a root folder, with longest-prefix grants outside it
9
+
10
+ `packages/core/src/services/scoped-content-service.ts` narrows a `ContentService`
11
+ to one folder. The doc comment describes the rule (`scoped-content-service.ts:57-67`).
12
+
13
+ Paths are normalised first (`scoped-content-service.ts:39-49`). Each value is
14
+ decoded up to four times (`scoped-content-service.ts:16`, `25-35`). A null byte,
15
+ a leading `/` or `\`, or a `.` or `..` segment in any pass is refused
16
+ (`scoped-content-service.ts:40-46`).
17
+
18
+ A bucket that does not start with `@` is placed under the root
19
+ (`scoped-content-service.ts:105-109`). A bucket that starts with `@` names a path
20
+ from the store root. It is allowed if it sits under the root
21
+ (`scoped-content-service.ts:113`). Otherwise the grants are checked, sorted
22
+ longest prefix first (`scoped-content-service.ts:79-88`), and the first match
23
+ decides (`scoped-content-service.ts:114-117`). A write needs a `write` grant
24
+ (`scoped-content-service.ts:115`).
25
+
26
+ `signURL` and `copyFile` throw unconditionally (`scoped-content-service.ts:126-128`,
27
+ `145-147`). The doc comment says both take a finished URL or a local path, "so
28
+ neither can be narrowed to a folder" (`scoped-content-service.ts:64-66`).
29
+
30
+ **What this rules out:** traversal, including encoded traversal, out of the root,
31
+ signed URLs for content outside the root, and copies that name arbitrary paths.
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: decision
3
+ title: Contexts that must not hold secrets get a services bag whose secrets accessor throws
4
+ description: withoutSecrets replaces the secrets entry with a non-enumerable getter that throws, so code outside the permitted contexts cannot reach the secret service
5
+ tags: core, secrets, services
6
+ ---
7
+
8
+ # Contexts that must not hold secrets get a services bag whose secrets accessor throws
9
+
10
+ `packages/core/src/services/secretless.ts` exports `withoutSecrets`. If the
11
+ services object has no `secrets` key, it is returned unchanged
12
+ (`secretless.ts:17-19`). Otherwise a shallow copy is made, and `secrets` is
13
+ redefined as a getter that throws `SecretAccessDeniedError`
14
+ (`secretless.ts:20-27`). The property is non-enumerable and configurable
15
+ (`secretless.ts:25-26`), so a spread of the object does not copy it.
16
+
17
+ The error message says `secrets` is confined to `pikkuServices`,
18
+ `pikkuWireServices`, addon service factories and middleware, and tells the caller
19
+ to give the service the secret at construction time instead (`secretless.ts:6`).
20
+
21
+ **What this rules out:** reading `services.secrets` from a context that is not on
22
+ that list, and a secret leaking through a copy of the services object.
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: decision
3
+ title: Shared guards refuse to mutate or shadow a system role, in every store
4
+ description: system-role-guard holds the guards every ScopeService must call before changing a role, and roleLockReason gives the console a reason to show for a locked role
5
+ tags: core, roles, scopes, guards
6
+ ---
7
+
8
+ # Shared guards refuse to mutate or shadow a system role, in every store
9
+
10
+ `packages/core/src/services/system-role-guard.ts` is the shared guard that a
11
+ `ScopeService` implementation must apply before it mutates a role. The doc
12
+ comment says the rule is "a contract, not a policy", because a store that lets
13
+ the console delete a declared role makes virtual user and scenario checks stop testing
14
+ what they claim to (`system-role-guard.ts:7-15`).
15
+
16
+ - `assertRoleIsMutable` throws `SystemRoleImmutableError` when the name is a
17
+ system role (`system-role-guard.ts:27-35`). The caller names the operation in
18
+ the message.
19
+ - `assertRoleNameAvailable` throws `SystemRoleShadowedError` when a new custom
20
+ role would take a declared name (`system-role-guard.ts:40-47`).
21
+ - `isSystemRole` is passed in, so an implementation can answer from a column, a
22
+ cache or a query (`system-role-guard.ts:17-18`, `20`).
23
+
24
+ `roleLockReason` (`system-role-guard.ts:56-71`) returns a reason string for a
25
+ system role, or `null` when it is editable. A role whose declaration was removed
26
+ gets a message that says it is inert and points to `pikku roles prune`
27
+ (`system-role-guard.ts:60-66`). The doc comment says a reason is shown "instead
28
+ of a disabled button with no explanation" (`system-role-guard.ts:52-54`).
29
+
30
+ Sibling note: [system roles are synced additively and cannot be deleted or
31
+ rewritten](system-roles-are-additive-and-immutable.md).
32
+
33
+ **What this rules out:** a store deleting, re-scoping or shadowing a declared
34
+ role, and a locked role shown without a reason.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: Signed uploads are verified by the same check as signed reads
4
+ description: The local content handler refuses an upload whose presigned URL does not verify, using the same exported verifier that signed reads use
5
+ tags: core, content, security
6
+ ---
7
+
8
+ # Signed uploads are verified by the same check as signed reads
9
+
10
+ `verifySignedContentRequest` in
11
+ `packages/core/src/services/local-content-request-handler.ts:60-72` is the one
12
+ function that decides whether a signed read or a signed upload is genuine. It is
13
+ exported so every runtime that serves content verifies the same way.
14
+
15
+ The handler's `validateSignedAssetRequest` wraps it (`:146-149`).
16
+ `handleUpload` calls that wrapper before writing anything and returns the failure
17
+ response when the check does not pass (`:157-169`). The comment at `:162-165`
18
+ gives the reason: without the check, an unauthenticated PUT could write to any
19
+ key under the content root.
20
+
21
+ The existing note `signed-content-urls-bind-the-request-path.md` covers the read
22
+ side and the node server verifier; this note covers the upload path in the core
23
+ handler.
24
+
25
+ **What this rules out:** writing an upload key before its signed URL has verified,
26
+ and a separate upload check that could drift from the read check.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: System role scopes must be declared with defineScope, or the build fails
4
+ description: A system role may grant only scope ids that defineScope declares, and the bare wildcard is refused; both are critical inspector diagnostics, so a role cannot grant a scope no function checks
5
+ tags: core, roles, scopes, inspector
6
+ ---
7
+
8
+ # System role scopes must be declared with defineScope, or the build fails
9
+
10
+ The type doc on `CoreSystemRole.scopes` says every id a role grants "must be declared with `defineScope` — the inspector fails the build otherwise, so a role cannot quietly grant a scope that no function checks" (`packages/core/src/wirings/role/role.types.ts:15-19`).
11
+
12
+ The check is `validateSystemRoleScopes` in `packages/inspector/src/utils/post-process.ts:1745-1775`. It walks every `state.systemRoles.definitions` entry and each of its scopes:
13
+
14
+ - The bare `*` is reported as critical and skipped (`post-process.ts:1757-1763`).
15
+ - A trailing `:*` is stripped before the lookup (`post-process.ts:1765`), so `admin:*` needs `admin` to be declared.
16
+ - Any other scope missing from the set built from `state.scopes.definitions` is reported as critical, with the list of available scopes (`post-process.ts:1766-1771`).
17
+
18
+ It runs from the validation pass in `packages/inspector/src/inspector.ts:580-581`, straight after `applyDeclaredScopes` and `validateScopeReferences`.
19
+
20
+ Critical diagnostics are what block the build by default. The `diagnostic` doc in `packages/inspector/src/types.ts:400-403` says `error` and `warn` block only under `--fail-on-error` / `--fail-on-warn`, with the default at critical only.
21
+
22
+ The literal-array and unqualified-name rules for `defineSystemRole` are a separate check, covered in [System roles must declare literal, unqualified scopes](../../../../inspector/knowledge/decisions/system-roles-declare-literal-unqualified-scopes.md). The runtime side, where a synced role can only add grants, is in [System roles are synced additively and cannot be deleted or rewritten](system-roles-are-additive-and-immutable.md).
23
+
24
+ **What this rules out:** a system role that grants a scope no `defineScope` declares, which no function would ever check. It also rules out a role granting bare `*`, which would hide what the role confers. Neither is a warning to tolerate; both stop the build.
@@ -0,0 +1,16 @@
1
+ ---
2
+ type: decision
3
+ title: System roles are never deleted by a deploy, but their scope set follows the declaration, and the admin API cannot change them
4
+ description: syncSystemRoles keeps a role row whose declaration has gone (marked declared false) but replaces a declared role's scope set; deleteRole and setRoleScopes refuse system roles
5
+ tags: core, scopes, roles
6
+ ---
7
+
8
+ # System roles are never deleted by a deploy, but their scope set follows the declaration, and the admin API cannot change them
9
+
10
+ `syncSystemRoles` is documented in `packages/core/src/services/scope-service.ts:36-47`. It is additive for the **role row**: a role whose `defineSystemRole` declaration has gone is left in place and marked `declared: false`, so a rollback or a rolling deploy cannot strip everyone's grant.
11
+
12
+ It is **not** additive for the role's **scopes**. The same comment says "A role's scope set *is* re-synced, because that is the declaration's whole content" (`scope-service.ts:41-43`). The Kysely implementation does this by deleting the role's `pikkuRoleScopes` rows and inserting the declared ones inside one transaction (`packages/services/kysely/src/kysely-scope-service.ts:136-145`). A deploy that stops declaring a scope therefore removes it from that role. Only system rows are marked stale (`kysely-scope-service.ts:148-160`).
13
+
14
+ `deleteRole` and `setRoleScopes` throw `SystemRoleImmutableError` for a system role. Changing a system role's grants is therefore a code change plus a deploy, not an admin action at runtime. Scope sync itself (`syncScopes`) never deletes; see [scope resolution](scope-resolution-happens-at-the-session-boundary-and-sync-never-deletes.md).
15
+
16
+ **What this rules out:** an admin editing or deleting a system role through the normal role API, and a deploy deleting a system role row. It does not rule out a deploy removing a scope from a declared role. That is how a role's meaning is changed.
@@ -0,0 +1,21 @@
1
+ ---
2
+ type: decision
3
+ title: The channel RPC does not put the caller's session on the wire
4
+ description: ChannelDeploymentService.invoke accepts a session only to satisfy the DeploymentService shape and never sends it, so the peer runs the capability as itself
5
+ tags: core, channel, rpc, security
6
+ ---
7
+
8
+ # The channel RPC does not put the caller's session on the wire
9
+
10
+ The doc comment on `ChannelDeploymentService.invoke` in
11
+ `packages/core/src/wirings/channel/channel-rpc-service.ts:55-60` says `session`
12
+ is accepted to satisfy `DeploymentService` and is deliberately not sent. The peer
13
+ runs the capability as itself, on its own machine, and a session on the wire
14
+ would carry the caller's credentials while authorising nothing.
15
+
16
+ The signature takes the value as `_session?: unknown` (`:61-65`). The request
17
+ sent on the channel carries `action`, `id`, `funcName`, `data` and `traceId` (`:88-94`), and
18
+ the file does not refer to the session elsewhere in `invoke`.
19
+
20
+ **What this rules out:** forwarding the caller's session to a channel peer, and
21
+ treating a session on this path as an authorisation the peer will honour.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: The remote jobs inbox rejects every caller when its secret is unset
4
+ description: remoteJobsSecret refuses all requests when the dispatch secret variable is empty, and compares the header in constant time when it is set
5
+ tags: core, remote-jobs, secrets, middleware
6
+ ---
7
+
8
+ # The remote jobs inbox rejects every caller when its secret is unset
9
+
10
+ `packages/core/src/middleware/remote-jobs-secret.ts` guards the remote job inbox.
11
+ The doc comment says it "fails closed: an unset secret rejects every caller, so
12
+ the routes are never open just because the deployment forgot to configure one"
13
+ (`remote-jobs-secret.ts:18-21`).
14
+
15
+ The check rejects when any of three things is true (`remote-jobs-secret.ts:26-31`):
16
+ the expected value is empty, the provided header is not a string, or the two
17
+ values differ. All three throw `UnauthorizedError` (`remote-jobs-secret.ts:31`).
18
+
19
+ The comparison is `constantTimeEqual` (`remote-jobs-secret.ts:8-15`), which XORs
20
+ each character code and accumulates the result. It returns early on a length
21
+ mismatch (`remote-jobs-secret.ts:9`), so the length of the secret is not hidden.
22
+
23
+ **What this rules out:** an open inbox on a deployment that has not set the
24
+ secret, and a plain `===` comparison of the secret.
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: decision
3
+ title: The remote RPC gate fails closed and binds a token to one function
4
+ description: pikkuRemoteAuthMiddleware refuses a remote-RPC request unless the mesh token is audience pikku-remote, names the requested function, and carries a session that decrypts under the mesh secret
5
+ tags: core, auth, remote
6
+ ---
7
+
8
+ # The remote RPC gate fails closed and binds a token to one function
9
+
10
+ `pikkuRemoteAuthMiddleware` (`packages/core/src/middleware/remote-auth.ts`)
11
+ guards the receiving end of a mesh call, which `buildRemoteHeaders`
12
+ (`packages/core/src/remote.ts`) signs from the caller side. Where the code and
13
+ its tests state a rule:
14
+
15
+ - With no `PIKKU_REMOTE_SECRET`, a request whose path is under `/remote/rpc/` is
16
+ refused. The path check is case-insensitive, and the test
17
+ "requires authorization for a case-varied remote path when no secret is set"
18
+ says why.
19
+ - With the secret set but no JWT service, the middleware throws rather than
20
+ passing the request on.
21
+ - A missing header, a scheme other than `Bearer`, a token that fails to decode,
22
+ or an audience other than `pikku-remote` is `UnauthorizedError`.
23
+ - A token carrying `fn` is only accepted for the function it names on a remote
24
+ path. The test "should skip fn check for non-RPC paths even if fn in token"
25
+ confirms the binding is about the RPC surface.
26
+ - The session claim is decrypted under `PIKKU_REMOTE_SECRET` with
27
+ `REMOTE_SESSION_INFO`, which `buildRemoteHeaders` encrypts. A decryption
28
+ failure is `UnauthorizedError`, so a session is never read as plain claims.
29
+
30
+ **What this rules out:** accepting a mesh token with the wrong audience, reusing
31
+ a token minted for one function on another RPC path, and reading the session
32
+ claim without decrypting it. The gap for requests outside `/remote/rpc/` once the
33
+ secret is set is recorded in
34
+ [Does a non-remote request need a remote token once the secret is set?](../../questions/remote-secret-refuses-requests-without-a-remote-token-on-every-path.md).
@@ -0,0 +1,16 @@
1
+ ---
2
+ type: decision
3
+ title: The workflow status stream re-checks run ownership on every poll and closes when access is lost
4
+ description: streamWorkflowRunStatus asserts the run owner on each poll rather than once when the stream opens, and closes the channel when that check throws
5
+ tags: core, workflow, security, streaming
6
+ ---
7
+
8
+ # The workflow status stream re-checks run ownership on every poll and closes when access is lost
9
+
10
+ `streamWorkflowRunStatus` defines `poll`, which reads the run and calls `assertWorkflowRunOwner(run.wire, session)` on every read (`packages/core/src/wirings/workflow/workflow-status-stream.ts:63-72`). The comment says ownership is read from the run itself, so a stream that outlives its session should stop rather than keep reporting (`:69-71`).
11
+
12
+ The polling loop runs inside a `try` (`:142`). A throw from any poll, including the ownership refusal, runs `channel.close()` and rethrows (`:159-161`). The comment at `:139-141` names this case.
13
+
14
+ `assertWorkflowRunOwner` is in `packages/core/src/wirings/workflow/workflow-run-ownership.ts:26-36`. It returns when the run has no recorded owner, and throws `WorkflowRunForbiddenError` when the session's `userId` does not match the owner (`:26-35`).
15
+
16
+ **What this rules out:** checking ownership only when the stream opens, and leaving a stream open after its ownership check throws.
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: decision
3
+ title: Two credentials resolving to one key must agree on whether they are per-user or singleton
4
+ description: buildCredentialResolutions throws when two credentials resolve to the same stored key but would be read in different modes, so one cannot silently serve the other's value
5
+ tags: core, credentials, overrides, wire
6
+ ---
7
+
8
+ # Two credentials resolving to one key must agree on whether they are per-user or singleton
9
+
10
+ `packages/core/src/wirings/credential/credential-overrides.ts` decides, for each
11
+ credential the package declares, where its value is read from.
12
+
13
+ An override is either a string, which renames the credential, or an object with a
14
+ `name` and a `mode` (`credential-overrides.ts:9-16`). The doc comment says the
15
+ object form "decides where its value is read from", so one addon can serve a
16
+ per-user product and a team account (`credential-overrides.ts:5-7`).
17
+
18
+ For each name, the resolved key is the override's name or the name itself
19
+ (`credential-overrides.ts:56-57`). The mode is the override's `mode`, or else the
20
+ type the author declared (`credential-overrides.ts:59-63`). A credential with no
21
+ override keeps its declared type (`credential-overrides.ts:38-40`).
22
+
23
+ If a key is already claimed with a different mode, the function throws
24
+ (`credential-overrides.ts:65-74`). The message says the loser "would silently read
25
+ the other's value — a per-user credential served from the deployment's own account,
26
+ or the reverse" (`credential-overrides.ts:70-72`).
27
+
28
+ **What this rules out:** a per-user credential and a deployment credential sharing
29
+ one stored key, with one quietly serving the other.
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: Verified bearer claims are pass-through data that core never fills
4
+ description: PikkuHTTP.authInfo is a typed slot for claims a host has already verified; core declares it and no core source assigns it from request headers
5
+ tags: core, http, auth
6
+ ---
7
+
8
+ # Verified bearer claims are pass-through data that core never fills
9
+
10
+ The doc comment on `PikkuHTTPAuthInfo` in `packages/core/src/wirings/http/http.types.ts:67-74`
11
+ says the claims are strictly pass-through: nothing in pikku derives them from a
12
+ request's headers, because verifying a token is the host's job. The field carries
13
+ what the raw `Authorization` header cannot: the scopes and client the token was
14
+ issued for.
15
+
16
+ The interface is at `http.types.ts:79-88`, and `authInfo?: PikkuHTTPAuthInfo` is on
17
+ `PikkuHTTP` at `:94`. A search of `packages/core/src` finds only the type and its
18
+ re-export (`packages/core/src/wirings/http/index.ts:23`). The comment at `:76-77`
19
+ says the MCP SDK is the one caller that populates it, and that is outside this
20
+ package.
21
+
22
+ **What this rules out:** deriving `scopes`, `clientId` or `token` from the
23
+ `Authorization` header inside core.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: The virtual user catalogue excludes any RPC scoped under virtualUser
4
+ description: deriveCatalogue drops every function whose scope begins with virtualUser, so a virtual user holding those scopes cannot start runs, read findings or set schedules through the catalogue
5
+ tags: core, virtual-users, catalogue, scopes
6
+ ---
7
+
8
+ # The virtual user catalogue excludes any RPC scoped under virtualUser
9
+
10
+ `packages/core/src/wirings/virtual-user/virtual-user-derive.ts` builds the catalogue of
11
+ RPCs a virtual user could reach. It skips a function whose scope list contains any
12
+ scope whose first segment is `virtualUser` (`virtual-user-derive.ts:38-46`, the
13
+ test at line 44).
14
+
15
+ The comment gives the reasons. A virtual user whose role carries `virtualUser:*` could
16
+ otherwise start more runs, read every run's findings (an adversarial run's
17
+ transcript is "working exploits against this same app"), and put a virtual user on a
18
+ schedule "that outlives it" (`virtual-user-derive.ts:39-43`).
19
+
20
+ The same function also skips scenario steps (`virtual-user-derive.ts:35`) and
21
+ functions that are not exposed (`virtual-user-derive.ts:37`). Those rules are
22
+ recorded in other notes.
23
+
24
+ **What this rules out:** a virtual user reaching the run-control or schedule RPCs
25
+ through its own catalogue, and an adversarial run reading its own findings.
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  type: decision
3
- title: An actor's missing approval decision defaults to denied
4
- description: Every pending tool call gets an explicit decision; an id the persona LLM omitted is denied, so a dropped field can never read as consent
5
- tags: actor-flow
3
+ title: A virtual user's missing approval decision defaults to denied
4
+ description: Every pending tool call gets an explicit decision; an id the virtual user LLM omitted is denied, so a dropped field can never read as consent
5
+ tags: virtual-user-conversation
6
6
  ---
7
7
 
8
- # An actor's missing approval decision defaults to denied
8
+ # A virtual user's missing approval decision defaults to denied
9
9
 
10
10
  `decideApprovals` in
11
- `packages/core/src/wirings/actor-flow/run-conversation.ts` maps over the
12
- target agent's `pendingApprovals`, not over the decisions the persona LLM
11
+ `packages/core/src/wirings/virtual-user/conversation/run-conversation.ts` maps over the
12
+ target agent's `pendingApprovals`, not over the decisions the virtual user LLM
13
13
  returned. Any `toolCallId` the model omitted, hallucinated, or renamed resolves
14
14
  to `approved: false`.
15
15
 
16
- The persona's decision comes from a structured LLM call, and structured output
16
+ The virtual user's decision comes from a structured LLM call, and structured output
17
17
  is not guaranteed to be complete. Iterating the model's array instead would make
18
18
  an omission indistinguishable from silence — and silence would let a scenario
19
19
  approve a destructive tool call nobody decided on, then pass.
@@ -0,0 +1,18 @@
1
+ ---
2
+ type: decision
3
+ title: Virtual user run records are privileged, and reads are gated behind their own scope
4
+ description: The generated read RPCs carry virtualUser:read while starting a run carries virtualUser:run, because adversarial findings are working exploits with live ids
5
+ tags: core, cli, virtual-users, scopes, security
6
+ ---
7
+
8
+ # Virtual user run records are privileged, and reads sit behind a scope
9
+
10
+ `packages/core/src/wirings/virtual-user/virtual-user-run-store.ts` defines where runs and their findings are kept. Its SECURITY note says findings from an `adversarial` run are "working exploits carrying live ids", so the store is "a privileged store". The scaffold "gates every read behind a scope for that reason", and a host that exposes these records more widely "is publishing its own exploits" (`virtual-user-run-store.ts:101-105`).
11
+
12
+ The gate is generated by the CLI, not written in core. `packages/cli/src/functions/wirings/virtual-user/serialize-virtual-user-functions.ts` emits `scopes: ['virtualUser:read']` on `getVirtualUserRun` (line 393), `listVirtualUserRuns` (line 414), `getVirtualUserRunSteps` (line 435) and `listVirtualUserSchedules` (line 477). Starting a run is `scopes: ['virtualUser:run']` (line 328). The three scopes are declared by `defineScope` in the same file (lines 308-320), and `virtual-user-functions.test.ts` asserts each one (lines 53, 66, 149, 150).
13
+
14
+ The disposition that makes findings sensitive is defined in `virtual-user.types.ts`, where `adversarial` "probes authorization and tenancy; a 2xx can be the finding" (`virtual-user.types.ts:15`).
15
+
16
+ A virtual user cannot reach these RPCs through the catalogue, because the catalogue excludes any function whose scope starts with `virtualUser` (`virtual-user-derive.ts:38-46`).
17
+
18
+ **What this rules out:** exposing run records, and their transcripts, to any caller who holds the virtual user's scopes but not `virtualUser:read`, and a virtual user starting or reading runs through its own catalogue.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: Virtual user sign-in endpoints do not exist in production
4
+ description: The plugin registers the statement, in-process and switcher endpoints only when the sign-in gate is open at construction, and creating a row is dev-only
5
+ tags: services
6
+ ---
7
+
8
+ # Virtual user sign-in endpoints do not exist in production
9
+
10
+ Virtual users never run in production, and the disposition and environment refusals
11
+ enforce that at request time. On top of them, the plugin resolves
12
+ `resolveVirtualUserSignIn()` once, at construction, and registers the statement
13
+ endpoint, `signInVirtualUserInternal` and the switcher only when the gate is open —
14
+ `pikku dev`, or the explicit stage opt-in. In production none of them is registered:
15
+ there is no route to refuse and no handler to reach through `auth.api`.
16
+
17
+ This replaces "always registered, refused per request" because a refusal is code that
18
+ has to be right on every request, while an endpoint that was never built cannot be
19
+ reached by a bug in it.
20
+
21
+ Creating a row stays under `pikku dev`. A stage signs in only to virtual users
22
+ provisioned at startup.
23
+
24
+ **What this rules out:** deciding at request time whether the endpoint exists, and
25
+ letting a stage create virtual user rows by signing in.
@@ -0,0 +1,42 @@
1
+ ---
2
+ type: decision
3
+ title: Virtual user sign-in has one door, reached two ways
4
+ description: Only the app's virtual user sign-in handler creates a virtual user session; runs reach it in-process and everything else reaches it with a signed statement
5
+ tags: services
6
+ ---
7
+
8
+ # Virtual user sign-in has one door, reached two ways
9
+
10
+ The `signIn` closure in the Better Auth virtual user plugin
11
+ (`packages/services/better-auth/src/virtual-user-plugin.ts`) is the only thing that
12
+ creates a virtual user session. It refuses any user row not flagged `virtualUser`,
13
+ creates a row only under `pikku dev` (`mayProvision`), and sets the session cookie.
14
+ Every route below ends in it; nothing else mints a session for a virtual user.
15
+
16
+ **Runs go in-process.** A run — started by a person, or by the scheduled tick as
17
+ `pikku-platform` holding only `virtualUser:run` — already executes inside the app, so
18
+ it has nothing to prove to itself. It calls `auth.api.signInVirtualUserInternal`, an
19
+ endpoint marked `metadata: { SERVER_ONLY: true }`: better-call leaves it out of the
20
+ router, so `auth.handler` answers 404 for its path while `auth.api` can still call it.
21
+ The seam is `services.auth`, which core already types structurally, so core imports
22
+ nothing from Better Auth and a project without it gets an error that says so.
23
+
24
+ **Everything outside the app presents a signed statement** — see
25
+ [an external caller presents one signed statement](an-external-virtual-user-caller-presents-one-signed-statement.md).
26
+ That covers the dev switcher, `pikku scenario run`, `pikku virtual-user run` and
27
+ `rpc`, `pikku pages screenshot --as`, and the Playwright provider.
28
+
29
+ Two choices fall out of this and are deliberate:
30
+
31
+ - **Cookie, not bearer.** The run takes the `Set-Cookie` values from the in-process
32
+ call into the cookie jar core already has, then drives the app over HTTP with it.
33
+ Apps do not register Better Auth's bearer plugin and this does not make them.
34
+ - **A public URL, not a service binding.** The run is a queue worker unit and the
35
+ existing bindings carry RPC, not HTTP. It reads `VIRTUAL_USER_API_URL` from
36
+ variables; `pikku dev` sets it once the port is bound, and a stage's deployer sets it
37
+ there. A missing value fails the run naming the variable. The same variable works on
38
+ a dev machine, a node box and a Worker.
39
+
40
+ **What this rules out:** a second sign-in route with its own checks, a credential that
41
+ is shared with, or derived for, a caller inside the app, and a bearer token handed
42
+ back to a run.
@@ -1,23 +1,25 @@
1
1
  ---
2
2
  type: decision
3
- title: Actor sign-in is proven by Set-Cookie, not a non-empty jar
4
- description: HttpScenarioActor tracks its own signedIn flag and requires the sign-in response itself to set a cookie, because a populated jar proves nothing
3
+ title: Virtual user sign-in is proven by Set-Cookie, not a non-empty jar
4
+ description: HttpVirtualUser tracks its own signedIn flag and both VirtualUserSignIn implementations require the sign-in response itself to set a cookie, because a populated jar proves nothing
5
5
  tags: services
6
6
  ---
7
7
 
8
- # Actor sign-in is proven by Set-Cookie, not a non-empty jar
8
+ # Virtual user sign-in is proven by Set-Cookie, not a non-empty jar
9
9
 
10
- `HttpScenarioActor` (`packages/core/src/services/http-scenario-actors.ts`) keeps
11
- a private `signedIn` boolean rather than inferring the session from its cookie
12
- jar, and `login()` throws when the sign-in response carries no `Set-Cookie`
13
- header even though the response was a 2xx.
10
+ `HttpVirtualUser` (`packages/core/src/services/http-virtual-users.ts`) keeps a
11
+ private `signedIn` boolean rather than inferring the session from its cookie
12
+ jar. Both `VirtualUserSignIn` implementations in
13
+ `packages/core/src/services/virtual-user-sign-in.ts` — `statementSignIn` and
14
+ `inProcessSignIn` — throw when the sign-in response carries no `Set-Cookie`
15
+ header, even though the response was a 2xx.
14
16
 
15
17
  A cookie jar cannot answer "did sign-in happen". A target app may set a cookie on
16
18
  any request — a CSRF token, an anonymous session, a locale — so a non-empty jar
17
19
  after a failed or skipped sign-in looks exactly like a successful one. What
18
20
  actually proves a session was established is _this_ response setting a cookie. A
19
21
  2xx alone is not enough either: an endpoint that returns 200 while quietly
20
- declining to issue a session would leave the actor running every subsequent
22
+ declining to issue a session would leave the virtual user running every subsequent
21
23
  request unauthenticated, and the scenario would report the resulting refusals as
22
24
  genuine permission failures.
23
25
 
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: decision
3
+ title: Virtual user sign-in only works for virtual-user-flagged users
4
+ description: A virtual user statement mints sessions for user rows flagged virtual user and nothing else, so holding one never impersonates a real user
5
+ tags: services
6
+ ---
7
+
8
+ # Virtual user sign-in only works for virtual-user-flagged users
9
+
10
+ A statement or an in-process call signs in only user rows flagged `virtualUser: true`
11
+ (see [virtual user sign-in has one door](virtual-user-sign-in-has-one-door.md)). That
12
+ could look like a master key, and it deliberately is not one — for two independent
13
+ reasons.
14
+
15
+ The first is the flag. The Better Auth virtual user plugin upserts and signs in only
16
+ rows flagged `virtualUser: true`. A statement naming a real customer's address does not
17
+ mint that customer's session — it is refused. The flag also flows into the minted
18
+ session, so audits and analytics can tell scenario traffic from human traffic.
19
+
20
+ The second is that a statement names one virtual user: `sub` must be a declared virtual
21
+ user, and it expires and is accepted once — see
22
+ [an external caller presents one signed statement](an-external-virtual-user-caller-presents-one-signed-statement.md).
23
+ A leaked statement opens one synthetic account for a few minutes, not the population.
24
+
25
+ **What this rules out:** widening the sign-in endpoint to accept any email "so
26
+ scenarios can test as a real user", and treating a statement as
27
+ equivalent to a session-signing key. It also rules out dropping the `virtualUser` flag
28
+ from the minted session — the audit trail's ability to separate synthetic from
29
+ real activity depends on it.