@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,43 @@
1
+ ---
2
+ type: decision
3
+ title: Both validators check virtual users through one shared module, and a virtual user counts from either the meta or the source
4
+ description: workspace validate and cloud validate are separate implementations, so the virtual user/virtual-user/knowledge checks live in virtual-user-checks.ts and are called by both — and a virtual user is counted from the generated meta or a defineVirtualUsers() source scan, whichever answers first
5
+ tags: cli, validate, scenarios, virtual users
6
+ ---
7
+
8
+ # Both validators check virtual users through one shared module, and a virtual user counts from either the meta or the source
9
+
10
+ `pikku workspace validate` and `pikku validate` read like one command
11
+ with a flag. They are not. They are two implementations that walk the same
12
+ project and duplicate about eighteen findings between them verbatim —
13
+ `functions-dir-missing` exists twice, character for character, in two files.
14
+ Pikku Studio Cloud adds the deploy-shaped checks (themes, frontends, the Cloudflare
15
+ adapter, the `.gitignore` contract); workspace adds the local-development ones
16
+ (`dev.db`, the auth migrations).
17
+
18
+ So a check written into either one is a check half the projects never run. An
19
+ app that never deploys through cloud only ever sees workspace validate; an app
20
+ that does sees cloud validate in CI. The virtual user checks are the kind that only
21
+ pay off when nobody remembers to ask for them, which means they had to be in
22
+ both. Rather than duplicate them a nineteenth time, they live in
23
+ `virtual-user-checks.ts` and both validators call `runVirtualUserChecks`. Merging the
24
+ two validators outright is the better fix and a much larger one; this does not
25
+ block it.
26
+
27
+ **A virtual user counts from either source, and either alone is wrong at a
28
+ predictable moment.** The generated `pikku-virtual-users-meta.gen.json` is empty on
29
+ a fresh clone where codegen has not run, so trusting only it means a warning
30
+ that fires on every new checkout — and a validator that cries wolf gets muted,
31
+ after which it never says anything worth hearing. A source scan for
32
+ `defineVirtualUsers(` cannot see virtual users an addon contributed. Declared by either
33
+ counts as declared.
34
+
35
+ **Everything here is `warn`, never `error`.** A project with no virtual users is
36
+ under-tested, not broken, and `validate` gating a deploy on it would make the
37
+ first thing anyone does with the check be to remove it. `knowledge-empty` stays
38
+ at the `info` the shared `@pikku/knowledge` package already assigned it rather
39
+ than being raised to match — the severity belongs to that package.
40
+
41
+ **What this rules out:** duplicating the checks into both validators;
42
+ adding them to only one; deriving the virtual user count from the generated meta
43
+ alone; failing a build over a missing virtual user.
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  type: decision
3
- title: The actor-flow conversation engine only sees a transport-agnostic target driver
3
+ title: The conversation engine only sees a transport-agnostic target driver
4
4
  description: The engine never imports the agent runner; the target is injected as run/approve, so scenarios exercise the real wire path
5
- tags: actor-flow
5
+ tags: virtual-user-conversation
6
6
  ---
7
7
 
8
- # The actor-flow conversation engine only sees a transport-agnostic target driver
8
+ # The conversation engine only sees a transport-agnostic target driver
9
9
 
10
10
  `runConversation` reaches the target agent exclusively through the injected
11
11
  `TargetAgentDriver` — `run(message)` and `approve(runId, decisions)` — declared
12
- in `packages/core/src/wirings/actor-flow/actor-flow.types.ts`. In production that
13
- driver is HTTP-backed, issuing the actor's `agentRun` and `agentApprove` calls as
14
- the signed-in actor.
12
+ in `packages/core/src/wirings/virtual-user/conversation/conversation.types.ts`. In production that
13
+ driver is HTTP-backed, issuing the virtual user's `agentRun` and `agentApprove` calls as
14
+ the signed-in virtual user.
15
15
 
16
16
  Keeping the seam transport-agnostic is what makes the conversation a real test:
