@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
@@ -10,47 +10,53 @@ A rule about how core behaves: what runs where, in what order, and what a
10
10
  caller is entitled to assume.
11
11
 
12
12
  <!-- pikku:knowledge-index -->
13
-
13
+ - [A credential name declared twice must agree on its schema and its type](a-credential-name-declared-twice-must-agree-on-schema-and-type.md) — validateAndBuildCredentialDefinitionsMeta throws when two declarations of one credential name differ in schema or in type, and otherwise keeps the first declaration
14
+ - [declareScopes lets an app grant a scope whose addon it does not wire](a-grant-can-name-a-scope-declared-without-its-addon.md) — defineScope is a no-op read by the CLI, and declareScopes declares scope ids so a grant is valid without wiring the addon that owns the scope tree
14
15
  - [A held run is woken later, not retried](a-held-run-is-woken-later-not-retried.md) — An orchestrator message that finds its run held enqueues a fresh wake-up a second later instead of failing, because the queue's retry budget is for real failures and a long pass outlasts it
15
- - [A non-streaming agent run registers with aiRunState on the same terms as a streaming one](a-non-streaming-agent-run-registers-with-airunstate-too.md) — Otherwise interruptAIAgent finds the run, passes the ownership check, then cannot stop it — and reports that as if the run were on another host
16
+ - [A non-streaming agent run registers with agentRunState on the same terms as a streaming one](a-non-streaming-agent-run-registers-with-agentrunstate-too.md) — Otherwise interruptAgent finds the run, passes the ownership check, then cannot stop it — and reports that as if the run were on another host
16
17
  - [With scenarios.reset on, the dev seed is the fixture every assertion is written against](a-reset-suite-makes-the-dev-seed-the-fixture.md) — The rollback restores the database as the suite found it, so seed rows are the shared baseline — which makes an absolute date in the seed a test that expires
17
18
  - [A resumed turn is as interruptible as the first one](a-resumed-agent-turn-is-as-interruptible-as-the-first.md) — It is the same person listening to the same voice, and after an approval it is where most of the reply actually gets spoken
