@pikku/core 0.12.141 → 0.12.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/CHANGELOG.md +33 -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/index.d.ts +2 -2
  11. package/dist/services/index.js +1 -1
  12. package/dist/services/meta-service.d.ts +6 -6
  13. package/dist/services/meta-service.js +8 -8
  14. package/dist/{wirings/workflow → services}/scenario-cookie-jar.d.ts +2 -0
  15. package/dist/{wirings/workflow → services}/scenario-cookie-jar.js +18 -14
  16. package/dist/services/system-role-guard.d.ts +1 -1
  17. package/dist/services/virtual-user-sign-in.d.ts +52 -0
  18. package/dist/services/virtual-user-sign-in.js +80 -0
  19. package/dist/services/virtual-user-statement.d.ts +31 -0
  20. package/dist/services/virtual-user-statement.js +24 -0
  21. package/dist/services/{personas-service.d.ts → virtual-users-service.d.ts} +25 -23
  22. package/dist/services/{personas-service.js → virtual-users-service.js} +3 -3
  23. package/dist/types/core.types.d.ts +11 -11
  24. package/dist/wirings/role/define-system-role.d.ts +1 -1
  25. package/dist/wirings/role/define-system-role.js +1 -1
  26. package/dist/wirings/{actor-flow/actor-flow.types.d.ts → virtual-user/conversation/conversation.types.d.ts} +5 -5
  27. package/dist/wirings/virtual-user/conversation/index.d.ts +2 -0
  28. package/dist/wirings/virtual-user/conversation/run-conversation.d.ts +36 -0
  29. package/dist/wirings/{actor-flow → virtual-user/conversation}/run-conversation.js +30 -30
  30. package/dist/wirings/virtual-user/define-virtual-users.d.ts +32 -0
  31. package/dist/wirings/{persona/define-personas.js → virtual-user/define-virtual-users.js} +9 -9
  32. package/dist/wirings/virtual-user/index.d.ts +30 -7
  33. package/dist/wirings/virtual-user/index.js +23 -4
  34. package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +8 -8
  35. package/dist/wirings/virtual-user/prepare-virtual-user-run.js +9 -9
  36. package/dist/wirings/virtual-user/run-virtual-user.d.ts +6 -6
  37. package/dist/wirings/virtual-user/run-virtual-user.js +10 -10
  38. package/dist/wirings/{persona/validate-personas.d.ts → virtual-user/validate-virtual-users.d.ts} +11 -11
  39. package/dist/wirings/{persona/validate-personas.js → virtual-user/validate-virtual-users.js} +24 -24
  40. package/dist/wirings/virtual-user/virtual-user-agents.d.ts +7 -7
  41. package/dist/wirings/virtual-user/virtual-user-agents.js +2 -2
  42. package/dist/wirings/{persona/persona-app-scopes.d.ts → virtual-user/virtual-user-app-scopes.d.ts} +10 -10
  43. package/dist/wirings/{persona/persona-app-scopes.js → virtual-user/virtual-user-app-scopes.js} +12 -12
  44. package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +3 -3
  45. package/dist/wirings/virtual-user/virtual-user-catalogue.js +2 -2
  46. package/dist/wirings/{persona/persona.types.d.ts → virtual-user/virtual-user-declaration.types.d.ts} +24 -24
  47. package/dist/wirings/virtual-user/virtual-user-derive.js +13 -7
  48. package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +1 -1
  49. package/dist/wirings/{persona/persona-email.d.ts → virtual-user/virtual-user-email.d.ts} +11 -11
  50. package/dist/wirings/{persona/persona-email.js → virtual-user/virtual-user-email.js} +19 -19
  51. package/dist/wirings/{persona/persona-environments.d.ts → virtual-user/virtual-user-environments.d.ts} +10 -10
  52. package/dist/wirings/{persona/persona-environments.js → virtual-user/virtual-user-environments.js} +21 -21
  53. package/dist/wirings/virtual-user/virtual-user-intents.d.ts +3 -3
  54. package/dist/wirings/virtual-user/virtual-user-intents.js +3 -3
  55. package/dist/wirings/{persona/persona-mailbox.d.ts → virtual-user/virtual-user-mailbox.d.ts} +4 -4
  56. package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +4 -4
  57. package/dist/wirings/virtual-user/virtual-user-scaffold.d.ts +42 -61
  58. package/dist/wirings/virtual-user/virtual-user-scaffold.js +69 -117
  59. package/dist/wirings/virtual-user/virtual-user-schedule-store.d.ts +15 -15
  60. package/dist/wirings/virtual-user/virtual-user-schedule.d.ts +6 -6
  61. package/dist/wirings/virtual-user/virtual-user-schedule.js +11 -11
  62. package/dist/wirings/virtual-user/virtual-user-target.d.ts +6 -6
  63. package/dist/wirings/virtual-user/virtual-user-target.js +5 -5
  64. package/dist/wirings/virtual-user/virtual-user.types.d.ts +11 -11
  65. package/dist/wirings/virtual-user/virtual-user.types.js +1 -1
  66. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +9 -9
  67. package/dist/wirings/workflow/pikku-scenario-service.d.ts +28 -28
  68. package/dist/wirings/workflow/pikku-scenario-service.js +77 -77
  69. package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -2
  70. package/dist/wirings/workflow/pikku-workflow-service.js +4 -4
  71. package/dist/wirings/workflow/scenario-prose.d.ts +8 -8
  72. package/dist/wirings/workflow/scenario-prose.js +12 -8
  73. package/dist/wirings/workflow/scenario-run.types.d.ts +13 -13
  74. package/dist/wirings/workflow/scenario-step.types.d.ts +23 -23
  75. package/dist/wirings/workflow/workflow.types.d.ts +2 -2
  76. package/knowledge/decisions/index.md +0 -2
  77. package/knowledge/decisions/internals/a-credential-name-declared-twice-must-agree-on-schema-and-type.md +29 -0
  78. package/knowledge/decisions/internals/a-grant-can-name-a-scope-declared-without-its-addon.md +29 -0
  79. package/knowledge/decisions/internals/a-scheduled-task-writes-its-caller-session-before-it-runs.md +22 -0
  80. package/knowledge/decisions/internals/a-secret-set-in-memory-shadows-the-variable-of-the-same-name.md +28 -0
  81. package/knowledge/decisions/internals/a-virtual-user-acts-through-invokeraw-so-a-refusal-is-data.md +32 -0
  82. package/knowledge/decisions/internals/a-virtual-user-cadence-is-a-row-not-a-timer.md +12 -12
  83. package/knowledge/decisions/internals/a-virtual-user-converse-signs-in-before-its-first-turn.md +24 -0
  84. package/knowledge/decisions/internals/a-virtual-user-email-is-computed-with-the-run-id-as-a-sub-address.md +34 -0
  85. package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-but-it-needs-a-trigger.md +2 -2
  86. package/knowledge/decisions/internals/an-agent-is-offered-under-its-registry-key-and-only-within-the-virtual-users-scopes.md +27 -0
  87. package/knowledge/decisions/internals/an-oauth2-credential-implies-an-optional-app-secret.md +26 -0
  88. package/knowledge/decisions/internals/analytics-identity-resolvers-run-in-declared-order.md +22 -0
  89. package/knowledge/decisions/internals/feature-flag-declarations-refuse-an-empty-anyof-and-conflicting-scopes.md +22 -0
  90. package/knowledge/decisions/internals/feature-flags-fail-open-and-never-enforce-access.md +28 -0
  91. package/knowledge/decisions/internals/flag-sync-rewrites-only-the-declared-text.md +24 -0
  92. package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +3 -3
  93. package/knowledge/decisions/internals/index.md +46 -31
  94. package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +10 -10
  95. package/knowledge/decisions/internals/role-names-cannot-contain-the-scope-separator.md +30 -0
  96. package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +23 -19
  97. package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +2 -2
  98. package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +12 -12
  99. package/knowledge/decisions/internals/the-core-scope-validator-refuses-separator-wildcard-and-empty-segments.md +22 -0
  100. package/knowledge/decisions/internals/the-pikku-user-id-resolves-in-a-fixed-order.md +24 -0
  101. 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
  102. 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
  103. package/knowledge/decisions/internals/validate-checks-virtual-users-through-a-shared-module.md +43 -0
  104. 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
  105. package/knowledge/decisions/internals/virtual-user-conversation-verdicts-are-llm-self-evaluations.md +25 -0
  106. package/knowledge/decisions/internals/virtual-user-conversations-seed-a-hidden-kickoff-message.md +23 -0
  107. package/knowledge/decisions/internals/virtual-users-are-declared-in-one-definevirtualusers-call-per-codebase.md +30 -0
  108. package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +5 -5
  109. package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +2 -2
  110. package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +4 -4
  111. package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
  112. package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +2 -2
  113. package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +1 -1
  114. package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +1 -1
  115. package/knowledge/decisions/security/a-bearer-token-checked-against-a-secret-reads-it-per-request.md +26 -0
  116. package/knowledge/decisions/security/a-call-without-a-session-raises-missing-session-not-forbidden.md +20 -0
  117. package/knowledge/decisions/security/a-caller-cookie-wins-over-the-jar-for-the-same-name.md +23 -0
  118. package/knowledge/decisions/security/a-channel-lifecycle-acts-as-the-socket-upgrade-session.md +16 -0
  119. package/knowledge/decisions/security/a-channel-requires-a-session-unless-it-says-auth-false.md +26 -0
  120. package/knowledge/decisions/security/a-credential-read-path-follows-its-declared-type-not-absence.md +32 -0
  121. package/knowledge/decisions/security/a-credential-type-is-a-literal-the-inspector-checks-at-declaration.md +27 -0
  122. package/knowledge/decisions/security/a-first-party-cookie-is-written-only-after-its-purposes-consent.md +33 -0
  123. package/knowledge/decisions/security/a-readonly-session-reaches-only-readonly-functions.md +25 -0
  124. package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md +10 -5
  125. package/knowledge/decisions/security/a-scheduled-virtual-user-run-needs-no-admin-at-run-time.md +27 -0
  126. package/knowledge/decisions/security/a-scoped-credential-view-is-limited-to-declared-names.md +26 -0
  127. package/knowledge/decisions/security/a-scoped-secret-view-reads-only-declared-keys-and-never-writes.md +25 -0
  128. package/knowledge/decisions/security/a-secret-is-sent-only-to-the-hosts-it-declares.md +29 -0
  129. package/knowledge/decisions/security/a-step-can-name-an-identity-no-virtual-user-holds-through-per-call-headers.md +25 -0
  130. package/knowledge/decisions/security/a-stopped-run-by-id-skips-the-ownership-check.md +24 -0
  131. package/knowledge/decisions/security/a-vault-secret-redacts-on-serialization-and-throws-on-coercion.md +29 -0
  132. package/knowledge/decisions/security/a-virtual-user-catalogue-entry-is-filtered-by-scope-and-approval-is-denied-by-default.md +30 -0
  133. package/knowledge/decisions/security/a-virtual-user-environment-fails-closed.md +38 -0
  134. package/knowledge/decisions/security/a-virtual-user-never-runs-against-production.md +23 -0
  135. package/knowledge/decisions/security/a-virtual-user-reads-its-roles-from-the-apps-own-rpc-before-better-auth.md +27 -0
  136. package/knowledge/decisions/security/a-virtual-user-run-treats-an-unresolved-environment-as-production.md +41 -0
  137. package/knowledge/decisions/security/a-virtual-user-scopes-are-expanded-from-its-roles-once-at-run-preparation.md +25 -0
  138. package/knowledge/decisions/security/a-virtual-user-whose-roles-drift-from-the-declaration-refuses-to-run.md +32 -0
  139. package/knowledge/decisions/security/addon-screen-scopes-hide-and-do-not-enforce.md +20 -0
  140. package/knowledge/decisions/security/an-addon-header-can-only-narrow-the-session.md +26 -0
  141. package/knowledge/decisions/security/an-addon-instance-reads-only-the-secrets-and-credentials-it-is-named.md +33 -0
  142. package/knowledge/decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md +16 -0
  143. package/knowledge/decisions/security/an-api-key-is-jwt-decoded-into-the-session.md +26 -0
  144. package/knowledge/decisions/security/an-external-virtual-user-caller-presents-one-signed-statement.md +38 -0
  145. package/knowledge/decisions/security/an-inbound-webhook-source-dispatches-only-verified-requests.md +31 -0
  146. package/knowledge/decisions/security/an-mcp-call-with-no-http-request-stays-anonymous.md +18 -0
  147. package/knowledge/decisions/security/an-omitted-user-id-addresses-the-platform-credential.md +32 -0
  148. package/knowledge/decisions/security/analytics-identity-is-stamped-from-the-session-not-the-event.md +24 -0
  149. package/knowledge/decisions/security/app-access-is-a-scope-tree-synthesised-from-virtual-users.md +26 -0
  150. package/knowledge/decisions/security/approval-policy-decides-who-answers-a-tool-call.md +27 -0
  151. package/knowledge/decisions/security/auth-false-on-a-session-function-warns-and-still-requires-one.md +25 -0
  152. package/knowledge/decisions/security/bearer-token-hashes-are-unpeppered-sha256.md +18 -0
  153. package/knowledge/decisions/security/consent-is-read-from-a-cookie-and-a-recognised-denial-counts-as-refusal.md +29 -0
  154. package/knowledge/decisions/security/cors-defaults-to-any-origin-and-refuses-the-wildcard-with-credentials.md +22 -0
  155. package/knowledge/decisions/security/credential-backends-share-one-conformance-suite-for-per-user-isolation.md +29 -0
  156. package/knowledge/decisions/security/each-stage-has-one-virtual-user-key-pair.md +27 -0
  157. package/knowledge/decisions/security/hmac-verification-fails-closed-on-empty-inputs.md +25 -0
  158. package/knowledge/decisions/security/index.md +88 -24
  159. package/knowledge/decisions/security/local-content-refuses-private-buckets-under-the-public-segment.md +21 -0
  160. package/knowledge/decisions/security/mail-is-allowlisted-before-a-virtual-user-run-sees-it.md +38 -0
  161. package/knowledge/decisions/security/mcp-resources-tools-and-prompts-take-sessionless-functions.md +18 -0
  162. package/knowledge/decisions/security/origin-locked-routes-match-the-origin-exactly.md +22 -0
  163. package/knowledge/decisions/security/oss-auth-works-without-pikku-studio-cloud.md +26 -0
  164. package/knowledge/decisions/security/outbound-webhooks-use-an-allowlist-when-configured.md +24 -0
  165. package/knowledge/decisions/security/pikku-carries-virtual-user-scopes-as-data-and-the-app-grants-them.md +35 -0
  166. package/knowledge/decisions/security/scoped-content-access-is-a-root-folder-with-longest-prefix-grants.md +31 -0
  167. package/knowledge/decisions/security/services-without-secrets-get-a-throwing-secrets-accessor.md +22 -0
  168. package/knowledge/decisions/security/shared-guards-refuse-to-mutate-or-shadow-a-system-role.md +34 -0
  169. package/knowledge/decisions/security/signed-uploads-are-verified-by-the-same-check-as-signed-reads.md +26 -0
  170. package/knowledge/decisions/security/system-role-scopes-must-be-declared-with-define-scope.md +24 -0
  171. package/knowledge/decisions/security/system-roles-are-additive-and-immutable.md +16 -0
  172. package/knowledge/decisions/security/the-channel-rpc-does-not-send-the-callers-session.md +21 -0
  173. package/knowledge/decisions/security/the-remote-jobs-inbox-fails-closed-without-a-secret.md +24 -0
  174. package/knowledge/decisions/security/the-remote-rpc-gate-fails-closed-and-binds-the-token-to-its-function.md +34 -0
  175. package/knowledge/decisions/security/the-workflow-status-stream-rechecks-ownership-on-every-poll.md +16 -0
  176. package/knowledge/decisions/security/two-credentials-on-one-key-must-share-a-read-mode.md +29 -0
  177. package/knowledge/decisions/security/verified-bearer-claims-are-pass-through-data-that-core-never-fills.md +23 -0
  178. package/knowledge/decisions/security/virtual-user-catalogue-excludes-the-machinery-that-runs-virtual-users.md +25 -0
  179. 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
  180. package/knowledge/decisions/security/virtual-user-run-records-are-gated-behind-a-read-scope.md +18 -0
  181. package/knowledge/decisions/security/virtual-user-sign-in-does-not-exist-in-production.md +25 -0
  182. package/knowledge/decisions/security/virtual-user-sign-in-has-one-door.md +42 -0
  183. 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
  184. package/knowledge/decisions/security/virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md +29 -0
  185. package/knowledge/decisions/security/workflow-helper-functions-are-sessionless-by-declaration.md +18 -0
  186. 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
  187. package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +5 -5
  188. 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
  189. package/knowledge/decisions/security/writing-a-virtual-user-schedule-is-gated-by-its-own-scope.md +18 -0
  190. package/knowledge/index.md +0 -2
  191. package/knowledge/questions/a-bearer-jwt-with-no-identity-becomes-a-session.md +24 -0
  192. package/knowledge/questions/dev-quick-login-as-impersonation.md +25 -0
  193. package/knowledge/questions/does-a-delayed-rpc-run-as-the-session-it-was-scheduled-with.md +26 -0
  194. package/knowledge/questions/does-a-user-id-with-a-colon-read-another-users-credentials.md +27 -0
  195. package/knowledge/questions/does-a-wirecli-command-need-a-session-by-default.md +26 -0
  196. package/knowledge/questions/does-an-adversarial-in-scope-success-count-as-a-finding.md +26 -0
  197. package/knowledge/questions/does-approve-step-need-the-run-owners-session-or-the-gates-approvers.md +34 -0
  198. package/knowledge/questions/does-cors-origin-true-reflect-any-origin-with-credentials.md +20 -0
  199. package/knowledge/questions/does-skip-user-session-do-anything-on-a-channel.md +24 -0
  200. package/knowledge/questions/does-the-store-session-match-the-other-two.md +19 -0
  201. package/knowledge/questions/index.md +25 -2
  202. package/knowledge/questions/is-the-agent-score-worker-reachable-without-a-session.md +21 -0
  203. package/knowledge/questions/live-scheduled-tests-may-write.md +19 -0
  204. package/knowledge/questions/provision-virtual-users-has-no-caller-in-this-repo.md +23 -0
  205. package/knowledge/questions/remote-secret-refuses-requests-without-a-remote-token-on-every-path.md +32 -0
  206. package/knowledge/questions/should-a-middleware-set-session-persist-to-the-store.md +25 -0
  207. package/knowledge/questions/should-a-scoped-secret-view-throw-from-has-secret-for-a-disallowed-key.md +23 -0
  208. package/knowledge/questions/should-a-wrong-bearer-token-be-anonymous-or-refused.md +35 -0
  209. package/knowledge/questions/should-an-empty-webhook-signing-secret-send-unsigned.md +23 -0
  210. package/knowledge/questions/should-http-virtual-user-sessions-reset-between-scenarios-in-one-run.md +27 -0
  211. package/knowledge/questions/should-the-cookie-jar-scope-cookies-to-the-api-origin.md +26 -0
  212. package/knowledge/questions/should-voice-input-fetch-audio-urls-without-a-host-allowlist.md +20 -0
  213. package/knowledge/questions/staging-impersonation-rules.md +27 -0
  214. package/knowledge/questions/virtual-user-keys-on-cloud-stages-are-pikku-studios-to-build.md +34 -0
  215. package/knowledge/questions/virtual-user-statement-replay-is-per-process.md +20 -0
  216. package/knowledge/questions/what-an-impersonated-session-carries.md +22 -0
  217. package/package.json +2 -3
  218. package/src/public-surface.json +38 -47
  219. package/dist/services/persona-actor-secret.d.ts +0 -38
  220. package/dist/services/persona-actor-secret.js +0 -39
  221. package/dist/services/persona-sign-in.d.ts +0 -117
  222. package/dist/services/persona-sign-in.js +0 -154
  223. package/dist/wirings/actor-flow/index.d.ts +0 -2
  224. package/dist/wirings/actor-flow/run-conversation.d.ts +0 -36
  225. package/dist/wirings/persona/define-personas.d.ts +0 -32
  226. package/dist/wirings/persona/index.d.ts +0 -23
  227. package/dist/wirings/persona/index.js +0 -19
  228. package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +0 -23
  229. package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +0 -25
  230. package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +0 -43
  231. package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +0 -31
  232. package/knowledge/decisions/security/an-actor-credential-is-derived-per-persona.md +0 -42
  233. package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +0 -26
  234. /package/dist/wirings/{actor-flow/actor-flow.types.js → virtual-user/conversation/conversation.types.js} +0 -0
  235. /package/dist/wirings/{actor-flow → virtual-user/conversation}/index.js +0 -0
  236. /package/dist/wirings/{persona/persona.types.js → virtual-user/virtual-user-declaration.types.js} +0 -0
  237. /package/dist/wirings/{persona/persona-mailbox.js → virtual-user/virtual-user-mailbox.js} +0 -0
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: decision
3
+ title: A secret may only be sent to the hosts its declaration names
4
+ description: assertSecretAllowedForHost refuses a host outside a secret's allowedHosts, and refuses any host at all for a secret with no allowedHosts when the project requires them
5
+ tags: core, secrets, egress
6
+ ---
7
+
8
+ # A secret may only be sent to the hosts its declaration names
9
+
10
+ `packages/core/src/services/secret-host-binding.ts` holds the check
11
+ `assertSecretAllowedForHost`. It looks up the secret's definition by id and reads
12
+ its `allowedHosts` (`secret-host-binding.ts:36-39`).
13
+
14
+ - No `allowedHosts`, and `requireAllowedHosts` is set: throw
15
+ (`secret-host-binding.ts:41-44`).
16
+ - No `allowedHosts`, and the flag is not set: allowed (`secret-host-binding.ts:45`).
17
+ - `allowedHosts` present: the host must match at least one entry, or throw
18
+ (`secret-host-binding.ts:48-50`).
19
+
20
+ Matching (`secret-host-binding.ts:18-26`) is case-insensitive. An entry is either
21
+ an exact host or a single leading `*.` wildcard. The wildcard requires a
22
+ non-empty label before the dot (`secret-host-binding.ts:23`), so `*.example.com`
23
+ does not match `example.com` itself.
24
+
25
+ The error message tells the author to add the host to `allowedHosts` on the
26
+ `defineSecret` if the send is intended (`secret-host-binding.ts:11`).
27
+
28
+ **What this rules out:** sending a secret to a host its declaration does not
29
+ name, and a bare-domain match against a wildcard entry.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: A scenario step can name an identity no virtual user holds, through per-call headers
4
+ description: ScenarioInvokeOptions.headers lets a step send an impersonation or shim header on one call, and a virtual-user run never gets that option
5
+ tags: core, virtual-users, scenario
6
+ ---
7
+
8
+ # A scenario step can name an identity no virtual user holds, through per-call headers
9
+
10
+ `ScenarioInvokeOptions` in `packages/core/src/services/virtual-users-service.ts` says
11
+ its headers are how "a step expresses an identity no declared virtual user can — an
12
+ impersonation header, or one of the header-shim principals a credential scenario
13
+ invents." `HttpVirtualUser.invokeRaw` passes them to `postRpc`
14
+ (`packages/core/src/services/http-virtual-users.ts`), so they go out on that one call
15
+ alongside the virtual user's session.
16
+
17
+ The option belongs to the scenario author, who writes the steps and is trusted to
18
+ say which identity a check needs. A virtual-user run does not get it. Its target in
19
+ `packages/core/src/wirings/virtual-user/virtual-user-target.ts` calls
20
+ `virtualUser.invokeRaw(rpcName, args)` with no options, so an adversarial run cannot
21
+ add a header to reach an identity its virtual user does not hold.
22
+
23
+ **What this rules out:** giving a virtual-user run a per-call header, and removing the
24
+ option from scenario steps on the grounds that a declared virtual user should be the
25
+ only identity a step can use.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: Stopping an agent run by its id skips the ownership check; interruptAgent is the checked path
4
+ description: signalRunInterrupt is exported and performs no authorisation, while interruptAgent asserts the run's resource owner before it signals
5
+ tags: core, agent, security
6
+ ---
7
+
8
+ # Stopping an agent run by its id skips the ownership check; interruptAgent is the checked path
9
+
10
+ `signalRunInterrupt` in `packages/core/src/wirings/agent/agent-interrupt.ts:227-236`
11
+ looks up the run and aborts it, with no check on who is asking. Its doc comment at
12
+ `:215-220` says so directly: anyone holding a `runId` can stop the run, and callers
13
+ should use `interruptAgent` unless the run's ownership is already established.
14
+
15
+ It is exported from the agent entry point (`packages/core/src/wirings/agent/index.ts:33`).
16
+
17
+ `interruptAgent` in `packages/core/src/wirings/agent/agent-stream.ts:1061-1086`
18
+ loads the run (`:1070`), asserts the resource owner with `assertResourceOwner`
19
+ (`:1074-1082`), and only then calls `signalRunInterrupt` (`:1084`). The RPC wrapper
20
+ calls `interruptAgent` (`packages/core/src/wirings/agent/agent-rpc.ts:98`).
21
+
22
+ **What this rules out:** exposing `signalRunInterrupt` to a caller who has not
23
+ resolved the run's owner, and reaching a run through its id alone on a request
24
+ path.
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: decision
3
+ title: A vault secret redacts when serialized and throws when coerced to a string
4
+ description: SecretValue serializes to a redacted marker so logs stay honest without crashing, but string coercion throws, and reveal() is the only way to read the value
5
+ tags: core, secrets, classification
6
+ ---
7
+
8
+ # A vault secret redacts when serialized and throws when coerced to a string
9
+
10
+ `packages/core/src/classification/secret-value.ts` wraps a vault value in
11
+ `SecretValue`. The raw value sits in a private field (`secret-value.ts:37`) and
12
+ is returned only by `reveal()` (`secret-value.ts:43-45`).
13
+
14
+ Two paths out of the wrapper behave differently on purpose:
15
+
16
+ - Structured serialization is redacted. `toJSON` and the Node inspect hook both
17
+ return `REDACTED`, which is `[secret]` (`secret-value.ts:5`, `47-53`). The doc
18
+ comment says an audit or log write "must stay honest about the field without
19
+ crashing the request" (`secret-value.ts:29-30`).
20
+ - String coercion throws. `toString` and `Symbol.toPrimitive` both throw
21
+ `SecretCoercionError` (`secret-value.ts:55-61`). The comment says "a template
22
+ literal or concatenation is always a leak" (`secret-value.ts:31`).
23
+
24
+ The `Safe<T>` type (`secret-value.ts:91-106`) rejects a `SecretValue` anywhere
25
+ in a type, for sinks typed `any`, `unknown` or a free generic.
26
+
27
+ **What this rules out:** reading a secret without calling `reveal()`, putting a
28
+ secret into a template literal or string concatenation, and a logger that
29
+ crashes on a secret field instead of redacting it.
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: decision
3
+ title: Virtual users are offered calls by declared scope, and approval-gated calls are denied by default
4
+ description: The catalogue type filters by the declared scopes of each RPC rather than by permission functions, and the catalogue filter drops approval-required entries unless the caller opts in
5
+ tags: core, virtual-users, scopes, approval
6
+ ---
7
+
8
+ # Virtual users are offered calls by declared scope, and approval-gated calls are denied by default
9
+
10
+ `packages/core/src/wirings/virtual-user/virtual-user.types.ts` defines the
11
+ catalogue entry a virtual user is offered.
12
+
13
+ The `scopes` field lists the scopes the session must hold to call the RPC, all of
14
+ them (`virtual-user.types.ts:92-99`). The doc comment explains why scopes and not
15
+ permission functions: a scope is "declared and checkable", while "a permission is
16
+ arbitrary code whose verdict nobody can predict without running it"
17
+ (`virtual-user.types.ts:95-97`).
18
+
19
+ `approvalRequired` is documented as "denied by default" (`virtual-user.types.ts:89-90`).
20
+ The filter in `virtual-user-catalogue.ts` enforces this. It drops an entry when
21
+ `approvalRequired` is set and the caller has not passed `allowApprovalRequired`
22
+ (`virtual-user-catalogue.ts:113`), and that flag defaults to `false`
23
+ (`virtual-user-catalogue.ts:95`).
24
+
25
+ The same filter drops any entry the session's scopes do not cover
26
+ (`virtual-user-catalogue.ts:115-117`).
27
+
28
+ **What this rules out:** a virtual user calling an approval-gated RPC unless the
29
+ run explicitly allows it, and a permission function being used to decide what a
30
+ virtual user can see.
@@ -0,0 +1,38 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user's sign-in is refused unless the environment is known and allowed
4
+ description: Virtual user provisioning, sync and sign-in refuse when PIKKU_ENV is unresolved, unknown, production without the accountable disposition, or outside the virtual user's environments, and production environments are omitted by default
5
+ tags: core, virtual-users, environments, security
6
+ ---
7
+
8
+ # A virtual user's sign-in is refused unless the environment is known and allowed
9
+
10
+ `virtualUserEnvironmentRefusal`
11
+ (`packages/core/src/wirings/virtual-user/virtual-user-environments.ts`) fails
12
+ closed when:
13
+
14
+ - `PIKKU_ENV` is unresolved;
15
+ - `PIKKU_ENV` names an environment the config does not know;
16
+ - the environment is production and the virtual user's disposition is not
17
+ `PRODUCTION_DISPOSITION` (`accountable`);
18
+ - the environment is not one the virtual user may target.
19
+
20
+ `resolveVirtualUserEnvironments` leaves production environments out when a
21
+ virtual user declares no `environments`. A virtual user reaches production only
22
+ when someone has named it there. The build-time half is
23
+ `virtualUserEnvironmentErrors`, which the doc comment says the inspector runs;
24
+ the run-time half is re-checked because "the run check does not trust which
25
+ artifact got deployed".
26
+
27
+ The refusal is applied by `pikku virtual-user sync`
28
+ (`packages/cli/src/functions/commands/virtual-user-sync.ts`), by the
29
+ `pikku virtual-user` command (`packages/cli/src/functions/commands/virtual-user.ts`)
30
+ and by `provisionVirtualUsers`
31
+ (`packages/services/better-auth/src/provision-virtual-users.ts`).
32
+
33
+ **What this rules out:** provisioning or signing in virtual users when the target
34
+ environment is ambiguous, and a production stage that carries virtual users it was
35
+ never meant to have.
36
+
37
+ Runs are stricter: [A virtual user never runs against production](a-virtual-user-never-runs-against-production.md)
38
+ refuses even the `accountable` disposition.
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user never runs against production, whatever its disposition
4
+ description: startVirtualUserRun and an enabling writeVirtualUserSchedule both refuse with a 403 in production, including for the accountable disposition; an unresolved environment counts as production
5
+ tags: core, virtual-users, environments, security
6
+ ---
7
+
8
+ # A virtual user never runs against production, whatever its disposition
9
+
10
+ `refuseInProduction` (`packages/core/src/wirings/virtual-user/virtual-user-scaffold.ts:192`) throws a `ForbiddenError`, "Virtual users do not run against production", whenever the process is a production run. It has no disposition exception: `accountable`, which the virtual user environment check still accepts at sign-in in production, is refused here too.
11
+
12
+ It is called from two places:
13
+
14
+ - `startVirtualUserRun` (`virtual-user-scaffold.ts:232`), before the run record is created, so a refused run leaves no row.
15
+ - `writeVirtualUserSchedule` (`virtual-user-scaffold.ts:410`), only when the row is or becomes enabled. Disabling a schedule, or editing one that is off, is still allowed in production so a leftover schedule can be stopped. A scheduled tick dispatches `runVirtualUser` through `rpc.invoke` (`packages/cli/src/functions/wirings/virtual-user/serialize-virtual-user-functions.ts:563-566`), and that function calls `startVirtualUserRun` (same file, line 337), so a due tick is refused as well.
16
+
17
+ "Production" is decided by `isProductionRun` (`virtual-user-scaffold.ts:179`). The configured `PIKKU_ENV` environment wins when the project configures environments, and an unresolved environment counts as production. `NODE_ENV` decides only for a project that configures none.
18
+
19
+ The sign-in environment rule is separate and unchanged. See [A virtual user's sign-in is refused unless the environment is known and allowed](a-virtual-user-environment-fails-closed.md): signing an `accountable` virtual user in against production is still allowed there, and starting a run is not.
20
+
21
+ The tests are in `virtual-user-scaffold.test.ts`: "refuses every disposition in production", "the accountable disposition is refused in production too", "an accountable cadence is refused in production too" and "disabling or editing an off cadence is allowed in production".
22
+
23
+ **What this rules out:** a virtual user, probing or not, acting on real customers' data, and an enabled schedule that can only ever fail to start in production.
@@ -0,0 +1,27 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user reads its roles from the app's own RPC before better-auth's user.role
4
+ description: sessionRoles asks getMyScopes first, then get-session, and returns null rather than an empty list when the stage reports no roles
5
+ tags: core, virtual-users, auth
6
+ ---
7
+
8
+ # A virtual user reads its roles from the app's own RPC before better-auth's user.role
9
+
10
+ `HttpVirtualUser.sessionRoles` (`packages/core/src/services/http-virtual-users.ts`) answers
11
+ "what roles does this session hold" in a fixed order. First the app's exposed RPC,
12
+ which defaults to `getMyScopes` and can be renamed or switched off with `rolesRpc`.
13
+ Only then better-auth's `get-session`, where its admin plugin puts `role` on the
14
+ user.
15
+
16
+ The `rolesRpc` doc comment gives the reason. In an app that authorizes on scopes,
17
+ `user.role` is "a projection rather than the model": it exists so better-auth's
18
+ admin endpoints have something to read, and it may be absent. A virtual user verified
19
+ against it is verified against a copy.
20
+
21
+ Two answers are kept apart. A stage that answers neither returns `null`, which
22
+ means "this stage does not report roles". A session that carries a user with no
23
+ role field returns `[]`, which means "this person has none". The doc comment says
24
+ the two call for opposite responses from the caller.
25
+
26
+ **What this rules out:** making better-auth's `user.role` the first source of a
27
+ virtual user's roles, and reporting `[]` for a stage that could not answer at all.
@@ -0,0 +1,41 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual-user run treats an unresolved PIKKU_ENV as production
4
+ description: The virtual-user scaffold decides "production" from the configured PIKKU_ENV environment, counts an unresolved one as production, and falls back to NODE_ENV only when no environments are configured
5
+ tags: core, virtual-users, environments, security
6
+ ---
7
+
8
+ # A virtual-user run treats an unresolved PIKKU_ENV as production
9
+
10
+ `startVirtualUserRun` calls `refuseInProduction` before it creates a run record
11
+ (`packages/core/src/wirings/virtual-user/virtual-user-scaffold.ts:232`). `PIKKU_ENV`
12
+ is read as the default for `environment` (`virtual-user-scaffold.ts:217`). The
13
+ schedule writer runs the same check when the schedule is or becomes enabled
14
+ (`virtual-user-scaffold.ts:399`, `:409-411`).
15
+
16
+ `refuseInProduction` throws a `ForbiddenError` whenever `isProductionRun` is true,
17
+ whatever the disposition (`virtual-user-scaffold.ts:192-204`). That rule is
18
+ [a virtual user never runs against production](a-virtual-user-never-runs-against-production.md);
19
+ this note is about how "production" is decided.
20
+
21
+ `isProductionRun` decides it as follows (`virtual-user-scaffold.ts:179-188`):
22
+
23
+ - With no environments configured, it is `NODE_ENV === 'production'` (`:184-186`).
24
+ - With environments configured and `PIKKU_ENV` unset, it is true (`:187`,
25
+ `environment ? ... : true`).
26
+ - With `PIKKU_ENV` set, it is that environment's `production` flag (`:187`). An
27
+ unknown name evaluates to false in this check. `virtualUserEnvironmentRefusal`
28
+ refuses unknown names at sign-in instead
29
+ (`packages/core/src/wirings/virtual-user/virtual-user-environments.ts:113-119`).
30
+
31
+ The comment gives the reasons. The configured environment wins over `NODE_ENV`
32
+ because a production mirror runs `NODE_ENV=production` too
33
+ (`virtual-user-scaffold.ts:166-172`). An unresolved environment counts as
34
+ production because nobody can vouch for its data (`:174-177`).
35
+
36
+ See also [a virtual user's sign-in is refused unless the environment is known and allowed](a-virtual-user-environment-fails-closed.md),
37
+ which covers the sign-in refusal in `virtual-user-environments.ts`.
38
+
39
+ **What this rules out:** starting a run when the environment cannot be named, and
40
+ keying the scaffold's refusal on `NODE_ENV` in a project that has configured
41
+ environments.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user's scopes are expanded from its roles once, at run preparation
4
+ description: A virtual user's scope set is computed from its declared roles against the defineSystemRole definitions when the run is prepared, and that set is the oracle for scope drift
5
+ tags: core, virtual-user, auth
6
+ ---
7
+
8
+ # A virtual user's scopes are expanded from its roles once, at run preparation
9
+
10
+ `virtualUserScopes` in `packages/core/src/wirings/virtual-user/prepare-virtual-user-run.ts`
11
+ expands a virtual user's `roles` into scopes through the role definitions the seed
12
+ grants from. Its doc comment says roles are the only thing a virtual user declares, and
13
+ that the expansion happens "once, here, against the same `defineSystemRole`
14
+ definitions the seed grants from".
15
+
16
+ The result feeds two things in `run-virtual-user.ts`. It narrows the catalogue
17
+ for a non-adversarial user, and it is the oracle: a call that succeeds on an
18
+ endpoint whose `scopes` the virtual user does not hold is recorded as
19
+ `unexpected-success`, which the code calls authorization drift. Using the static
20
+ expansion as the truth is what makes that finding mean something, because the
21
+ run never asks the target what the virtual user may do.
22
+
23
+ **What this rules out:** building a virtual user's catalogue or judging its
24
+ successes from a scope list fetched from the live session, which would make the
25
+ oracle agree with whatever the target currently grants.
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user whose granted roles drift from its declaration refuses to run
4
+ description: verifyVirtualUserRoles compares declared and granted roles in both directions before a run, and roleMismatchMessage produces the refusal text; a provider login is never runnable
5
+ tags: core, virtual-users, roles, drift
6
+ ---
7
+
8
+ # A virtual user whose granted roles drift from its declaration refuses to run
9
+
10
+ `packages/core/src/wirings/virtual-user/validate-virtual-users.ts` has the role check that
11
+ runs before a virtual user's first step. `verifyVirtualUserRoles` computes what is missing
12
+ and what is extra, in both directions (`validate-virtual-users.ts:94-111`). `roleMismatchMessage`
13
+ returns `null` when they match, and otherwise a message that ends with "Refusing
14
+ to run" (`validate-virtual-users.ts:114-133`, the text at `validate-virtual-users.ts:131`).
15
+
16
+ The doc comment says the declaration is authoritative, but a run may target a
17
+ stage that the generated seed never touched (`validate-virtual-users.ts:84-87`). It
18
+ gives a reason for each direction: an under-granted virtual user reports seed drift
19
+ as an authorization bug, and an over-granted one "silently stops testing the
20
+ boundary it was written to test" (`validate-virtual-users.ts:89-92`).
21
+
22
+ Nothing in `packages/core/src` calls `roleMismatchMessage`. The caller that
23
+ stops the run is the `pikku virtual-user` command
24
+ (`packages/cli/src/functions/commands/virtual-user.ts`), so this file only
25
+ supplies the check and the message.
26
+
27
+ `isRunnableVirtualUser` (`validate-virtual-users.ts:19-27`) returns false for a virtual user
28
+ with `runnable: false`, and for any virtual user with an account provider, because
29
+ driving a provider consent screen "needs a human" (`validate-virtual-users.ts:10-12`).
30
+
31
+ **What this rules out:** running a virtual user whose roles no longer match what
32
+ it declared, and a provider-login virtual user being driven by a run.
@@ -0,0 +1,20 @@
1
+ ---
2
+ type: decision
3
+ title: Addon screen scopes hide a screen and do not enforce access
4
+ description: The scopes on a defineScreens entry state what a viewer must hold to see the screen; the addon's own functions are the access check
5
+ tags: core, addons, screens, security
6
+ ---
7
+
8
+ # Addon screen scopes hide a screen and do not enforce access
9
+
10
+ `packages/core/src/wirings/addon/define-screens.ts:7-8` documents `scopes` on an
11
+ addon screen as required of the viewer to see it, and says the addon's functions
12
+ must enforce the same scopes, because hiding a screen is not access control.
13
+
14
+ The field is read statically by `pikku all`, which is why `scopes` must be a
15
+ literal (`define-screens.ts:32`). Within `packages/core/src`, no code reads a
16
+ screen's `scopes` at runtime; `wire-addon.ts:47-48` mounts the screens under
17
+ `/addons/<name>` and is the only other place they are referenced.
18
+
19
+ **What this rules out:** relying on a screen's scopes to keep data from a viewer,
20
+ and an addon shipping a function that trusts the screen to have been hidden.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: An addon header can only narrow the session, and an unknown addon is refused
4
+ description: addon-scope-cap intersects the session's scopes with the named addon's role, so naming an addon never widens access, and a name the host does not know is a 403
5
+ tags: core, middleware, scopes, addons
6
+ ---
7
+
8
+ # An addon header can only narrow the session, and an unknown addon is refused
9
+
10
+ `packages/core/src/middleware/addon-scope-cap.ts` reads the `x-pikku-addon`
11
+ header (`addon-scope-cap.ts:8`, `31`). When a session is present, it resolves
12
+ the addon's role and replaces the session's scopes with
13
+ `intersectScopes(session.scopes, role)` (`addon-scope-cap.ts:37-45`).
14
+
15
+ The doc comment says the header "is a selector, not a credential: naming an
16
+ addon can only reduce a session" (`addon-scope-cap.ts:16-17`). An addon the host
17
+ does not know throws `ForbiddenError` (`addon-scope-cap.ts:38-40`), because the
18
+ comment says ignoring it "would let a misspelt name run uncapped"
19
+ (`addon-scope-cap.ts:18-19`).
20
+
21
+ With no header, or no session, the middleware calls `next()` and changes
22
+ nothing (`addon-scope-cap.ts:33-34`). The comment says to register it after the
23
+ session middleware (`addon-scope-cap.ts:21-22`).
24
+
25
+ **What this rules out:** using the header to gain scopes, and treating an
26
+ unrecognised addon name as "no cap".
@@ -0,0 +1,33 @@
1
+ ---
2
+ type: decision
3
+ title: An addon instance reads only the secrets and credentials it is named
4
+ description: An addon's secret and credential views are its declared names plus the consuming app's grants and override keys; the global flags opt out of scoping
5
+ tags: core, addons, secrets, credentials
6
+ ---
7
+
8
+ # An addon instance reads only the secrets and credentials it is named
9
+
10
+ The consuming app decides which secrets and credentials an addon instance may
11
+ read. The grant fields are declared on the wiring as "set by the consuming app"
12
+ (`packages/core/src/wirings/rpc/rpc-types.ts:73-76`) and carried through
13
+ `wireAddon` (`packages/core/src/wirings/addon/wire-addon.ts:123-126`) into the
14
+ instance (`packages/core/src/wirings/addon/addon-runner.ts:20-22`).
15
+
16
+ The allowed set is built by `allowedNames` in `addon-runner.ts:40-49`: the names
17
+ the package declared, plus the grants, plus the keys of the overrides. An
18
+ override key therefore grants the name; the override value is the key the
19
+ lookup is pointed at. `wire-addon.ts:71` says the grant check runs before
20
+ `secretOverrides` rename a name.
21
+
22
+ Scoping is applied when the instance's services are built
23
+ (`addon-runner.ts:200-227`). Unless `globalSecrets` is set, the secret service
24
+ is wrapped in `ScopedSecretService`; unless `globalCredentials` is set, the
25
+ credential service is wrapped in `ScopedCredentialService`. The global options
26
+ hand over the unscoped service, and their value is a reason string
27
+ (`wire-addon.ts:75-82`). The comments say the reason is recorded in the deploy
28
+ manifest; that recording was not traced here.
29
+
30
+ **What this rules out:** an addon reading a name neither it declared nor the
31
+ host named, including through an override value; and an unscoped service
32
+ reaching an addon without the consuming app setting a `globalSecrets` or
33
+ `globalCredentials` reason.
@@ -0,0 +1,16 @@
1
+ ---
2
+ type: decision
3
+ title: An addon may reach only itself and the addons it declares in uses
4
+ description: An RPC call that arrives tagged with an addon header is refused unless the target is the caller or one of the caller's declared uses, and an unknown addon name is refused outright
5
+ tags: core, addons, rpc, scopes
6
+ ---
7
+
8
+ # An addon may reach only itself and the addons it declares in uses
9
+
10
+ `addonMayReach` in `packages/core/src/wirings/addon/addon-reach.ts` takes the caller addon and the target function name (`addon-reach.ts:6`). It takes the target from the part of the name before `:` (`addon-reach.ts:7`). It returns `false` for an empty target (`:8`), `true` when the target is the caller (`:9`), and otherwise `true` only when the caller's declared `uses` includes the target (`:10-11`).
11
+
12
+ `addonCallerOf` reads the addon header from the HTTP request (`addon-reach.ts:14-16`). A name that is not a registered addon is refused with `ForbiddenError` (`addon-reach.ts:17-18`).
13
+
14
+ `ContextAwareRPCService.rpcExposed` applies the check before resolving the function. A caller that fails `addonMayReach` gets `AddonNotUsedError` (`packages/core/src/wirings/rpc/rpc-runner.ts:189-192`).
15
+
16
+ **What this rules out:** an addon calling into another addon it does not declare in `uses`, and an addon header naming an unregistered addon being treated as anonymous rather than refused.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: An API key is JWT-decoded into the session, from a header or a query parameter
4
+ description: authAPIKey reads x-api-key or the apiKey query parameter, decodes it as a JWT, and sets the session only when no session exists yet
5
+ tags: core, auth, middleware, api-keys
6
+ ---
7
+
8
+ # An API key is JWT-decoded into the session, from a header or a query parameter
9
+
10
+ `packages/core/src/middleware/auth-apikey.ts` takes an API key from the
11
+ `x-api-key` header, the `apiKey` query parameter, or both, depending on the
12
+ `source` option (`auth-apikey.ts:11-13`, `21-27`). The header is tried first;
13
+ the query is read only when no header value was found (`auth-apikey.ts:25`).
14
+
15
+ The key is decoded with `jwtService.decode` and, if that returns a value, passed
16
+ to `setSession` (`auth-apikey.ts:29-33`). The middleware does nothing when a
17
+ session already exists (`auth-apikey.ts:17`), which the doc comment describes as
18
+ leaving "an existing session alone, so it composes with other auth middleware"
19
+ (`auth-apikey.ts:6-7`).
20
+
21
+ Nothing is decoded when no `jwtService` is wired (`auth-apikey.ts:29`).
22
+
23
+ **What this rules out:** an API key overriding a session that is already set,
24
+ and a key being accepted without a JWT service to verify it. The code also
25
+ accepts a key from the query string (`auth-apikey.ts:26`), so keys can appear in
26
+ URLs when `source` includes `query`.
@@ -0,0 +1,38 @@
1
+ ---
2
+ type: decision
3
+ title: An external virtual user caller presents one signed statement
4
+ description: Callers outside the app sign an EdDSA statement naming the virtual user; the app verifies it against a public key and accepts each statement once
5
+ tags: services
6
+ ---
7
+
8
+ # An external virtual user caller presents one signed statement
9
+
10
+ The statement is an EdDSA (Ed25519) JWT: `sub` is the virtual user id, `aud` is the
11
+ app's origin, `iat`, `exp` a few minutes out (default 180 seconds, ceiling 300), and
12
+ `jti`, a one-time id. The CLI signs it with `node:crypto`; the app verifies it with
13
+ `JoseVirtualUserStatementVerifier` (`packages/services/jose`), `jwtVerify` pinned to
14
+ `algorithms: ['EdDSA']`, against a public key. `POST /sign-in/virtual-user` takes
15
+ `{ statement }`, maps `sub` to a declared virtual user, and signs it in.
16
+
17
+ It is rejected for a wrong algorithm (`none` and HS256 included), a wrong key, a wrong
18
+ audience, expiry, a lifetime beyond the ceiling, a missing `sub` or `jti`, and a `jti`
19
+ already accepted. The replay store is a per-process map of `jti` to expiry, purged on
20
+ each call, so it never outgrows the last few minutes of statements.
21
+
22
+ One statement replaces three older credentials — a secret derived per virtual user from
23
+ a root, an operator token with impersonation, and an app-wired sign-in hook — because
24
+ each of them put something the app could mint a session from in the hands of a caller,
25
+ or in the app's own configuration. A public key lets the app check a statement and
26
+ nothing else; capturing the app's environment yields no ability to sign one.
27
+
28
+ The **id-only dev switcher** (`/sign-in/switch-virtual-user`) is unchanged under
29
+ `pikku dev`. Anywhere else it also needs a `statement` naming the same id, so no
30
+ deployed stage has a credential-free way in.
31
+
32
+ The statement endpoint needs the declared virtual users to map an id to an address, so
33
+ it is registered only when the app passes `virtualUserSignIn.virtualUsers`, which the
34
+ generated `virtualUserList` supplies.
35
+
36
+ **What this rules out:** the app holding a private key, a statement without an
37
+ expiry or one-time id, an algorithm negotiated by the token, and a switcher that works
38
+ on a stage without proof.
@@ -0,0 +1,31 @@
1
+ ---
2
+ type: decision
3
+ title: An inbound webhook source dispatches only requests verified against its credential
4
+ description: A webhook source with verify refuses a body-carrying request that fails its check, fails closed on an empty secret, and does not dispatch events from a body-less request
5
+ tags: core, webhooks, trigger, security
6
+ ---
7
+
8
+ # An inbound webhook source dispatches only requests verified against its credential
9
+
10
+ `verifyRequest` in `packages/core/src/wirings/trigger/webhook-source-runner.ts:173-200`
11
+ decides whether a request counts as signed. A source with no `verify` returns
12
+ true (`:178`). Otherwise the secret is read from the credential named by
13
+ `source.credential`, or from `webhookSecretCredentialName` (`:180-182`).
14
+
15
+ When the secret is missing or empty, a body-less request is treated as
16
+ unverified (`:184`), and a request with a body throws `UnauthorizedError`
17
+ (`:185-187`). `signedWith` also fails closed on an empty secret (`:127-128`).
18
+ The token form compares with `timingSafeStringEqual` (`:153-155`), which is
19
+ defined in `packages/core/src/utils/hmac.ts:57-63`.
20
+
21
+ A mismatch on a request with a body throws (`:199`). A body-less request is not
22
+ an error, but the runner refuses any events it produces: if `verified` is false
23
+ and `receive` returned events, it throws `UnauthorizedError` (`:273-276`). The
24
+ same contract is documented for authors at
25
+ `packages/core/src/wirings/trigger/webhook-source.types.ts:130-135`, and the
26
+ helper that runs the source's steps calls `runPikkuFunc` with `auth: false`
27
+ (`webhook-source-runner.ts:96-101`), so the signature, not a session, is the
28
+ gate.
29
+
30
+ **What this rules out:** dispatching events from a body-carrying request that did
31
+ not verify, and treating an empty signing secret as a valid key.
@@ -0,0 +1,18 @@
1
+ ---
2
+ type: decision
3
+ title: An MCP call with no HTTP request stays anonymous by transport design
4
+ description: MCP calls that arrive without an HTTP request, such as stdio, reach the function with no session because auth middleware reads the session only from the request
5
+ tags: core, mcp, auth, transport
6
+ ---
7
+
8
+ # An MCP call with no HTTP request stays anonymous by transport design
9
+
10
+ `RunMCPEndpointParams.http` in `packages/core/src/wirings/mcp/mcp-runner.ts` is the HTTP request an MCP call arrived on (`mcp-runner.ts:39-41`, field at `:52`).
11
+
12
+ The comment gives the mechanism. Auth middleware reads the session off `wire.http.request`, and every implementation returns early when there is no request (`mcp-runner.ts:42-46`). Without the request, an MCP call reaches the function unauthenticated whatever middleware is registered (`mcp-runner.ts:43-46`).
13
+
14
+ The field is left undefined for transports with no request, such as stdio. The comment says those calls remain anonymous, and that this is a property of the transport rather than a default chosen in the runner (`mcp-runner.ts:48-50`).
15
+
16
+ The surrounding decision about sessionless MCP entry points is in [MCP resources, tools and prompts take sessionless functions](mcp-resources-tools-and-prompts-take-sessionless-functions.md). The refusal for an HTTP call that needs a session is in [an MCP refusal is decided before dispatch](an-mcp-refusal-is-decided-before-dispatch.md).
17
+
18
+ **What this rules out:** treating a request-less MCP call as carrying a session, and filling in a fake HTTP request to make middleware run for a stdio caller.
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: decision
3
+ title: An omitted userId on CredentialService addresses the platform credential
4
+ description: The CredentialService contract reads and writes the platform-level credential whenever a per-credential call omits userId, and the better-auth implementation defaults its platform owner to pikku-platform
5
+ tags: core, credentials, security, interface
6
+ ---
7
+
8
+ # An omitted userId on CredentialService addresses the platform credential
9
+
10
+ `CredentialService` in `packages/core/src/services/credential-service.ts` is the store for dynamic
11
+ credentials such as OAuth tokens and per-user keys. Its doc comment says that throughout the interface,
12
+ an omitted `userId` means the platform-level credential (`packages/core/src/services/credential-service.ts:4`).
13
+
14
+ The per-credential methods take an optional `userId`: `get` (`credential-service.ts:7`), `set` (`:9`),
15
+ `delete` (`:11`) and `has` (`:13`). Omitting it selects the platform credential, so the caller chooses
16
+ the identity by whether it passes one.
17
+
18
+ The methods that enumerate users are different. `getAll` requires `userId` (`credential-service.ts:15`),
19
+ and `getUsersWithCredential` and `getAllUsers` take a credential name or nothing
20
+ (`credential-service.ts:17` and `:19`). They do not have a platform default.
21
+
22
+ The better-auth implementation applies the platform default only to some credentials. It takes an optional
23
+ `platformUserId` that falls back to `PLATFORM_USER_ID` (`packages/services/better-auth/src/better-auth-credential.service.ts:58`).
24
+ Its owner lookup returns null for a name that is not an OAuth2 credential (`better-auth-credential.service.ts:69-70`),
25
+ returns that platform owner only for a singleton OAuth2 name (`better-auth-credential.service.ts:72-73`),
26
+ and otherwise returns the `userId` passed in, or null when none was passed (`better-auth-credential.service.ts:75`).
27
+ So for a non-singleton credential, an omitted `userId` reads as no owner, not as the platform credential. The
28
+ interface comment describes the platform case more widely than this implementation does. Who may link the
29
+ singleton is covered by [a singleton credential belongs to a reserved platform user](../../../../services/better-auth/knowledge/decisions/a-singleton-credential-belongs-to-a-reserved-platform-user.md).
30
+
31
+ **What this rules out:** a sentinel string or an empty value to mean the platform credential, and relying on an
32
+ omitted `userId` to reach the platform credential for a credential that is not a singleton.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: Analytics identity is stamped from the session and resolvers, never from the event body
4
+ description: Each analytics record takes userId and orgId from the wire session, pikkuUserId from the wire, and the rest from the identity resolvers, not from the event payload
5
+ tags: core, analytics, security
6
+ ---
7
+
8
+ # Analytics identity is stamped from the session and resolvers, never from the event body
9
+
10
+ `resolveEvent` in `packages/core/src/analytics/analytics.ts:55-72` builds
11
+ `userIdentity` from the wire session (`:59`, `:67-69`), `this.wire.pikkuUserId`
12
+ (`:70`), and the output of the identity resolver (`:61`, `:71`). The event's own
13
+ fields are `name`, `props` and the optional `at` (`:60`, `:66`). The identity
14
+ fields are not read from the event.
15
+
16
+ `packages/core/src/analytics/analytics.types.ts:26-29` gives the reason: identity
17
+ is stamped server-side and never read from the request body, which is what makes
18
+ an unauthenticated ingest safe to expose.
19
+
20
+ The consent notes in this folder cover the cookie side of identity. They do not
21
+ cover how the record's identity is attached.
22
+
23
+ **What this rules out:** a client supplying `userId`, `orgId` or `pikkuUserId` in
24
+ an analytics event and having it recorded as the caller's identity.