17
17
  the agent is exercised through the same authenticated wire path a user takes,
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: A conversation verdict is the virtual user's self-evaluation, not an assertion
4
+ description: The engine returns what the virtual user judged plus the transcript; deterministic checks stay with the caller
5
+ tags: virtual-user-conversation
6
+ ---
7
+
8
+ # A conversation verdict is the virtual user's self-evaluation, not an assertion
9
+
10
+ `runConversation` ends by asking the virtual user LLM whether the `evaluate`
11
+ criterion was met and returns that as `ConversationVerdict` — `passed`, `reasoning`
12
+ and the full `transcript`
13
+ (`packages/core/src/wirings/virtual-user/conversation/`). Nothing in the engine inspects the
14
+ system the agent acted on.
15
+
16
+ The verdict is deliberately soft because the thing it judges is soft: whether a
17
+ conversation accomplished a natural-language task. Deterministic checks — that a
18
+ row was written, that an email went out — belong to the caller, which already
19
+ holds the virtual user and can `virtualUser.invoke(...)` after the conversation returns. The
20
+ `reasoning` and `transcript` fields exist so a soft failure is debuggable rather
21
+ than merely red.
22
+
23
+ **What this rules out:** having `runConversation` assert or throw on a failed
24
+ verdict, and adding deterministic post-conditions to `ConverseOptions` instead of
25
+ running them at the call site.
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user conversation starts from a seeded kickoff message
4
+ description: The virtual user's first turn needs a non-empty message list because providers reject an empty prompt; the seed is an instruction and stays out of the transcript
5
+ tags: virtual-user-conversation
6
+ ---
7
+
8
+ # A virtual user conversation starts from a seeded kickoff message
9
+
10
+ `runConversation` in
11
+ `packages/core/src/wirings/virtual-user/conversation/run-conversation.ts` primes
12
+ `virtualUserMessages` with a single user message telling the virtual user to begin, before
13
+ the first turn is generated.
14
+
15
+ The virtual user speaks first, so at that point there is nothing for it to reply to —
16
+ and LLM providers reject a completion request with no messages. The seed is
17
+ addressed to the virtual user rather than to the target agent, which is why it is never
18
+ pushed onto `transcript`: it is scaffolding, not something the simulated user
19
+ said, and including it would corrupt the transcript the final evaluation reads.
20
+
21
+ **What this rules out:** starting from an empty `virtualUserMessages` array, folding
22
+ the kickoff into the virtual user instructions where a provider may weight it
23
+ differently, and pushing the seed into the transcript for symmetry.
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: decision
3
+ title: Virtual users are declared in exactly one defineVirtualUsers call per codebase
4
+ description: defineVirtualUsers is a no-op declaration that the inspector reads statically, and a second call anywhere in the codebase fails the build
5
+ tags: core, inspector, virtual-users, declaration
6
+ ---
7
+
8
+ # Virtual users are declared in exactly one defineVirtualUsers call per codebase
9
+
10
+ `defineVirtualUsers` in `packages/core/src/wirings/virtual-user/define-virtual-users.ts`
11
+ is a no-op (`define-virtual-users.ts:33`). The CLI reads the call by AST and
12
+ generates a `VirtualUserId` union and `virtual-users.gen.json` from it
13
+ (`define-virtual-users.ts:3-8`). The doc comment states the rule: "Exactly one
14
+ `defineVirtualUsers(...)` is allowed per codebase, so there is one place to read the
15
+ cast from and one place to add to. A second call — even in the same file — fails
16
+ the build" (`define-virtual-users.ts:14-16`).
17
+
18
+ The inspector enforces it. `addVirtualUsers` requires an inline object literal and
19
+ refuses anything else (`packages/inspector/src/add/add-virtual-users.ts:163-170`),
20
+ then claims the single declaration (`add-virtual-users.ts:174-181`, error
21
+ `DUPLICATE_VIRTUAL_USERS_DEFINITION`). `claimSingleDeclaration` in
22
+ `packages/inspector/src/utils/single-declaration.ts` refuses a second call in the
23
+ same file or another one (`single-declaration.ts:44-56`). Generated `.gen.ts` files
24
+ are exempt and never claim the slot (`single-declaration.ts:40-42`).
25
+
26
+ **What this rules out:** a second `defineVirtualUsers` call, including in the same
27
+ file, and splitting the cast across several declarations. The rule is about where a
28
+ person adds a virtual user, so it applies to hand-written files only.
29
+
30
+ **Related:** [scenario and virtual-user files are named for what they hold](scenarios-live-in-files-named-for-them.md)
@@ -31,12 +31,12 @@ matches a family of ids, `containing` picks the match holding a piece of text,
31
31
  and `within` scopes the lookup to one row or section. Core defines the shape;
32
32
  the driver resolves it against a real page.
33
33
 