18
19
  - [A scenario step's prose template is offered to a virtual user unfilled](a-scenario-step-template-is-offered-unfilled.md) — A reporter fills placeholders from a run that happened; there is no run yet, and the filled form would answer the question the user is there to answer
20
+ - [A scheduled task writes its caller's session to the session store before it runs](a-scheduled-task-writes-its-caller-session-before-it-runs.md) — runScheduledTask stores the session it is passed, or nothing, before it looks up the task, so the run uses only a session its caller supplied
21
+ - [A secret set in memory shadows the variable of the same name](a-secret-set-in-memory-shadows-the-variable-of-the-same-name.md) — LocalSecretService reads its in-memory map before the variables service, stores a SecretValue unwrapped, and treats an empty string as absent
19
22
  - [A secret that fails to decrypt fails the whole read](a-secret-that-fails-to-decrypt-fails-the-whole-read.md) — getSecrets throws naming the key and its key_version rather than omitting the row, because a silent omission surfaces as an unrelated failure much later
23
+ - [A virtual user acts through invokeRaw, so a refusal is data it can act on](a-virtual-user-acts-through-invokeraw-so-a-refusal-is-data.md) — virtualUserTarget calls virtualUser.invokeRaw, which signs the virtual user in when needed, retries once after a 401, and returns the status instead of throwing
24
+ - [A virtual user cadence is a row, not a timer](a-virtual-user-cadence-is-a-row-not-a-timer.md) — How often a virtual user runs is stored as a due time per virtual user and acted on by a tick the project schedules — pikku never starts a timer, and a run never reschedules itself
25
+ - [A virtual user's converse signs in before its first turn](a-virtual-user-converse-signs-in-before-its-first-turn.md) — HttpVirtualUser.converse calls login() before a run when it holds no session, because a public agent route never triggers the 401 retry that would sign the virtual user in
20
26
  - [A virtual user decides whether to trust its notes once per turn, by one roll](a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md) — The difference between the stale, newcomer and auditor dispositions is expressed as a single probability rather than as prose in each prompt
21
- - [A virtual user cadence is a row, not a timer](a-virtual-user-cadence-is-a-row-not-a-timer.md) — how often a persona runs is stored as a due time per persona and acted on by a tick the project schedules — pikku never starts a timer, and a run never reschedules itself
27
+ - [A virtual user's email is computed from its id, with the run id as a sub-address](a-virtual-user-email-is-computed-with-the-run-id-as-a-sub-address.md) — virtualUserEmail derives the address from the virtual user id and a real domain, appends the run id so concurrent runs get separate inboxes and accounts, and refuses colliding addresses
22
28
  - [A virtual user run is not a workflow, but it needs a trigger](a-virtual-user-run-is-not-a-workflow-but-it-needs-a-trigger.md) — runVirtualUser writes its record and dispatches the run onto a queue at one attempt — an exploratory run has nothing to replay, but a deployment that puts each function in its own unit has nothing to fire it either
23
29
  - [A wall-clock threshold is a load test in disguise](a-wall-clock-threshold-is-a-load-test-in-disguise.md) — The KEK derivation test asserted a fixed 50ms budget for work that took 10ms, which went red about one run in five once the suite was large enough to compete for the machine
24
30
  - [A workflow's wire is built from the run record, not from the RPC service](a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md) — The RPC service exposes no wire, so every rpcService.wire read was undefined; the run record is the only thing that carries the caller across a step boundary
25
- - [An actor conversation starts from a seeded kickoff message](actor-flow-conversations-seed-a-hidden-kickoff-message.md) — The actor's first turn needs a non-empty message list because providers reject an empty prompt; the seed is an instruction and stays out of the transcript
26
- - [The actor-flow conversation engine only sees a transport-agnostic target driver](actor-flow-drives-the-target-through-a-transport-seam.md) — The engine never imports the agent runner; the target is injected as run/approve, so scenarios exercise the real wire path
27
- - [An actor-flow verdict is the persona's self-evaluation, not an assertion](actor-flow-verdicts-are-llm-self-evaluations.md) — The engine returns what the actor judged plus the transcript; deterministic checks stay with the caller
28
31
  - [Addon package roots resolve by walking node module search paths](addon-package-roots-resolve-by-walking-node-module-search-paths.md) — LocalMetaService finds an addon's directory by scanning resolve.paths, not by require.resolve, because addon packages expose no main entry
29
32
  - [An addon's .pikku meta ships at the package root or under dist, and readers probe both](addon-pikku-meta-ships-at-the-package-root-or-under-dist.md) — The layout depends on the addon's build and files field, so a consumer resolving the directory by path must try `<pkg>/.pikku` and `<pkg>/dist/.pikku` before concluding an addon declares nothing
30
33
  - [Addon singleton services are cached per namespace, not per package](addon-singleton-services-are-cached-per-namespace-not-per-package.md) — Each wireAddon instance gets its own services built from its own overrides, at the cost of one service graph per wired instance
31
34
  - [Bare workflow names from inside an addon are prefixed with the consumer's namespace](addon-workflow-names-are-prefixed-with-the-consumer-namespace.md) — The addon's workflowService is proxied so an addon never has to hardcode the name its consumer chose
35
+ - [The AG-UI bridge obeys the client's event-ordering contract exactly](agent-agui-bridge-obeys-the-client-ordering-contract.md) — RUN_STARTED opens lazily, RUN_FINISHED fires once on done, and step names are globally sequential — a violation makes the client drop the whole stream
36
+ - [Audio chunks are labelled with the format the provider actually returned](agent-audio-chunks-carry-the-format-the-provider-returned.md) — The configured format is only a request, so the response's own format wins with the request and pcm16 as fallbacks
32
37
  - [Loading agent context waits for a tool result that may still be landing](agent-context-waits-for-a-tool-result-still-being-written.md) — An interrupted run's tool can still be writing to the thread, and in voice the next turn arrives within seconds — soon enough to load context missing what it is about to be asked about
38
+ - [A credential-required tool result never reaches the client or the transcript](agent-credential-suspensions-hide-the-tool-result.md) — The run suspends with credential-request events instead, leaving the tool call unresulted so it can be resumed after connecting
39
+ - [Delegate mode hides the parent's later text, supervise mode hides the sub-agent's](agent-delegate-and-supervise-hide-different-text.md) — Exactly one voice reaches the client per agent-mode; approvals and tool events always flow through either way
40
+ - [Nulls are stripped from approved tool arguments before execution](agent-llm-tool-arguments-have-nulls-stripped.md) — LLMs emit null for optional fields where the schema layer expects undefined, so a resumed tool call is cleaned recursively first
41
+ - [Model resolution stays a single seam even though it is currently a passthrough](agent-model-config-stays-a-single-resolution-seam.md) — resolveModelConfig looks removable but is the one merge point for per-request model overrides
42
+ - [An agentMiddleware onError hook cannot change how a run fails](agent-onerror-hooks-cannot-change-the-failure.md) — Hook throws are swallowed so observability code can never convert, mask, or replace the original error
43
+ - [AI runner methods must be called on the runner, never as detached references](agent-runner-methods-must-keep-their-receiver.md) — Implementations use this internally, so grabbing transcribe or generateSpeech as a bare function loses the receiver and throws at runtime
33
44
  - [Agent speech travels as a CUSTOM AG-UI event rather than being dropped](agent-speech-travels-as-a-custom-agui-event.md) — AG-UI has no speech event, and dropping it makes a voice agent reached over HTTP silently inaudible while the provider still bills for the audio
34
- - [The AG-UI bridge obeys the client's event-ordering contract exactly](ai-agent-agui-bridge-obeys-the-client-ordering-contract.md) — RUN_STARTED opens lazily, RUN_FINISHED fires once on done, and step names are globally sequential — a violation makes the client drop the whole stream
35
- - [Audio chunks are labelled with the format the provider actually returned](ai-agent-audio-chunks-carry-the-format-the-provider-returned.md) — The configured format is only a request, so the response's own format wins with the request and pcm16 as fallbacks
36
- - [A credential-required tool result never reaches the client or the transcript](ai-agent-credential-suspensions-hide-the-tool-result.md) — The run suspends with credential-request events instead, leaving the tool call unresulted so it can be resumed after connecting
37
- - [Delegate mode hides the parent's later text, supervise mode hides the sub-agent's](ai-agent-delegate-and-supervise-hide-different-text.md) — Exactly one voice reaches the client per agent-mode; approvals and tool events always flow through either way
38
- - [Nulls are stripped from approved tool arguments before execution](ai-agent-llm-tool-arguments-have-nulls-stripped.md) — LLMs emit null for optional fields where the schema layer expects undefined, so a resumed tool call is cleaned recursively first
39
- - [Model resolution stays a single seam even though it is currently a passthrough](ai-agent-model-config-stays-a-single-resolution-seam.md) — resolveModelConfig looks removable but is the one merge point for per-request model overrides
40
- - [An aiMiddleware onError hook cannot change how a run fails](ai-agent-onerror-hooks-cannot-change-the-failure.md) — Hook throws are swallowed so observability code can never convert, mask, or replace the original error
41
- - [AI runner methods must be called on the runner, never as detached references](ai-agent-runner-methods-must-keep-their-receiver.md) — Implementations use this internally, so grabbing transcribe or generateSpeech as a bare function loses the receiver and throws at runtime
42
- - [Persisting an agent stream from send is best-effort and must never reject](ai-agent-stream-persistence-is-best-effort.md) — The persisting channel flushes fire-and-forget because send is synchronous; a storage failure degrades the transcript instead of killing the process
43
- - [A delegated sub-agent inherits the parent run's context block](ai-agent-sub-agents-inherit-the-parent-context-block.md) — The sub-agent tool schema carries only message and session, so the parent's identifier block is forwarded rather than re-typed by the model
44
- - [A tool's execute() failure is logged before the AI SDK swallows it](ai-agent-tool-execute-failures-are-logged-unconditionally.md) — Every agent tool is wrapped in a logging try/catch, because a thrown tool error otherwise becomes a conversational reply and is invisible server-side
45
- - [Voice input transcribes audio parts sequentially and in place](ai-agent-voice-input-transcribes-audio-parts-in-place.md) — Each audio part is replaced by its text where it sat, one at a time, bounding concurrent downloads and preserving content order
46
- - [Working memory is persisted only when the merged value validates](ai-agent-working-memory-is-persisted-only-when-valid.md) — A failed schema check logs and drops the update rather than saving it, because invalid state poisons every later read
45
+ - [Persisting an agent stream from send is best-effort and must never reject](agent-stream-persistence-is-best-effort.md) — The persisting channel flushes fire-and-forget because send is synchronous; a storage failure degrades the transcript instead of killing the process
46
+ - [A delegated sub-agent inherits the parent run's context block](agent-sub-agents-inherit-the-parent-context-block.md) — The sub-agent tool schema carries only message and session, so the parent's identifier block is forwarded rather than re-typed by the model
47
+ - [A tool's execute() failure is logged before the AI SDK swallows it](agent-tool-execute-failures-are-logged-unconditionally.md) — Every agent tool is wrapped in a logging try/catch, because a thrown tool error otherwise becomes a conversational reply and is invisible server-side
48
+ - [Voice input transcribes audio parts sequentially and in place](agent-voice-input-transcribes-audio-parts-in-place.md) — Each audio part is replaced by its text where it sat, one at a time, bounding concurrent downloads and preserving content order
49
+ - [Working memory is persisted only when the merged value validates](agent-working-memory-is-persisted-only-when-valid.md) — A failed schema check logs and drops the update rather than saving it, because invalid state poisons every later read
47
50
  - [An addon's scope root loses to a root the host app already declares](an-addon-scope-root-loses-to-a-root-the-host-already-declares.md) — loadAddonFunctionsMeta merges addon scope trees by root name, first declaration wins, so an addon sharing a root with its host contributes nothing — silently
48
51
  - [An interrupt is not a failure, and the non-streaming path throws rather than returning](an-agent-interrupt-is-not-a-failure.md) — It skips the onError hooks and never becomes an errorMessage; with no partial reply to hand back, a typed throw is what distinguishes it from a provider outage
52
+ - [A reachable agent is offered by its registry key, filtered by the virtual user's scopes](an-agent-is-offered-under-its-registry-key-and-only-within-the-virtual-users-scopes.md) — reachableAgents offers an agent to a virtual user under the key it is registered with, and only when the virtual user holds the agent's declared scopes; the server still enforces access
49
53
  - [An interrupt for a run owned by another instance says so, rather than returning false](an-agent-run-owned-by-another-instance-says-so.md) — A bare false is indistinguishable from "already finished", which is the one deployment shape the in-process registry cannot cover
50
54
  - [A wrapped agent-stream send must return the inner send's promise](an-agent-stream-send-must-return-the-inner-sends-promise.md) — Middleware runs asynchronously, so dropping the returned promise turns every awaited channel.send upstream into a no-op
51
55
  - [A message with nothing to say carries no text part at all](an-empty-text-part-is-omitted-from-an-agent-message.md) — An attachment on its own is a real turn, and providers are entitled to reject an empty text part sitting beside it
52
56
  - [A transcript is recorded only when something was actually heard](an-empty-transcript-is-not-recorded.md) — Recording an empty string sends a transcript event saying the user said nothing, which renders as an empty bubble rather than a pending one
57
+ - [An OAuth2 credential implies its app client secret, optional and overridable by declaration](an-oauth2-credential-implies-an-optional-app-secret.md) — deriveOAuth2AppSecrets creates the app client id and secret for each OAuth2 credential, marked optional, unless the project already declares that secret id
53
58
  - [An unref'd timer cannot be awaited under node:test](an-unref-d-timer-cannot-be-awaited-under-node-test.md) — The channel RPC registry unrefs its timeout so a pending call never holds a process open, which makes that timeout unawaitable in a test and silently cancels the rest of the file
59
+ - [Analytics identity resolvers run in declared order so consent is read before a cookie is minted](analytics-identity-resolvers-run-in-declared-order.md) — composeAnalyticsIdentity threads each resolver's output into the next, so a cookie-minting resolver sees the consent that an earlier resolver read
54
60
  - [Channel message handlers accept three config shapes](channel-message-handlers-accept-three-config-shapes.md) — onMessage may be a function config, a wrapper with middleware, or a wrapper around a function config — the runtime discriminates structurally
55
61
  - [Channel middleware caching covers only statically resolved middleware](channel-middleware-caches-only-statically-resolved-middleware.md) — Inherited tag/named middleware is cached per uid; per-run closures are appended fresh every call, at the cost of re-allocating the array
56
62
  - [`getState<T>()` lets the caller name any type, so every implementation asserts](channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md) — The type parameter appears only in the return position with nothing to infer from, so it is a caller-chosen claim rather than a checked fact
@@ -61,9 +67,8 @@ caller is entitled to assume.
61
67
  - [CLI stdout is reserved for machine-readable output](cli-stdout-is-reserved-for-machine-readable-output.md) — The default renderer emits single-line NDJSON, diagnostics go to stderr, and --json only hijacks rendering for commands that declared a renderer
62
68
  - [CLI unknown long options warn instead of failing](cli-unknown-long-options-warn-instead-of-failing.md) — Unrecognised --long options are accepted, warned about and dropped so older binaries tolerate newer invocations, while unknown short flags stay hard errors
63
69
  - [A column's at-rest form is an axis of its own](core-column-form-is-an-axis-of-its-own.md) — How a value is stored is independent of how sensitive it is, so form carries a required nominal brand on writes while classification stays optional on reads
64
- - [The data-classification brand is an optional property](core-data-classification-brand-is-an-optional-property.md) — Making **classification** required would break ordinary Kysely operands, so the brand only constrains values flowing out
70
+ - [The data-classification brand is an optional property](core-data-classification-brand-is-an-optional-property.md) — Making __classification__ required would break ordinary Kysely operands, so the brand only constrains values flowing out
65
71
  - [The function runner restores the wire fields it overwrites](core-function-runner-restores-the-wire-fields-it-overwrites.md) — One wire object is reused across nested calls, so functionId, audit, addonNamespace and rpc are saved and put back in a finally
66
- - [The function runner resolves wire services itself](the-runner-resolves-wire-services-itself.md) — A function's wire services come from its own package's factory or the app's registered one, never borrowed from whoever called it
67
72
  - [Hot reload merges generated meta and never replaces it](core-hot-reload-merges-generated-meta-never-replaces-it.md) — Reloading codegen output must preserve runtime-registered meta, which no generated JSON contains
68
73
  - [Hot reload owns its module registry instead of re-importing](core-hot-reload-owns-its-module-registry.md) — Dev reload transpiles to CJS and runs modules through vm.compileFunction, because the native ESM loader map cannot be evicted
69
74
  - [Middleware order is resolution scope first, then priority](core-middleware-order-is-scope-then-priority.md) — Middleware is collected global to function, then stably sorted by priority, deduped, frozen and cached per wire
@@ -71,6 +76,9 @@ caller is entitled to assume.
71
76
  - [Scopes are an AND gate, separate from permissions](core-scopes-are-an-and-gate-separate-from-permissions.md) — Every declared scope must be held, so adding one can only narrow access — permissions OR, and can only widen it
72
77
  - [Pikku state is a global map written only at registration time](core-state-is-a-global-map-written-only-at-registration-time.md) — A symbol-keyed globalThis map holds the wiring registry; nothing per-request may ever be written to it
73
78
  - [Email meta is read uncached because codegen rewrites it mid-session](email-meta-is-read-uncached-because-codegen-rewrites-it-mid-session.md) — getEmailMeta re-reads its file on every call, unlike every other meta accessor, because the file appears and changes during a long-lived session
79
+ - [Feature flag declarations refuse an empty anyOf and conflicting scopes](feature-flag-declarations-refuse-an-empty-anyof-and-conflicting-scopes.md) — validateAndBuildFeatureFlagDefinitionsMeta throws on an anyOf that is an empty list, and on a repeated flag name whose scopes differ between declarations
80
+ - [Feature flags fail open on availability and never enforce access](feature-flags-fail-open-and-never-enforce-access.md) — A flag with no config row is available by default, anyOf decides only the advisory capable half, and the runner's assertFeatureAvailable checks availability alone
81
+ - [Flag declaration sync rewrites only the description and anyOf](flag-sync-rewrites-only-the-declared-text.md) — The FeatureFlagStore contract re-syncs a flag's description and anyOf on deploy and leaves enabled, rollout and overrides to the operator
74
82
  - [Gateway adapters resolve lazily and are promise-cached](gateway-adapters-resolve-lazily-and-are-promise-cached.md) — wireGateway accepts an adapter factory because real adapters need boot-time secrets, which forces the webhook GET route to be registered unconditionally
75
83
  - [Gateway middleware sees wire.rpc on webhook and websocket, but not on listener](gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md) — The listener transport invokes its handler directly, so middleware runs outside any runPikkuFunc call and the lazily-attached rpc is absent
76
84
  - [Gateway webhook challenges echo bytes not JSON](gateway-webhook-challenges-echo-bytes-not-json.md) — String verification challenges are returned raw with returnsJSON false, because platforms byte-compare the echo and JSON quoting fails the handshake
@@ -92,24 +100,25 @@ caller is entitled to assume.
92
100
  - [Local trigger and gateway services assume a single process](local-trigger-and-gateway-services-assume-a-single-process.md) — InMemoryTriggerService and LocalGatewayService start every listener unconditionally with no distributed claiming, so a second instance duplicates every event
93
101
  - [MCP wire names are assigned over the whole registry, not derived per name](mcp-wire-names-are-assigned-over-the-whole-registry.md) — Sanitising each name on its own is lossy — `a:b` and `a.b` both become `a_b` — so one of the two targets would be unreachable
94
102
  - [Node-only builtins are imported dynamically](node-only-builtins-are-imported-dynamically.md) — V8CoverageService imports node:inspector inside start() so the module stays loadable on runtimes that have no such builtin
95
- - [One project-shape check, called by both validators](one-project-shape-check-two-validators.md) — workspace validate and fabric validate were separate walks over the same project that duplicated sixteen findings verbatim; the shared half now lives in shared-checks.ts and fabric validate is that plus the deploy-shaped checks
103
+ - [One project-shape check, called by both validators](one-project-shape-check-two-validators.md) — workspace validate and cloud validate were separate walks over the same project that duplicated sixteen findings verbatim; the shared half now lives in shared-checks.ts and cloud validate is that plus the deploy-shaped checks
96
104
  - [Only functions marked `expose: true` enter a virtual user's catalogue](only-exposed-functions-enter-a-virtual-user-catalogue.md) — Absent is not permissive — an unexposed function 404s over rpc, so offering one spends a step to learn nothing about the product
97
105
  - [Queue group concurrency keeps one shared queue fair](queue-group-concurrency-keeps-one-shared-queue-fair.md) — Per-group slot caps let many producers share a single queue instead of one queue each, at the cost of a cap that must stay under batchSize
98
106
  - [Queue jobs always carry an explicit attempts count](queue-jobs-always-carry-an-explicit-attempts-count.md) — resolveJobOptions always passes attempts so a queue backend can never apply its own retry default, and an explicit retries of 0 is honoured
99
107
  - [Remote addons dispatch over HTTP instead of through local function meta](remote-addons-dispatch-over-http-instead-of-local-meta.md) — wireRemoteAddon ships the addon as a devDependency and posts to the host, so there is deliberately no local handler to resolve
108
+ - [System role names cannot contain the scope separator, and redeclared roles must grant the same scopes](role-names-cannot-contain-the-scope-separator.md) — validateAndBuildSystemRoleDefinitionsMeta rejects an empty role name or one containing ':', and throws when two declarations of one role name grant different scopes
100
109
  - [Bare RPC names resolve through the caller's package scope before root](rpc-names-resolve-through-package-scope-before-root.md) — An addon's own functions win over root RPC meta for bare names, and the resolving scope is returned so it can be threaded into runPikkuFunc
101
- - [Scenario agent calls sign in on 401 only](scenario-agent-calls-sign-in-on-401-only.md) — postAgent sends the first request with whatever session it holds and logs in only if refused, so an actor can converse with a no-auth agent with no sign-in wiring
110
+ - [Scenario agent calls retry sign-in only on 401](scenario-agent-calls-sign-in-on-401-only.md) — HttpVirtualUser.sendAgent sends with whatever session the jar holds and signs in only after a 401, so a no-auth agent route never needs a sign-in; converse signs in eagerly instead
102
111
  - [`given` and `when` are sugar for each other; `then` is not](scenario-given-and-when-are-sugar-but-then-is-not.md) — The phase decides whether a step's bindings are alternatives or witnesses, so the same step function called as `when` and as `then` runs differently
103
112
  - [Scenario meta lives apart from app meta but merges when read off disk](scenario-meta-lives-apart-from-app-meta-but-merges-when-read-off-disk.md) — Scenarios generate into .pikku/scenarios so no app module imports them, yet MetaService folds them back into the workflow and function meta
104
113
  - [Scenario steps return drained response records](scenario-steps-return-drained-response-records.md) — A scenario step hands back ScenarioHttpResponse rather than a Response, because the body stream reads once and the return value crosses into the run record
105
- - [Scenarios, steps and personas live in files named for them, and validate errors when they do not](scenarios-live-in-files-named-for-them.md) — pikkuScenario/pikkuFeature/pikkuScenarioStep must be in *.scenario.ts, *.scenarios.ts or *.steps.ts, and definePersonas/runVirtualUser in *.virtual-user.ts or *.vu.ts — an error, because the mixing is only cheap to undo while it is one file
114
+ - [Scenarios, steps and virtual users live in files named for them, and validate errors when they do not](scenarios-live-in-files-named-for-them.md) — pikkuScenario/pikkuFeature/pikkuScenarioStep must be in *.scenario.ts, *.scenarios.ts or *.steps.ts, and defineVirtualUsers/runVirtualUser in *.virtual-user.ts or *.vu.ts — an error, because the mixing is only cheap to undo while it is one file
106
115
  - [Scope roots may be co-declared by an addon and its host app](scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md) — flattenScopeDefinitions dedupes ids because the same root can legitimately be declared twice, and every consumer requires one entry per scope
107
116
  - [Serverless channel disconnect must tolerate a missing channel](serverless-channel-disconnect-must-tolerate-a-missing-channel.md) — A failed channel lookup on disconnect returns quietly, because serverless runtimes deliver disconnect more than once
108
117
  - [sideEffects is an allowlist, because core genuinely has some](side-effects-are-an-allowlist-not-a-boolean.md) — The error registry is built by addError calls that run on import, so claiming sideEffects:false would let a bundler drop it and leave getErrorResponse unable to find any error
109
118
  - [Speech synthesis picks a voice per sentence but announces a limitation once](speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md) — A bilingual reply should speak the half it can, and repeating the notice for every sentence would bury the reply itself
110
- - [The actor's instructions say the word "json" because a degraded gateway demands it](the-actor-prompt-says-json-because-of-json-object-mode.md) — A gateway that cannot take a JSON schema falls back to OpenAI's json_object mode, which refuses any request whose prompt does not contain the word
111
119
  - [The agent `done` event goes through the middleware and is awaited](the-agent-done-event-goes-through-the-middleware-and-is-awaited.md) — `done` is the only end-of-reply signal a stream hook gets, and buffering hooks flush on it — sending it raw discards work already paid for
112
120
  - [The API report pins members, because the export list only pins names](the-api-report-pins-members-not-just-names.md) — public-surface.json catches an export appearing or vanishing; it cannot see a method added to an interface, which is the change that breaks a consumer's build
121
+ - [The core scope validator refuses separator, wildcard and empty scope segments](the-core-scope-validator-refuses-separator-wildcard-and-empty-segments.md) — assertSegment rejects a scope segment that contains the colon separator, equals the wildcard, or is empty
113
122
  - [The dev queue copies prod timing and serialization semantics](the-dev-queue-copies-prod-timing-and-serialization-semantics.md) — InMemoryQueueService dispatches via setTimeout, retries with backoff, and JSON round-trips every payload so dev behaviour matches a real backend
114
123
  - [One door per name — the ecosystem tier and the package root are both gone](the-ecosystem-entry-point-carries-the-adapter-surface.md) — the adapter surface was split onto @pikku/core/ecosystem to keep a stability promise at the root; publishing every module twice cost more than the promise was worth, so both the facades and the root barrel were deleted
115
124
  - [The embedding model is pinned per service and doc/query embedding is split](the-embedding-model-is-pinned-per-service-and-doc-query-embedding-is-split.md) — AIEmbeddingService fixes its model at construction so index and query share a vector space, and separates embedDocuments from embedQuery for asymmetric models
@@ -118,17 +127,24 @@ caller is entitled to assume.
118
127
  - [The MCP handshake is challenged only when every target is gated](the-mcp-handshake-is-challenged-only-when-every-target-is-gated.md) — A client decides whether a server speaks OAuth from the handshake alone, so a fully-gated server that answers initialize with a 200 is detected as needing no sign-in
119
128
  - [The middleware resolution cache is deliberately unbounded](the-middleware-resolution-cache-is-deliberately-unbounded.md) — Its keyspace is the set of registered wires, not request traffic, and middleware is dynamic — so eviction would buy nothing and cost the dedupe guarantee
120
129
  - [The per-invocation rpc view is a class, because an object literal with a getter is slow to build](the-per-invocation-rpc-view-is-a-class.md) — An accessor declared on an object literal is defined per instance, which drops the literal off V8's fast construction path — measured at 1.15µs against 0.47µs, on every request
121
- - [The persona runtime is exported from @pikku/core/persona, never from services](the-persona-runtime-is-exported-from-the-persona-entry-point.md) — Those values reach the actor-flow and agent runners, which no production server runs — and an unbundled deploy loads whatever the import graph names
130
+ - [The pikku user id resolves in a fixed order](the-pikku-user-id-resolves-in-a-fixed-order.md) — defaultPikkuUserIdResolver takes an explicit pikkuUserId first, then the session userId, then the queue job's id, then the workflow's id
131
+ - [The function runner resolves wire services itself](the-runner-resolves-wire-services-itself.md) — A function's wire services come from its own package's factory or the app's registered one, never borrowed from whoever called it
122
132
  - [The schema service is never stubbed, or tests validate nothing](the-schema-service-is-never-stubbed.md) — createStubProxy returns undefined for the schema property so the real schema service is built — a stubbed one turns validation into a silent no-op
123
133
  - [A voice turn's transcript is sent before the run starts, on the raw channel](the-transcript-event-is-sent-ahead-of-the-run.md) — The client does not know what it said, and an answer starts streaming within a few hundred milliseconds — a question arriving after its answer reads as the wrong question
124
134
  - [The catalogue is the only gate on what a virtual user may call](the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md) — Narrowing happens once at derivation, so there is no second check in the run loop that could drift out of step with it
135
+ - [The virtual user's instructions say the word "json" because a degraded gateway demands it](the-virtual-user-prompt-says-json-because-of-json-object-mode.md) — A gateway that cannot take a JSON schema falls back to OpenAI's json_object mode, which refuses any request whose prompt does not contain the word
136
+ - [The virtual user runtime is exported from @pikku/core/virtual-user, never from services](the-virtual-user-runtime-is-exported-from-the-virtual-user-entry-point.md) — Those values reach the conversation and agent runners, which no production server runs — and an unbundled deploy loads whatever the import graph names
125
137
  - [The worker disposition is the only one not testing anything](the-worker-disposition-is-the-one-that-is-not-testing.md) — It is doing the job, so it abandons rarely, runs cool, and is told to stop and say so rather than guess
126
138
  - [Thread history records what the model was asked, which for a spoken turn is the transcript](thread-history-records-the-transcript-not-the-audio.md) — The wire carried a base64 audio blob; persisting it writes megabytes of unreadable data and loses the only readable record of the turn
127
139
  - [Trigger declaration is split from trigger source](trigger-declaration-is-split-from-trigger-source.md) — Triggers are declared everywhere but subscribed only in the trigger worker, so app processes never open the underlying subscription
128
140
  - [TypedSecretService caches for the process lifetime](typed-secret-service-caches-for-the-process-lifetime.md) — Resolved secrets are cached with no TTL, so a secret rotated out of band is not picked up until restart — tracked as pikkujs/pikku#964
129
- - [Both validators check personas through one shared module, and a persona counts from either the meta or the source](validate-checks-personas-through-a-shared-module.md) — workspace validate and fabric validate are separate implementations, so the persona/actor/knowledge checks live in persona-checks.ts and are called by both — and a persona is counted from the generated meta or a definePersonas() source scan, whichever answers first
141
+ - [Both validators check virtual users through one shared module, and a virtual user counts from either the meta or the source](validate-checks-virtual-users-through-a-shared-module.md) — 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
130
142
  - [pikku validate runs every applicable check, it does not detect a project kind](validate-runs-checks-by-precondition.md) — one `pikku validate` replaces `pikku workspace validate`; checks declare a precondition and run wherever it holds, because the repos that matter are an app and a pile of addons at the same time
143
+ - [The conversation engine only sees a transport-agnostic target driver](virtual-user-conversation-drives-the-target-through-a-transport-seam.md) — The engine never imports the agent runner; the target is injected as run/approve, so scenarios exercise the real wire path
144
+ - [A conversation verdict is the virtual user's self-evaluation, not an assertion](virtual-user-conversation-verdicts-are-llm-self-evaluations.md) — The engine returns what the virtual user judged plus the transcript; deterministic checks stay with the caller
145
+ - [A virtual user conversation starts from a seeded kickoff message](virtual-user-conversations-seed-a-hidden-kickoff-message.md) — 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
131
146
  - [A scenario's step order for a virtual user is insertion order, not a graph traversal](virtual-user-step-order-comes-from-insertion-order.md) — The CLI writes nodes in declaration order, so following `next` would buy an ordering that is already true and cost a traversal that has to interpret branches
147
+ - [Virtual users are declared in exactly one defineVirtualUsers call per codebase](virtual-users-are-declared-in-one-definevirtualusers-call-per-codebase.md) — defineVirtualUsers is a no-op declaration that the inspector reads statically, and a second call anywhere in the codebase fails the build
132
148
  - [Only an explicit `false` silences a spoken reply](voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md) — The key being absent means no voice input is wired and nothing reported either way, so those callers keep the behaviour they had before the option existed
133
149
  - [Webhook delivery history records every attempt, best effort](webhook-delivery-history-records-every-attempt-best-effort.md) — The webhook worker persists each attempt before it throws, and a failure to persist is logged rather than allowed to mask the delivery result
134
150
  - [Webhook service collaborators are constructor args, not locator lookups](webhook-service-collaborators-are-constructor-args-not-locator-lookups.md) — QueueWebhookService takes its queue as a constructor parameter so a project wiring webhooks without a queue fails to compile instead of at first send
@@ -153,14 +169,13 @@ caller is entitled to assume.
153
169
  - [The workflow run timeline is a pure fold over durable history, with the row's status as the authority](workflow-run-timeline-is-a-pure-fold-over-durable-history.md) — No IO in the fold keeps time-travel transport-independent; the terminal event comes from `status`, not from a timestamp every backend populates
154
170
  - [Scenario steps default to no retries, and a whole poll is one durable step](workflow-scenario-assertions-never-retry-and-record-one-step.md) — Retrying a failed assertion is wrong for a test primitive; recording the poll as one step means replay returns the outcome, not the loop
155
171
  - [Scenario lifecycle hooks are a scenario-only affordance and never mask the failure they follow](workflow-scenario-hooks-are-a-scenario-only-affordance.md) — A durable workflow replays, so a callback that reruns each replay has no honest meaning there
156
- - [Scenario prose is rendered out of typed calls, not parsed into them](workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md) — The inversion of cucumber — a readable report with no regex step registry to maintain
172
+ - [Scenario prose is rendered out of typed calls, not parsed into them](workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md) — A readable report with no regex step registry to maintain
157
173
  - [A quarantined scenario states its reason in code, not in a CI invocation](workflow-scenario-quarantine-reason-lives-in-code.md) — `skip` carries the why next to the scenario it applies to, and naming the scenario explicitly still runs it
158
174
  - [Scenario step targets are string literals so the inspector can read them statically](workflow-scenario-step-targets-are-string-literals-for-the-inspector.md) — `step/given/when/then` mirror `do`'s RPC shape because the extractor reads a literal, not an imported symbol
159
- - [A step's compensation handler runs as a durable step of its own, and never compensates itself](workflow-step-compensation-runs-as-its-own-durable-step.md) — A refund or rollback must not fire twice on replay, so `onError` is recorded as `<step>:onError` with retries disabled
175
+ - [A step's compensation runs as a durable step of its own, declared on the function](workflow-step-compensation-runs-as-its-own-durable-step.md) — A refund or rollback must not fire twice on replay, so it is recorded as `<step>:compensate`; `onError` is gone
160
176
  - [A failed workflow step dispatch is transient infrastructure, not a run failure](workflow-step-dispatch-failure-is-transient-not-a-run-failure.md) — Queue-unreachable errors leave the run running and the step pending so the orchestrator replays; marking the run failed loses it
161
- - [A workflow step lock is held only to claim the step, never across its execution](workflow-step-lock-is-held-only-to-claim-the-step.md) — Holding the advisory lock — and its pooled connection — across step work exhausted the connection pool and self-deadlocked
177
+ - [A workflow step lock is held only to claim the step, never across its execution](workflow-step-lock-is-held-only-to-claim-the-step.md) — Holding the step lock across step work exhausted the connection pool and self-deadlocked when it was an advisory lock; it stays claim-only on leases
162
178
  - [A workflow step's recorded `rpcName` is provenance only — nothing dispatches off it](workflow-step-rpc-name-is-provenance-only.md) — It exists so a reader can join a runtime step row back to the declaration that produced it, especially when the durable name was built in a loop
163
179
  - [A suspend or approval `reason` is the step's durable identity, not just a message](workflow-suspend-and-approval-reasons-are-durable-step-identities.md) — The reason is namespaced and used raw as the step key, so it must be derived deterministically across replays
164
180
  - [A suspended workflow run keeps its in-process context; only terminal runs release it](workflow-suspended-runs-keep-their-in-process-context.md) — `suspended` is absent from the terminal set on purpose, and a context is dropped only when nothing is holding it open
165
-
166
181
  <!-- /pikku:knowledge-index -->
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  type: decision
3
3
  title: One project-shape check, called by both validators
4
- description: workspace validate and fabric validate were separate walks over the same project that duplicated sixteen findings verbatim; the shared half now lives in shared-checks.ts and fabric validate is that plus the deploy-shaped checks
5
- tags: cli, validate, fabric
4
+ description: workspace validate and cloud validate were separate walks over the same project that duplicated sixteen findings verbatim; the shared half now lives in shared-checks.ts and cloud validate is that plus the deploy-shaped checks
5
+ tags: cli, validate, cloud
6
6
  ---
7
7
 
8
8
  # One project-shape check, called by both validators
9
9
 
10
- `pikku workspace validate` and `pikku fabric validate` read like one command
10
+ `pikku workspace validate` and `pikku validate` read like one command
11
11
  with a flag. They were two implementations — 495 lines and 2026 — that walked
12
12
  the same project and emitted sixteen identical findings. `functions-dir-missing`
13
13
  existed character for character in both files. All sixteen already agreed on
@@ -17,20 +17,20 @@ What the duplication cost was not the lines. It was that each validator carried
17
17
  checks the other lacked for no reason anyone could name, and nobody noticed
18
18
  because nobody read them side by side:
19
19
 
20
- - fabric validate never checked that `packages/functions` declares zod v4, or
20
+ - cloud validate never checked that `packages/functions` declares zod v4, or
21
21
  that it has a `package.json` at all — while it did check that six other
22
22
  dependencies are present.
23
23
  - workspace validate never checked the four scaffold flags beyond `console`,
24
24
  though the console it requires is useless without `rpc`.
25
- - fabric validate swallowed a JSON parse error and reported
25
+ - cloud validate swallowed a JSON parse error and reported
26
26
  `pikku-config-missing` for a file sitting right there.
27
27
 
28
- Two of workspace validate's own checks were not merely absent from fabric — they
28
+ Two of workspace validate's own checks were not merely absent from cloud — they
29
29
  had never run anywhere:
30
30
 
31
31
  - the auth checks were gated on a middleware instance with `definitionId ===
32
32
  'betterAuthSession'`, but the CLI wires `betterAuthStatelessSession` whenever
33
- `session.cookieCache` is on, which is the configuration Fabric asks for. So
33
+ `session.cookieCache` is on, which is the configuration Pikku Studio Cloud asks for. So
34
34
  they skipped precisely the apps most likely to have auth.
35
35
  - they looked for migrations in `packages/functions/db/`, and for tables named
36
36
  `app_user` and `auth_verification_token`. `pikku db migrate` reads migrations
@@ -39,15 +39,15 @@ had never run anywhere:
39
39
  wrong for every project it fired on.
40
40
 
41
41
  `shared-checks.ts` is now the half that is true of any pikku project regardless
42
- of where it deploys. Workspace validate is exactly that module; fabric validate
42
+ of where it deploys. Workspace validate is exactly that module; cloud validate
43
43
  is that module plus the deploy-shaped checks — themes, frontends, the
44
44
  Cloudflare adapter, the `.gitignore` contract, the frontend type-check.
45
45
 
46
- Fabric's fix hints won where the two differed: they are multi-line and show the
46
+ Pikku Studio Cloud's fix hints won where the two differed: they are multi-line and show the
47
47
  config block to paste, and there was no reason workspace users were getting the
48
48
  one-line version.
49
49
 
50
50
  **What this rules out:** adding a check to one validator and not the other;
51
- `pikku fabric validate` reporting a corrupt config as a missing one; asserting
51
+ `pikku validate` reporting a corrupt config as a missing one; asserting
52
52
  auth table names no scaffold generates; and reading migrations from anywhere
53
53
  other than where `pikku db migrate` reads them.
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: decision
3
+ title: System role names cannot contain the scope separator, and redeclared roles must grant the same scopes
4
+ description: validateAndBuildSystemRoleDefinitionsMeta rejects an empty role name or one containing ':', and throws when two declarations of one role name grant different scopes
5
+ tags: core, roles, scopes, validation
6
+ ---
7
+
8
+ # System role names cannot contain the scope separator, and redeclared roles must grant the same scopes
9
+
10
+ `packages/core/src/wirings/role/validate-role-definitions.ts` checks declared
11
+ system roles.
12
+
13
+ The separator is `:` (`validate-role-definitions.ts:7`). A name is refused when it
14
+ is empty (`validate-role-definitions.ts:10-12`) or contains the separator
15
+ (`validate-role-definitions.ts:13-18`). The message says the separator "delimits
16
+ scope ids; a role named like a scope reads as one" (`validate-role-definitions.ts:16`).
17
+
18
+ When a name is declared twice, the scope lists are compared after sorting, so order
19
+ does not matter (`validate-role-definitions.ts:69-71`). A different set throws,
20
+ with the message "Roles sharing a name must grant the same scopes"
21
+ (`validate-role-definitions.ts:72-79`). An identical repeat is skipped
22
+ (`validate-role-definitions.ts:80`).
23
+
24
+ `flattenSystemRoleDefinitions` keeps the first entry for each name
25
+ (`validate-role-definitions.ts:36-40`). Its doc comment says this is safe only
26
+ because the validator already guaranteed the repeats are identical
27
+ (`validate-role-definitions.ts:24-29`).
28
+
29
+ **What this rules out:** a role named with a scope-like `:` in it, and two
30
+ declarations of one role that grant different scopes.
@@ -1,27 +1,31 @@
1
1
  ---
2
2
  type: decision
3
- title: Scenario agent calls sign in on 401 only
4
- description: postAgent sends the first request with whatever session it holds and logs in only if refused, so an actor can converse with a no-auth agent with no sign-in wiring
3
+ title: Scenario agent calls retry sign-in only on 401
4
+ description: HttpVirtualUser.sendAgent sends with whatever session the jar holds and signs in only after a 401, so a no-auth agent route never needs a sign-in; converse signs in eagerly instead
5
5
  tags: services
6
6
  ---
7
7
 
8
- # Scenario agent calls sign in on 401 only
8
+ # Scenario agent calls retry sign-in only on 401
9
9
 
10
- `HttpScenarioActor.postAgent`
11
- (`packages/core/src/services/http-scenario-actors.ts`) sends its first request
12
- with whatever cookie the jar happens to hold — none, for an actor that has never
13
- signed in — and only a 401 triggers `login()` and a single retry. Its sibling
14
- `invokeRaw` does the opposite: it signs in eagerly before the first RPC.
10
+ `HttpVirtualUser.sendAgent` (`packages/core/src/services/http-virtual-users.ts`)
11
+ sends its request with whatever cookie the jar holds. Only a 401 triggers
12
+ `signOut()`, `login()` and a single retry. The agent calls that go through it
13
+ are the stream and approve paths and `postAgent`. Its sibling `invokeRaw` does
14
+ the opposite: it signs in eagerly before the first RPC.
15
15
 
16
- The asymmetry is deliberate. Agent HTTP routes may be public; RPC routes are
17
- assumed not to be. Making agent calls eager would mean an actor could not talk to
18
- a no-auth agent at all without a sign-in endpoint, an actor secret and a user
19
- table — a large amount of wiring to exercise an agent that requires none of it.
20
- Deferring to the 401 keeps the authenticated case working (the retry succeeds
21
- with the session attached) at the cost of one extra round trip the first time.
16
+ `HttpVirtualUser.converse` does not rely on that path. Before its first turn it
17
+ calls `login()` when `signedIn` is false. The reason is in the code comment
18
+ there: a public agent route never returns 401, so the retry would never sign
19
+ the virtual user in, and a virtual user is a real account, so conversing as
20
+ nobody is not the intent. See
21
+ [a virtual user's converse signs in before its first turn](a-virtual-user-converse-signs-in-before-its-first-turn.md).
22
22
 
23
- **What this rules out:** unifying the two paths so `postAgent` signs in up front
24
- "like `invokeRaw` does", and removing the 401 retry on the grounds that
25
- `invokeRaw` already guarantees a session. Note the ordering consequence: an actor
26
- whose first action is `converse` against a no-auth agent never signs in at all, so
27
- nothing else may assume `signedIn` is true after a conversation.
23
+ The 401 path exists because agent HTTP routes may be public; RPC routes are
24
+ assumed not to be. Making every agent call eager would mean a virtual user could
25
+ not reach a no-auth agent without a sign-in endpoint and credentials. Deferring
26
+ to the 401 keeps the authenticated case working (the retry succeeds with the
27
+ session attached) at the cost of one extra round trip the first time.
28
+
29
+ **What this rules out:** removing the 401 retry from `sendAgent` on the grounds
30
+ that a sign-in always happens first, and making `sendAgent` sign in up front
31
+ "like `invokeRaw` does" without first checking what the agent route requires.
@@ -8,7 +8,7 @@ tags: services
8
8
  # Scenario steps return drained response records
9
9
 
10
10
  `ScenarioHttpResponse` and `readScenarioHttpResponse`
11
- (`packages/core/src/services/scenario-actors-service.ts`) exist because a `fetch`
11
+ (`packages/core/src/services/virtual-users-service.ts`) exist because a `fetch`
12
12
  `Response` cannot be a scenario step's return value. A step's result is
13
13
  serialised into the workflow run record, and a `Response` body is a stream that
14
14
  reads exactly once — by the time anything downstream looks at it, it is either
@@ -23,5 +23,5 @@ the payload was not JSON; its type parameter is a claim the caller makes, not on
23
23
  the transport checked.
24
24
 
25
25
  **What this rules out:** returning `Response` (or anything holding a stream) from
26
- a scenario step or an actor method, and dropping `serialized` as redundant with
26
+ a scenario step or a virtual user method, and dropping `serialized` as redundant with
27
27
  `body` — a non-JSON error body has no other route to the assertion.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  type: decision
3
- title: Scenarios, steps and personas live in files named for them, and validate errors when they do not
4
- description: pikkuScenario/pikkuFeature/pikkuScenarioStep must be in *.scenario.ts, *.scenarios.ts or *.steps.ts, and definePersonas/runVirtualUser in *.virtual-user.ts or *.vu.ts — an error, because the mixing is only cheap to undo while it is one file
5
- tags: cli, validate, scenarios, personas
3
+ title: Scenarios, steps and virtual users live in files named for them, and validate errors when they do not
4
+ description: pikkuScenario/pikkuFeature/pikkuScenarioStep must be in *.scenario.ts, *.scenarios.ts or *.steps.ts, and defineVirtualUsers/runVirtualUser in *.virtual-user.ts or *.vu.ts — an error, because the mixing is only cheap to undo while it is one file
5
+ tags: cli, validate, scenarios, virtual users
6
6
  ---
7
7
 
8
- # Scenarios, steps and personas live in files named for them, and validate errors when they do not
8
+ # Scenarios, steps and virtual users live in files named for them, and validate errors when they do not
9
9
 
10
10
  A `pikkuScenario` declared beside the functions it exercises reads as more of
11
11
  the same file. The wiring, the function, and the test of the function arrive in
@@ -18,7 +18,7 @@ So `validate` requires the declaration to be in a file named for what it is:
18
18
  | --------------------------------------------------- | --------------------------------- |
19
19
  | `pikkuScenario`, `pikkuFeature` | `*.scenario.ts`, `*.scenarios.ts` |
20
20
  | `pikkuScenarioStep` and its platform/addon variants | `*.steps.ts` (or the above) |
21
- | `definePersonas`, `runVirtualUser` | `*.virtual-user.ts`, `*.vu.ts` |
21
+ | `defineVirtualUsers`, `runVirtualUser` | `*.virtual-user.ts`, `*.vu.ts` |
22
22
 
23
23
  Three suffixes for scenarios rather than one, because the split that matters is
24
24
  scenarios apart from application code, not a particular spelling — `.steps.ts`
@@ -28,15 +28,15 @@ project that already made that split does not have to rename anything.
28
28
  **Error, not warning.** The mixing is cheap to undo while it is one file and
29
29
  expensive once it is thirty, and a warning is exactly the signal a project
30
30
  learns to scroll past. This is the one place where the scenario checks are
31
- strict: everything else `validate` says about scenarios (no personas declared,
32
- no actor sign-in, no environments) is a warning, because those describe an
31
+ strict: everything else `validate` says about scenarios (no virtual users declared,
32
+ no virtual user sign-in, no environments) is a warning, because those describe an
33
33
  under-tested project rather than an unreadable one.
34
34
 
35
- **Personas are the virtual-user file** because there is no `defineVirtualUsers`
36
- to look for. That name was retired in favour of `definePersonas`: a virtual user
35
+ **Virtual users are the virtual-user file** because there is no `defineVirtualUsers`
36
+ to look for. That name was retired in favour of `defineVirtualUsers`: a virtual user
37
37
  is derived rather than authored — the function meta becomes its catalogue, the
38
- scenario meta becomes its intents, the declared personas become its identities.
39
- The persona list is the only part a project writes, so it is the part that has
38
+ scenario meta becomes its intents, the declared virtual users become its identities.
39
+ The virtual user list is the only part a project writes, so it is the part that has
40
40
  to be findable by filename.
41
41
 
42
42
  Matching is on the call (`\bpikkuScenario\s*\(`), not the import, so an aliased
@@ -44,5 +44,5 @@ import cannot slip past and a mention in a comment or a string does not trip it.
44
44
  Generated files are exempt — codegen puts things where it likes.
45
45
 
46
46
  **What this rules out:** a scenario in a function file; a step in a wiring file;
47
- personas in a general-purpose `personas.ts`; and reporting any of these as a
47
+ virtual users in a general-purpose `virtual-users.ts`; and reporting any of these as a
48
48
  warning that a project can carry indefinitely.
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: decision
3
+ title: The core scope validator refuses separator, wildcard and empty scope segments
4
+ description: assertSegment rejects a scope segment that contains the colon separator, equals the wildcard, or is empty
5
+ tags: core, scopes, validation
6
+ ---
7
+
8
+ # The core scope validator refuses separator, wildcard and empty scope segments
9
+
10
+ `assertSegment` in `packages/core/src/wirings/scope/validate-scope-definitions.ts:11-27`
11
+ checks each segment of a declared scope name in three ways.
12
+
13
+ A segment containing the `:` separator throws (`:12-17`). The message says to
14
+ nest scopes with the `scopes` property instead. A segment equal to `*` throws
15
+ (`:18-23`), because the wildcard is reserved for granting a scope and its
16
+ descendants. An empty segment throws (`:24-26`).
17
+
18
+ The separator and the wildcard constants are at `:8-9`. The walk that calls this
19
+ check is described in `scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md`.
20
+
21
+ **What this rules out:** a declared scope whose name embeds a separator, which
22
+ would make it match a different scope, and a declared wildcard.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: The pikku user id resolves in a fixed order
4
+ description: defaultPikkuUserIdResolver takes an explicit pikkuUserId first, then the session userId, then the queue job's id, then the workflow's id
5
+ tags: core, identity
6
+ ---
7
+
8
+ # The pikku user id resolves in a fixed order
9
+
10
+ `defaultPikkuUserIdResolver` in `packages/core/src/services/pikku-user-id.ts:5-12`
11
+ returns the first of these that is set:
12
+
13
+ 1. `wire.pikkuUserId` (`:6`);
14
+ 2. `wire.session.userId`, when it is a string (`:7-9`);
15
+ 3. `wire.queue.pikkuUserId` (`:10`);
16
+ 4. `wire.workflow.pikkuUserId` (`:11`).
17
+
18
+ Otherwise it returns `undefined` (`:12`).
19
+
20
+ The queue case is about the producer's identity carried on a job, and is covered in
21
+ `queue-job-identities-are-signed-at-enqueue.md`. This note records only the order.
22
+
23
+ **What this rules out:** a session user id overriding an explicit `pikkuUserId` set
24
+ on the wire, and a queue or workflow id outranking a live session.
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  type: decision
3
- title: The actor's instructions say the word "json" because a degraded gateway demands it
3
+ title: The virtual user's instructions say the word "json" because a degraded gateway demands it
4
4
  description: A gateway that cannot take a JSON schema falls back to OpenAI's json_object mode, which refuses any request whose prompt does not contain the word
5
- tags: core, actor-flow
5
+ tags: core, virtual-user-conversation
6
6
  ---
7
7
 
8
- # The actor's instructions say the word "json"
8
+ # The virtual user's instructions say the word "json"
9
9
 
10
- Every call the actor makes wants a schema'd object back. A gateway that cannot
10
+ Every call the virtual user makes wants a schema'd object back. A gateway that cannot
11
11
  accept a JSON _schema_ degrades to OpenAI's `json_object` response mode, and
12
12
  that mode refuses the request outright — a hard API error, not a bad answer —
13
13
  unless the literal word "json" appears somewhere in the prompt.
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  type: decision
3
- title: The persona runtime is exported from @pikku/core/persona, never from services
4
- description: Those values reach the actor-flow and agent runners, which no production server runs — and an unbundled deploy loads whatever the import graph names
3
+ title: The virtual user runtime is exported from @pikku/core/virtual-user, never from services
4
+ description: Those values reach the conversation and agent runners, which no production server runs — and an unbundled deploy loads whatever the import graph names
5
5
  tags: core, services
6
6
  ---
7
7
 
8
- # The persona runtime lives on `@pikku/core/persona`
8
+ # The virtual user runtime lives on `@pikku/core/virtual-user`
9
9
 
10
- `HttpPersona`, `createHttpPersonas`, `readScenarioHttpResponse` and
11
- `postScenarioJson` are exported from `@pikku/core/persona`, not from
10
+ `HttpVirtualUser`, `createHttpVirtualUsers`, `readScenarioHttpResponse` and
11
+ `postScenarioJson` are exported from `@pikku/core/virtual-user`, not from
12
12
  `@pikku/core/services`. Their _types_ are re-exported freely — TypeScript erases
13
13
  those and they cost a bundle nothing.
14
14
 
15
- The values are different. They reach `http-personas`, which reaches the
16
- actor-flow conversation runner, which reaches the agent runner: an entire
15
+ The values are different. They reach `http-virtual-users`, which reaches the
16
+ conversation runner, which reaches the agent runner: an entire
17
17
  scenario and virtual-user runtime that no production server executes. An
18
18
  unbundled Node or Lambda deploy does no tree-shaking — it loads whatever the
19
19
  import graph names — so exporting one value from the services barrel pulls all
@@ -23,6 +23,6 @@ This is enforced, not merely intended: `production-barrels-stay-lean.test.ts`
23
23
  walks the value-import graph from `services/index.ts` and
24
24
  `wirings/workflow/index.ts` and fails if any of those modules is reachable.
25
25
 
26
- **What this rules out:** re-exporting a persona value from the services barrel
26
+ **What this rules out:** re-exporting a virtual user value from the services barrel
27
27
  for convenience, and "just one small helper" — the graph is transitive, and one
28
28
  value is enough to pull the whole runtime.