34
- The same dependency discipline applies at runtime: `resolveScenarioActors` in
35
- `pikku-scenario-service.ts` imports the HTTP actor client lazily, so even a
36
- runner bundle only pays for the AI persona conversation loop when a scenario
37
- actually signs an actor in.
34
+ The same dependency discipline applies at runtime: `resolveScenarioVirtualUsers` in
35
+ `pikku-scenario-service.ts` imports the HTTP virtual user client lazily, so even a
36
+ runner bundle only pays for the AI virtual user conversation loop when a scenario
37
+ actually signs a virtual user in.
38
38
 
39
39
  **What this rules out:** importing playwright (or any driver) from core,
40
40
  tightening `reset`/`captureFailure` to required, letting `captureFailure` throw,
41
- reducing `TestIdSelector` to a plain string, or making the actor-client import
41
+ reducing `TestIdSelector` to a plain string, or making the virtual-user-client import
42
42
  static.
@@ -18,7 +18,7 @@ The reason it is not a subclass is bundle size: a bundler drops an unused
18
18
  _module_ but never an unused class member. Anything declared on
19
19
  `PikkuWorkflowService` ships in every server built on Pikku, along with
20
20
  everything it imports. `PikkuScenarioService` (`pikku-scenario-service.ts`) is
21
- the one implementation today — steps, actors, lifecycle hooks, the browser
21
+ the one implementation today — steps, virtual users, lifecycle hooks, the browser
22
22
  provider and the assertion wire members — and scenarios only ever run from
23
23
  `pikku scenario run`, so the whole surface stays behind an import that only the
24
24
  runner makes. It is not a workflow service in its own right because a scenario
@@ -33,7 +33,7 @@ installed rather than inherited. It pairs the extension with
33
33
  single external process driving a deployed app over its real transport: there
34
34
  is nothing to persist and no second worker to resume it.
35
35
 
36
- **What this rules out:** moving scenario steps, actors, hooks or the browser
36
+ **What this rules out:** moving scenario steps, virtual users, hooks or the browser
37
37
  provider onto `PikkuWorkflowService`, making `PikkuScenarioService` extend it,
38
38
  or widening `WorkflowRunEngine` into "just pass the service" — each one puts the
39
39
  scenario runtime, and its transitive imports, into every deployed Pikku server.
@@ -33,10 +33,10 @@ defaults are shorter than the assertion wrappers': `within` 15s, `interval`
33
33
  `requireScenarioEnv` (`scenario-step-guards.ts`) exists for the mirror-image
34
34
  reason: `env` is optional on the step wire because most steps need nothing from
35
35
  it, so the narrowing happens in one place with a message that says what to do,
36
- instead of every step file writing its own guard. The actor is not narrowed at
37
- all — a step that runs as somebody declares `actor: true` (or a `browser`
36
+ instead of every step file writing its own guard. The virtual user is not narrowed at
37
+ all — a step that runs as somebody declares `virtualUser: true` (or a `browser`
38
38
  binding, which cannot be provisioned without a window to open), and the runner
39
- injects `wire.actor` non-optionally into every binding and refuses to dispatch
39
+ injects `wire.virtualUser` non-optionally into every binding and refuses to dispatch
40
40
  the step without one. A wire member can be required per binding; a property of
41
41
  one cannot, which is why it is not on `scenarioStep`. `scenarioStep`'s `verifyStepName` call doubles as the guard for `then`
42
42
  being a wire member — an accidental `await scenario` calls it with a resolve
@@ -50,5 +50,5 @@ the first argument only; `times` means at-least-once unless a count is given;
50
50
 
51
51
  **What this rules out:** giving scenario steps the workflow retry default,
52
52
  recording each poll iteration as its own step, treating a falsy `pollUntil`
53
- result as "not yet", making `pollUntil` throw on timeout, or narrowing `actor`
53
+ result as "not yet", making `pollUntil` throw on timeout, or narrowing `virtualUser`
54
54
  by hand inside individual step files.
@@ -16,7 +16,7 @@ pass by a single external process.
16
16
  A hook is not a pikku function: it has no id, no meta and no schema, so it
17
17
  cannot go through `runPikkuFunc` and the runner records nothing for it. It gets
18
18
  exactly what the scenario body gets — the same wire (which is how it reaches the
19
- app through `actors`) and singleton services composed with this invocation's
19
+ app through `virtualUsers`) and singleton services composed with this invocation's
20
20
  wire services — and nothing else. `ScenarioHookError` keeps the original error
21
21
  as `cause`, so the failure that actually happened is never lost behind the label
22
22
  saying which phase it happened in.
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  type: decision
3
3
  title: Scenario prose is rendered out of typed calls, not parsed into them
4
- description: The inversion of cucumber — a readable report with no regex step registry to maintain
4
+ description: A readable report with no regex step registry to maintain
5
5
  tags: workflow
6
6
  ---
7
7
 
8
8
  # Scenario prose is rendered out of typed calls, not parsed into them
9
9
 
10
10
  `scenario-prose.ts` renders the English sentence a reporter shows for a scenario
11
- step. It is the inversion of cucumber: instead of parsing English into a call
11
+ step. Instead of parsing English into a call
12
12
  through a registry of regexes, the call is typed and the English is rendered out
13
13
  of it. The readable report survives without anyone maintaining a step-definition
14
14
  registry, and a step that no longer exists cannot leave a dangling phrase behind.
@@ -19,7 +19,7 @@ generated `TypedScenario` narrows these over `FlattenedScenarioStepMap`.
19
19
  prose a reporter renders (`scenario-prose.ts`), never what executes.
20
20
 
21
21
  The prose direction is itself the decision: rather than parsing English into a
22
- call the way cucumber does, `composeStepProse` renders English out of a typed
22
+ call through a regex registry, `composeStepProse` renders English out of a typed
23
23
  call, so a readable report survives without a regex registry paying for it. It
24
24
  lives in core so the CLI reporter and the console render the same sentence for
25
25
  the same step, and `renderStepTemplate` fills `{placeholders}` from the input
@@ -11,7 +11,7 @@ tags: workflow
11
11
  `completed | failed | cancelled`. `suspended` is deliberately absent: a
12
12
  suspended run stops a poll loop but can still be resumed, so anything the
13
13
  process holds for it — the run context, an extension's per-run state such as a
14
- scenario's live actor clients — has to survive. `WORKFLOW_END_STATES`, which
14
+ scenario's live virtual user clients — has to survive. `WORKFLOW_END_STATES`, which
15
15
  `awaitRunEnd` uses to stop reading, does include `suspended`, because a poller
16
16
  should not wait on a run that needs an external nudge. The two sets are not
17
17
  interchangeable.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: A bearer token checked against a secret is read per request, and an unreadable secret means no session
4
+ description: The secretId form of authBearer reads the secret on every request; a missing or failing read leaves the request without a session rather than raising an error
5
+ tags: core, middleware, auth
6
+ ---
7
+
8
+ # A bearer token checked against a secret is read per request, and an unreadable secret means no session
9
+
10
+ In `packages/core/src/middleware/auth-bearer.ts:57-70`, a token given as a
11
+ literal `value` is compared directly (`:59-60`). A token given a `secretId` is
12
+ read from the secret service on each request (`:63-64`). A failed read is caught
13
+ and becomes `undefined` (`:65`), and the code comments that an unset secret means
14
+ the feature is off, never a request error (`:62`).
15
+
16
+ An undefined expected value skips the comparison (`:68`). `userSession` stays
17
+ null, and `setSession` is not called (`:55`, `:75-77`). A malformed scheme is a
18
+ different case: it throws `InvalidSessionError` (`:50-53`).
19
+
20
+ The static and JWT branches of this middleware are covered by the existing
21
+ questions `a-bearer-jwt-with-no-identity-becomes-a-session.md` and
22
+ `should-a-wrong-bearer-token-be-anonymous-or-refused.md`.
23
+
24
+ **What this rules out:** turning a missing or unreadable signing secret into a
25
+ server error, and caching the secret value across requests in the middleware
26
+ closure.
@@ -0,0 +1,20 @@
1
+ ---
2
+ type: decision
3
+ title: A call without a session raises MissingSessionError, and a missing scope raises MissingScopeError
4
+ description: The function runner separates the no-session refusal from the scope and permission refusals, so a caller can tell a missing login from a denied grant
5
+ tags: core, errors, sessions, scopes
6
+ ---
7
+
8
+ # A call without a session raises MissingSessionError, and a missing scope raises MissingScopeError
9
+
10
+ `resolveSession` in `packages/core/src/function/function-runner.ts` (line 64) loads a stored session when there is a user id. It throws nothing.
11
+
12
+ The refusal is in `executeFunction`. A sessionless function requires a session only when its wiring, its own config, or the addon says `auth: true` (`function-runner.ts:385-395`). That refusal throws `MissingSessionError` (`function-runner.ts:392`). A `pikkuFunc` with no session throws the same error unconditionally (`function-runner.ts:402`). The class is at `packages/core/src/errors/errors.ts:36`.
13
+
14
+ Scope checks use a different error. `verifyScopes` throws `MissingScopeError` (`packages/core/src/scopes.ts:84-86`, class at `packages/core/src/errors/errors.ts:118`).
15
+
16
+ Permission checks use `ForbiddenError` with the message `Permission denied` (`packages/core/src/permissions.ts:155`, `:166`, class at `packages/core/src/errors/errors.ts:57`).
17
+
18
+ So the three refusals are distinct: no login, a missing scope, and a failed permission function.
19
+
20
+ **What this rules out:** reporting a call with no session as a `ForbiddenError`, and reading a `ForbiddenError` from the runner as proof that a session exists but lacks a grant.
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: A cookie the caller sets wins over the jar's copy of the same name
4
+ description: createCookieJar sends the caller's cookie for a name and drops the jar's copy of that name, so one request never carries the same cookie twice
5
+ tags: core, scenario, cookies
6
+ ---
7
+
8
+ # A cookie the caller sets wins over the jar's copy of the same name
9
+
10
+ In `createCookieJar` (`packages/core/src/services/scenario-cookie-jar.ts`)
11
+ the jar builds the outgoing `cookie` header from the caller's own header and the
12
+ jar's held cookies. A held cookie is left out when the caller's header already
13
+ names it. The code comment says a request that already carries a session "is
14
+ stating which one it means, and emitting the jar's copy alongside it sends the
15
+ same name twice."
16
+
17
+ The rule is per name, not per request. Cookies the caller did not name still go
18
+ out from the jar. The jar's `Set-Cookie` handling, which replaces a reissued
19
+ cookie rather than sending both, is covered by
20
+ [Scenario sessions are isolated per virtual user and reset between scenarios](workflow-scenario-sessions-are-isolated-per-virtual-user-and-per-scenario.md).
21
+
22
+ **What this rules out:** sending two values of one cookie name in a request, and
23
+ letting the jar's held session override a session the caller has chosen to send.
@@ -0,0 +1,16 @@
1
+ ---
2
+ type: decision
3
+ title: A channel lifecycle acts as the session the socket upgrade established
4
+ description: onConnect and onDisconnect run with the userSession captured during the socket upgrade, both in the wire they receive and as the session service passed to runPikkuFunc
5
+ tags: core, channel, sessions, lifecycle
6
+ ---
7
+
8
+ # A channel lifecycle acts as the session the socket upgrade established
9
+
10
+ `runChannelLifecycleWithMiddleware` in `packages/core/src/wirings/channel/channel-common.ts` takes an optional `userSession`. The comment says it is established while the socket upgrades, so `onConnect` can tell who connected (`channel-common.ts:43-47`). The same function also serves `connect` and `disconnect` (`channel-common.ts:38`).
11
+
12
+ The wire for the lifecycle is built from that session when one exists (`channel-common.ts:78`). The lifecycle function then runs through `runPikkuFunc` with `sessionService: userSession` (`channel-common.ts:84-93`). So the lifecycle reads and writes the same session the upgrade produced, for both connect and disconnect.
13
+
14
+ The related decision that records when the user id is stored is [a channel's user id is persisted only after onConnect middleware has run](../internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md).
15
+
16
+ **What this rules out:** running a channel's disconnect handler as a different identity than the one that connected the socket, and building the lifecycle wire without the upgrade session when one was established.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: A channel requires a session unless it says auth: false
4
+ description: The channel-level requirement is auth !== false, and a message handler's own auth, when it is set, decides for that message in either direction
5
+ tags: core, channel, auth
6
+ ---
7
+
8
+ # A channel requires a session unless it says auth: false
9
+
10
+ Both channel runners compute the channel-level requirement as
11
+ `channelConfig.auth !== false`: `packages/core/src/wirings/channel/channel-runner.ts:142`
12
+ and `packages/core/src/wirings/channel/channel-handler.ts:75`. An absent `auth`
13
+ therefore requires a session. `channel.types.ts:67-76` documents the same rule and
14
+ says why: a proxy in front of a channel needs to tell public from private.
15
+
16
+ Per message, `validateAuth` (`channel-handler.ts:49-65`) uses the channel's
17
+ `requiresSession` when the handler is a bare function or has no `auth` of its own
18
+ (`:50-58`). When the handler object sets `auth`, that value wins (`:58-60`). The
19
+ check then fails only when `auth` is true and there is no session (`:62-64`).
20
+
21
+ So a message handler's `auth: false` loosens a channel that requires a session for
22
+ that message, and `auth: true` tightens a public channel for that message. A
23
+ rejected message gets the reply described in the question
24
+ `unauthorized-channel-replies-escape-the-declared-out-type.md`.
25
+
26
+ **What this rules out:** treating a channel with no `auth` flag as public.
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: decision
3
+ title: A credential's read path follows its declared type, never a missing value
4
+ description: The credential wire service reads a wire credential only from the user's store and a singleton only from the deployment, so an absent user value never falls back to the deployment's token
5
+ tags: core, credentials, wire, singleton
6
+ ---
7
+
8
+ # A credential's read path follows its declared type, never a missing value
9
+
10
+ `packages/core/src/services/credential-wire-service.ts` decides where each
11
+ credential is read from. The doc comment says the choice is "decided by what the
12
+ credential IS — its declared type, as the wiring may have overridden it — and
13
+ never by whether a per-user lookup came back empty", because an absence-driven
14
+ fallback "would let a user who has not connected read the deployment's own token"
15
+ (`credential-wire-service.ts:6-10`).
16
+
17
+ The resolution type is `{ mode: 'wire' } | { mode: 'singleton' }`
18
+ (`credential-wire-service.ts:12`).
19
+
20
+ In `doLoad`, the user's store is read with `getAll(userId)`, and any name the
21
+ resolution marks `singleton` is skipped (`credential-wire-service.ts:103-104`).
22
+ The comment says this stops a user "in charge of a slot the deployment owns"
23
+ (`credential-wire-service.ts:116-121`).
24
+
25
+ `loadDeploymentCredentials` fetches only `singleton` names from the deployment
26
+ service (`credential-wire-service.ts:137-143`). A `wire` name is skipped there
27
+ (`credential-wire-service.ts:138`), and the comment says a wire credential "can
28
+ never pick up a platform value" (`credential-wire-service.ts:127-130`).
29
+
30
+ **What this rules out:** a `wire` credential falling back to the deployment's
31
+ value when the user has none, and a user's stored value being imported under a
32
+ name the deployment declared as `singleton`.
@@ -0,0 +1,27 @@
1
+ ---
2
+ type: decision
3
+ title: A credential's wire or singleton type is a literal the inspector checks at declaration
4
+ description: defineCredential is a no-op read by AST, so its type must be a literal 'wire' or 'singleton' and the inspector refuses any other value
5
+ tags: core, credentials, inspector, declaration
6
+ ---
7
+
8
+ # A credential's wire or singleton type is a literal the inspector checks at declaration
9
+
10
+ `defineCredential` in `packages/core/src/wirings/credential/define-credential.ts` is a
11
+ no-op. Its doc comment says the CLI reads the call by AST, so "the declaration must
12
+ be a top-level literal" (`define-credential.ts:4-5`). `type: 'wire'` is per-user and
13
+ `type: 'singleton'` is platform-wide (`define-credential.ts:6`).
14
+
15
+ The inspector finds the call by its identifier (`packages/inspector/src/add/add-credential.ts:27`)
16
+ and requires a `type` property. Anything other than `'singleton'` or `'wire'` is a
17
+ critical error: "missing or has invalid 'type' property. Must be 'singleton' or
18
+ 'wire'" (`add-credential.ts:64-68`).
19
+
20
+ The read path that follows from that type is in
21
+ [a credential's read path follows its declared type, never a missing value](a-credential-read-path-follows-its-declared-type-not-absence.md).
22
+ This note covers the declaration only: the type is decided where the credential is
23
+ declared, and the inspector rejects a declaration whose type is missing or not one of
24
+ the two literals.
25
+
26
+ **What this rules out:** computing a credential's type from a variable or at runtime,
27
+ and accepting a declaration whose type is missing or spelled differently.
@@ -0,0 +1,33 @@
1
+ ---
2
+ type: decision
3
+ title: A first-party tracking cookie is written only after its purposes are consented
4
+ description: mintCookie writes a new cookie only when every purpose it requires is true in the consent record, and writes nothing on a wire with no HTTP response
5
+ tags: core, analytics, consent, cookies
6
+ ---
7
+
8
+ # A first-party tracking cookie is written only after its purposes are consented
9
+
10
+ `packages/core/src/analytics/mint-cookie.ts` creates a first-party cookie when
11
+ none exists. The doc comment says writing the cookie "IS the act consent governs",
12
+ so a minter that runs before the banner is answered has already done what the
13
+ send gate was meant to prevent (`mint-cookie.ts:11-15`).
14
+
15
+ The consent check is `permitted`. It returns true when no purposes are required,
16
+ and otherwise requires each purpose to be exactly `true` in the consent record
17
+ (`mint-cookie.ts:29-35`). A purpose missing from the record counts as refused.
18
+ `mintCookie` runs that check after it has found no existing cookie and no cached
19
+ value (`mint-cookie.ts:72`).
20
+
21
+ An existing cookie is returned before the consent check (`mint-cookie.ts:63-64`).
22
+ Reading a cookie the visitor already holds is therefore not gated by consent. The
23
+ `overwrite` option is the only way to replace it (`mint-cookie.ts:19-26`).
24
+
25
+ A wire with no HTTP response returns `undefined` and writes nothing
26
+ (`mint-cookie.ts:70-71`), as the doc comment says for cron tasks and queue workers
27
+ (`mint-cookie.ts:50-53`).
28
+
29
+ The value is cached per wire, so a second call in one request returns the same
30
+ identity (`mint-cookie.ts:66-68`, `77`, and the comment at `mint-cookie.ts:46-48`).
31
+
32
+ **What this rules out:** writing a tracking cookie before its purposes are
33
+ consented, and minting an identity on a non-HTTP wire.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: A readonly session reaches only functions declared readonly
4
+ description: A session flagged readonly is refused by every function not declared readonly, checked straight after the session is required and before scopes
5
+ tags: core, auth, function-runner
6
+ ---
7
+
8
+ # A readonly session reaches only functions declared readonly
9
+
10
+ `CoreUserSession.readonly` (`packages/core/src/types/core.types.ts`) restricts a
11
+ session to functions declared `readonly`. In `runPikkuFunc`
12
+ (`packages/core/src/function/function-runner.ts`) the check is
13
+ `if (session?.readonly && !funcMeta.readonly) throw new ReadonlySessionError()`.
14
+ It runs after the missing-session check and before scopes, permissions and input
15
+ validation, so a readonly session is refused before any work is done for a
16
+ mutating function. The unit test "should reject readonly sessions for
17
+ non-readonly functions" in `function-runner.test.ts` pins the behaviour.
18
+
19
+ Core never sets the flag. Whoever builds the session decides it, and core only
20
+ enforces it. Readonly is a property of the session, not of a scope, so a scope
21
+ grant cannot widen what a readonly session may reach.
22
+
23
+ **What this rules out:** expressing read-only access as a scope the session
24
+ happens to lack, and letting a readonly session call a mutating function on the
25
+ grounds that its scopes cover it.
@@ -13,11 +13,16 @@ The field is gone. `PikkuScaffoldFeature` is now `boolean | { path?: string }`:
13
13
  whether the surface is generated, and where the file goes.
14
14
 
15
15
  The field was a second gate in front of one that already runs. `resolveSession`
16
- in `packages/core/src/function/function-runner.ts` throws `ForbiddenError` for a
17
- `pikkuFunc` with no session, unconditionally; a `pikkuSessionlessFunc` requires
18
- one when its own `auth: true` says so, when its wiring says so, or when
19
- `resolveAddonAuth` does. That is where authentication is decided, per function,
20
- by whoever wrote it. A config flag three directories away could only be coarser
16
+ in `packages/core/src/function/function-runner.ts` (line 64) only loads a stored
17
+ session; it throws nothing. The refusal is in `executeFunction`: a `pikkuFunc`
18
+ with no session throws `MissingSessionError` (line 402, class at
19
+ `packages/core/src/errors/errors.ts:36`), unconditionally. A
20
+ `pikkuSessionlessFunc` throws the same error (line 392) only when its own
21
+ `auth: true` says so, when its wiring says so, or when `resolveAddonAuth` does. That is where authentication is decided, per function,
22
+ by whoever wrote it. Neither refusal is a `ForbiddenError`: both throw `MissingSessionError`
23
+ (`function-runner.ts:392`, `:402`; class at `packages/core/src/errors/errors.ts:36`). The split between
24
+ that error, `MissingScopeError` and `ForbiddenError` is recorded in
25
+ [a call without a session raises MissingSessionError](./a-call-without-a-session-raises-missing-session-not-forbidden.md). A config flag three directories away could only be coarser
21
26
  than that, and being coarser was the whole problem the earlier decision was
22
27
  trying to work around by defaulting it closed.
23
28
 
@@ -0,0 +1,27 @@
1
+ ---
2
+ type: decision
3
+ title: A scheduled virtual-user run needs no admin at run time
4
+ description: An admin may start a virtual-user run, but the run itself never depends on an admin being signed in or on an admin's credential
5
+ tags: services, auth, workflow
6
+ ---
7
+
8
+ # A scheduled virtual-user run needs no admin at run time
9
+
10
+ The people who set up virtual-user runs are not technical. A run must keep
11
+ working after they stop being around, and it must not depend on any admin being
12
+ signed in or on any admin's credential.
13
+
14
+ An admin clicking "run" or creating a schedule is the authorization step. Once
15
+ the schedule exists, the run is started by the app's own scheduler and performs
16
+ the test as a provisioned test account (see
17
+ [virtual-user-flagged users only](virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md)).
18
+
19
+ The run presents no credential at all. `executeVirtualUserRun`
20
+ (`packages/core/src/wirings/virtual-user/virtual-user-scaffold.ts`) builds its
21
+ virtual users with `signIn: inProcessSignIn(auth)`, which calls the app's
22
+ server-only sign-in endpoint through `auth.api`
23
+ (`packages/core/src/services/virtual-user-sign-in.ts`). The code asking is the
24
+ app, so there is no key for an admin to hold or refresh.
25
+
26
+ **What this rules out:** a run that borrows an admin's session or key, and a run
27
+ that depends on a person to refresh a credential.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: A scoped credential service is limited to the names its package declared
4
+ description: ScopedCredentialService refuses any credential name outside the package's declared set, filters getAll to that set, and refuses to enumerate users
5
+ tags: core, credentials, scoping
6
+ ---
7
+
8
+ # A scoped credential service is limited to the names its package declared
9
+
10
+ `packages/core/src/services/scoped-credential-service.ts` wraps a
11
+ `CredentialService` with the set of names a package declared. The doc comment
12
+ says it "cannot reach a credential it never declared, or enumerate the app's
13
+ users" (`scoped-credential-service.ts:6-9`).
14
+
15
+ - `get`, `set`, `delete`, `has` and `getUsersWithCredential` each call
16
+ `assertAllowed` and throw `Access denied to credential` for an undeclared name
17
+ (`scoped-credential-service.ts:16-20`, `23`, `28`, `33`, `38`, `54`).
18
+ - `getAll(userId)` returns only the declared names that the user holds
19
+ (`scoped-credential-service.ts:42-51`).
20
+ - `getAllUsers` always throws (`scoped-credential-service.ts:58-62`).
21
+
22
+ Writes are allowed for declared names (`scoped-credential-service.ts:27-30`), per
23
+ the doc comment at `scoped-credential-service.ts:6-8`.
24
+
25
+ **What this rules out:** a package reading or writing another package's
26
+ credential, and a package listing every user of the app.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: A scoped secret view reads only its declared keys and never writes
4
+ description: ScopedSecretService checks every read against an allowed-key set and refuses all writes, unlike the credential service that stays writable
5
+ tags: core, secrets, scoping
6
+ ---
7
+
8
+ # A scoped secret view reads only its declared keys and never writes
9
+
10
+ `packages/core/src/services/scoped-secret-service.ts` wraps a `SecretService`
11
+ with a set of allowed keys (`scoped-secret-service.ts:4-8`).
12
+
13
+ Every read is checked first. `getSecret`, `hasSecret` and `getSecrets` each call
14
+ `assertAllowed`, which throws `Access denied to secret key` for any key outside
15
+ the set (`scoped-secret-service.ts:10-14`, `17`, `22`, `43`).
16
+
17
+ Every write is refused. `setSecret`, `setEncryptedSecret` and `deleteSecret`
18
+ throw unconditionally (`scoped-secret-service.ts:26-38`).
19
+
20
+ The credential counterpart is different. `scoped-credential-service.ts` says it
21
+ "stays writable — an addon refreshing its own OAuth token is the normal case"
22
+ (`scoped-credential-service.ts:6-8`).
23
+
24
+ **What this rules out:** an addon reading a secret it did not declare, and an
25
+ addon writing or deleting any secret through this view.