@pikku/core 0.12.78 → 0.12.80

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 (341) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/dist/dev/hot-reload.js +1 -6
  3. package/dist/ecosystem.d.ts +27 -0
  4. package/dist/ecosystem.js +26 -0
  5. package/dist/errors/error-handler.js +4 -2
  6. package/dist/function/function-runner.js +20 -42
  7. package/dist/function/functions.types.d.ts +9 -5
  8. package/dist/index.d.ts +4 -9
  9. package/dist/index.js +3 -8
  10. package/dist/middleware/auth-cookie.d.ts +0 -4
  11. package/dist/middleware/auth-cookie.js +38 -2
  12. package/dist/middleware/cors.js +1 -0
  13. package/dist/middleware/index.d.ts +1 -1
  14. package/dist/middleware/index.js +1 -1
  15. package/dist/middleware/remote-auth.js +14 -3
  16. package/dist/permissions.d.ts +8 -10
  17. package/dist/permissions.js +0 -11
  18. package/dist/pikku-state.js +12 -5
  19. package/dist/schema.js +35 -1
  20. package/dist/services/ai-run-state-service.d.ts +7 -1
  21. package/dist/services/in-memory-ai-run-state-service.d.ts +1 -1
  22. package/dist/services/in-memory-ai-run-state-service.js +2 -1
  23. package/dist/services/in-memory-workflow-service.d.ts +10 -0
  24. package/dist/services/in-memory-workflow-service.js +25 -0
  25. package/dist/services/local-content-request-handler.d.ts +21 -0
  26. package/dist/services/local-content-request-handler.js +72 -53
  27. package/dist/services/local-content.d.ts +6 -0
  28. package/dist/services/local-content.js +14 -1
  29. package/dist/services/workflow-service.d.ts +4 -2
  30. package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
  31. package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
  32. package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
  33. package/dist/testing/service-tests/ai-storage-service-tests.js +226 -0
  34. package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
  35. package/dist/testing/service-tests/channel-store-tests.js +72 -0
  36. package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
  37. package/dist/testing/service-tests/credential-service-tests.js +109 -0
  38. package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
  39. package/dist/testing/service-tests/deployment-service-tests.js +21 -0
  40. package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
  41. package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
  42. package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
  43. package/dist/testing/service-tests/secret-service-tests.js +80 -0
  44. package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
  45. package/dist/testing/service-tests/session-store-tests.js +43 -0
  46. package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
  47. package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
  48. package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
  49. package/dist/testing/service-tests/workflow-service-tests.js +150 -0
  50. package/dist/testing/service-tests.d.ts +6 -0
  51. package/dist/testing/service-tests.js +26 -791
  52. package/dist/types/core.types.d.ts +11 -1
  53. package/dist/types/state.types.d.ts +1 -1
  54. package/dist/utils/node-host-resolver.d.ts +12 -0
  55. package/dist/utils/node-host-resolver.js +16 -0
  56. package/dist/utils/safe-fetch.d.ts +18 -0
  57. package/dist/utils/safe-fetch.js +167 -29
  58. package/dist/wirings/actor-flow/run-conversation.js +1 -6
  59. package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
  60. package/dist/wirings/ai-agent/agent-rpc.js +53 -0
  61. package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
  62. package/dist/wirings/ai-agent/ai-agent-memory.js +3 -1
  63. package/dist/wirings/ai-agent/ai-agent-prepare.js +3 -9
  64. package/dist/wirings/ai-agent/ai-agent-runner.js +28 -107
  65. package/dist/wirings/ai-agent/ai-agent-stream.js +20 -61
  66. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +56 -0
  67. package/dist/wirings/ai-agent/ai-agent-turn.js +81 -0
  68. package/dist/wirings/ai-agent/ai-agent.types.d.ts +1 -1
  69. package/dist/wirings/ai-agent/voice-input.js +1 -6
  70. package/dist/wirings/ai-agent/voice-output.js +2 -12
  71. package/dist/wirings/channel/channel-common.js +1 -0
  72. package/dist/wirings/channel/channel-handler.js +3 -5
  73. package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
  74. package/dist/wirings/channel/channel-rpc-service.js +0 -8
  75. package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
  76. package/dist/wirings/channel/channel-rpc.types.js +8 -0
  77. package/dist/wirings/channel/channel-runner.d.ts +1 -3
  78. package/dist/wirings/channel/channel-runner.js +16 -8
  79. package/dist/wirings/channel/channel.types.d.ts +2 -0
  80. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  81. package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
  82. package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
  83. package/dist/wirings/cli/cli-runner.js +4 -2
  84. package/dist/wirings/cli/cli.types.d.ts +0 -8
  85. package/dist/wirings/cli/command-parser.js +13 -0
  86. package/dist/wirings/gateway/gateway-runner.js +41 -5
  87. package/dist/wirings/http/http-routes.js +2 -0
  88. package/dist/wirings/http/http-runner.d.ts +0 -10
  89. package/dist/wirings/http/http-runner.js +6 -13
  90. package/dist/wirings/http/http.types.d.ts +0 -10
  91. package/dist/wirings/http/index.d.ts +1 -1
  92. package/dist/wirings/http/index.js +1 -1
  93. package/dist/wirings/mcp/mcp-runner.d.ts +0 -7
  94. package/dist/wirings/mcp/mcp-runner.js +0 -6
  95. package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
  96. package/dist/wirings/rpc/rpc-runner.js +53 -86
  97. package/dist/wirings/rpc/rpc-types.d.ts +3 -0
  98. package/dist/wirings/secret/validate-secret-definitions.js +2 -0
  99. package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
  100. package/dist/wirings/trigger/trigger-runner.js +1 -0
  101. package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
  102. package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
  103. package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
  104. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +29 -2
  105. package/dist/wirings/workflow/feature.js +7 -4
  106. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
  107. package/dist/wirings/workflow/graph/graph-runner.js +8 -7
  108. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
  109. package/dist/wirings/workflow/index.d.ts +7 -2
  110. package/dist/wirings/workflow/index.js +5 -1
  111. package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
  112. package/dist/wirings/workflow/pikku-scenario-service.js +38 -10
  113. package/dist/wirings/workflow/pikku-workflow-service.d.ts +44 -111
  114. package/dist/wirings/workflow/pikku-workflow-service.js +86 -404
  115. package/dist/wirings/workflow/workflow-approval.d.ts +39 -0
  116. package/dist/wirings/workflow/workflow-approval.js +114 -0
  117. package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
  118. package/dist/wirings/workflow/workflow-constants.js +35 -0
  119. package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
  120. package/dist/wirings/workflow/workflow-errors.js +112 -0
  121. package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
  122. package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
  123. package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
  124. package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
  125. package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
  126. package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
  127. package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
  128. package/dist/wirings/workflow/workflow-recovery.js +101 -0
  129. package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
  130. package/dist/wirings/workflow/workflow-run-engine.types.js +1 -0
  131. package/dist/wirings/workflow/workflow-run-ownership.d.ts +16 -0
  132. package/dist/wirings/workflow/workflow-run-ownership.js +29 -0
  133. package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
  134. package/dist/wirings/workflow/workflow-suspend.js +33 -0
  135. package/dist/wirings/workflow/workflow.types.d.ts +7 -0
  136. package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
  137. package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
  138. package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
  139. package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
  140. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
  141. package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
  142. package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
  143. package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
  144. package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
  145. package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
  146. package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
  147. package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
  148. package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
  149. package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
  150. package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
  151. package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
  152. package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
  153. package/knowledge/decisions/internals/index.md +38 -26
  154. package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
  155. package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
  156. package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
  157. package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
  158. package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
  159. package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
  160. package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
  161. package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
  162. package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
  163. package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
  164. package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
  165. package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
  166. package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
  167. package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
  168. package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
  169. package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
  170. package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
  171. package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
  172. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
  173. package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
  174. package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
  175. package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
  176. package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
  177. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +34 -0
  178. package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
  179. package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
  180. package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +13 -3
  181. package/knowledge/decisions/security/index.md +7 -0
  182. package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
  183. package/knowledge/questions/index.md +2 -1
  184. package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
  185. package/package.json +14 -3
  186. package/scripts/generate-api-report.d.mts +1 -0
  187. package/scripts/generate-api-report.mjs +89 -0
  188. package/scripts/generate-api-report.mts +218 -0
  189. package/src/api-report.test.ts +69 -0
  190. package/src/column-form.ts +1 -4
  191. package/src/crypto-utils.test.ts +15 -2
  192. package/src/dev/hot-reload.ts +2 -7
  193. package/src/ecosystem.ts +40 -0
  194. package/src/errors/error-handler.ts +5 -3
  195. package/src/errors/error.test.ts +4 -1
  196. package/src/function/function-runner.test.ts +1 -1
  197. package/src/function/function-runner.ts +28 -39
  198. package/src/function/functions.types.ts +15 -9
  199. package/src/handle-error.ts +1 -1
  200. package/src/index.ts +3 -22
  201. package/src/middleware/auth-cookie.test.ts +50 -0
  202. package/src/middleware/auth-cookie.ts +38 -2
  203. package/src/middleware/cors.ts +2 -1
  204. package/src/middleware/index.ts +1 -1
  205. package/src/middleware/remote-auth.test.ts +43 -0
  206. package/src/middleware/remote-auth.ts +17 -3
  207. package/src/middleware-runner.ts +4 -2
  208. package/src/no-any-casts.test.ts +57 -0
  209. package/src/permissions.test.ts +3 -1
  210. package/src/permissions.ts +22 -24
  211. package/src/pikku-state.ts +17 -6
  212. package/src/public-surface.json +578 -0
  213. package/src/public-surface.json.README +25 -0
  214. package/src/public-surface.test.ts +105 -0
  215. package/src/removed-legacy-exports.test.ts +62 -0
  216. package/src/schema.test.ts +78 -0
  217. package/src/schema.ts +36 -1
  218. package/src/services/ai-run-state-service.ts +7 -1
  219. package/src/services/audit-service.ts +2 -2
  220. package/src/services/in-memory-ai-run-state-service.ts +3 -2
  221. package/src/services/in-memory-workflow-service.ts +25 -0
  222. package/src/services/index.ts +1 -4
  223. package/src/services/local-content-request-handler.test.ts +43 -5
  224. package/src/services/local-content-request-handler.ts +103 -74
  225. package/src/services/local-content.ts +15 -1
  226. package/src/services/local-email-service.ts +5 -1
  227. package/src/services/system-role-guard.test.ts +4 -1
  228. package/src/services/workflow-service.ts +9 -2
  229. package/src/side-effects-are-declared.test.ts +84 -0
  230. package/src/source-files-stay-composable.test.ts +41 -0
  231. package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
  232. package/src/testing/service-tests/ai-storage-service-tests.ts +286 -0
  233. package/src/testing/service-tests/channel-store-tests.ts +98 -0
  234. package/src/testing/service-tests/credential-service-tests.ts +143 -0
  235. package/src/testing/service-tests/deployment-service-tests.ts +33 -0
  236. package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
  237. package/src/testing/service-tests/secret-service-tests.ts +105 -0
  238. package/src/testing/service-tests/session-store-tests.ts +62 -0
  239. package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
  240. package/src/testing/service-tests/workflow-service-tests.ts +308 -0
  241. package/src/testing/service-tests.ts +31 -1111
  242. package/src/types/core.types.ts +15 -1
  243. package/src/types/state.types.ts +1 -1
  244. package/src/utils/node-host-resolver.ts +20 -0
  245. package/src/utils/safe-fetch.test.ts +143 -1
  246. package/src/utils/safe-fetch.ts +200 -25
  247. package/src/wirings/actor-flow/run-conversation.ts +1 -6
  248. package/src/wirings/ai-agent/agent-rpc.ts +120 -0
  249. package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
  250. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
  251. package/src/wirings/ai-agent/ai-agent-memory.ts +8 -1
  252. package/src/wirings/ai-agent/ai-agent-prepare.ts +10 -12
  253. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
  254. package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
  255. package/src/wirings/ai-agent/ai-agent-runner.ts +49 -120
  256. package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
  257. package/src/wirings/ai-agent/ai-agent-stream.ts +36 -63
  258. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
  259. package/src/wirings/ai-agent/ai-agent-turn.ts +121 -0
  260. package/src/wirings/ai-agent/ai-agent.types.ts +1 -1
  261. package/src/wirings/ai-agent/voice-input.ts +1 -6
  262. package/src/wirings/ai-agent/voice-output.ts +2 -12
  263. package/src/wirings/channel/channel-common.ts +5 -2
  264. package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
  265. package/src/wirings/channel/channel-handler.ts +7 -7
  266. package/src/wirings/channel/channel-rpc-service.ts +0 -14
  267. package/src/wirings/channel/channel-rpc.test.ts +25 -4
  268. package/src/wirings/channel/channel-rpc.types.ts +14 -0
  269. package/src/wirings/channel/channel-runner.ts +33 -20
  270. package/src/wirings/channel/channel.types.ts +2 -0
  271. package/src/wirings/channel/local/local-channel-runner.ts +1 -1
  272. package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
  273. package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
  274. package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
  275. package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
  276. package/src/wirings/cli/cli-runner.ts +7 -4
  277. package/src/wirings/cli/cli.types.ts +0 -39
  278. package/src/wirings/cli/command-parser.test.ts +19 -0
  279. package/src/wirings/cli/command-parser.ts +17 -1
  280. package/src/wirings/gateway/gateway-authorization.test.ts +131 -0
  281. package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
  282. package/src/wirings/gateway/gateway-runner.ts +62 -22
  283. package/src/wirings/http/http-routes.ts +4 -1
  284. package/src/wirings/http/http-runner.ts +11 -26
  285. package/src/wirings/http/http.types.ts +0 -15
  286. package/src/wirings/http/index.ts +1 -7
  287. package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
  288. package/src/wirings/http/web-request.ts +1 -1
  289. package/src/wirings/mcp/mcp-runner.ts +2 -16
  290. package/src/wirings/persona/persona-environments.test.ts +14 -3
  291. package/src/wirings/persona/persona.test.ts +13 -3
  292. package/src/wirings/persona/validate-personas.ts +5 -1
  293. package/src/wirings/queue/queue-runner.ts +1 -1
  294. package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
  295. package/src/wirings/rpc/rpc-runner.ts +58 -136
  296. package/src/wirings/rpc/rpc-types.ts +7 -0
  297. package/src/wirings/rpc/wire-addon.ts +3 -1
  298. package/src/wirings/secret/validate-secret-definitions.test.ts +69 -0
  299. package/src/wirings/secret/validate-secret-definitions.ts +2 -0
  300. package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
  301. package/src/wirings/trigger/trigger-runner.ts +7 -5
  302. package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
  303. package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
  304. package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
  305. package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
  306. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
  307. package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
  308. package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
  309. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +35 -2
  310. package/src/wirings/workflow/feature.ts +7 -4
  311. package/src/wirings/workflow/graph/graph-node.ts +1 -1
  312. package/src/wirings/workflow/graph/graph-runner.test.ts +4 -2
  313. package/src/wirings/workflow/graph/graph-runner.ts +13 -16
  314. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
  315. package/src/wirings/workflow/index.ts +12 -4
  316. package/src/wirings/workflow/pikku-scenario-service.ts +54 -25
  317. package/src/wirings/workflow/pikku-workflow-service.test.ts +2 -2
  318. package/src/wirings/workflow/pikku-workflow-service.ts +208 -630
  319. package/src/wirings/workflow/scenario-hooks.test.ts +51 -0
  320. package/src/wirings/workflow/workflow-approval.ts +185 -0
  321. package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
  322. package/src/wirings/workflow/workflow-constants.ts +47 -0
  323. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
  324. package/src/wirings/workflow/workflow-dispatch-relay.test.ts +128 -0
  325. package/src/wirings/workflow/workflow-errors.ts +128 -0
  326. package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
  327. package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
  328. package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
  329. package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
  330. package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
  331. package/src/wirings/workflow/workflow-recovery.ts +162 -0
  332. package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
  333. package/src/wirings/workflow/workflow-run-authority.test.ts +215 -0
  334. package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
  335. package/src/wirings/workflow/workflow-run-ownership.ts +36 -0
  336. package/src/wirings/workflow/workflow-suspend.ts +61 -0
  337. package/src/wirings/workflow/workflow.types.ts +7 -0
  338. package/src/wirings-stay-decoupled.test.ts +123 -0
  339. package/tsconfig.json +1 -1
  340. package/tsconfig.tsbuildinfo +1 -1
  341. package/src/internal.ts +0 -10
@@ -0,0 +1,105 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import type { ServiceTestConfig } from '../service-tests.js'
5
+
6
+ /** Conformance suite for `secretService`. Runs only when a backend supplies one. */
7
+ export const defineSecretServiceTests = (
8
+ name: string,
9
+ secretService: NonNullable<ServiceTestConfig['services']['secretService']>
10
+ ): void => {
11
+ const factory = secretService
12
+ const kek = 'test-key-encryption-key-32chars!'
13
+
14
+ describe(`SecretService [${name}]`, () => {
15
+ test('setSecret and getSecret', async () => {
16
+ const service = await factory({ key: kek })
17
+ await service.setSecret('api-key', {
18
+ token: 'sk-123',
19
+ endpoint: 'https://api.example.com',
20
+ })
21
+ const result = await service.getSecret<{
22
+ token: string
23
+ endpoint: string
24
+ }>('api-key')
25
+ assert.deepEqual(result.reveal(), {
26
+ token: 'sk-123',
27
+ endpoint: 'https://api.example.com',
28
+ })
29
+ })
30
+
31
+ test('getSecret wraps the raw string rather than returning it', async () => {
32
+ const service = await factory({ key: kek })
33
+ await service.setSecret('string-secret', 'plain-value')
34
+ const result = await service.getSecret('string-secret')
35
+ assert.strictEqual(result.reveal(), 'plain-value')
36
+ })
37
+
38
+ test('hasSecret returns true/false', async () => {
39
+ const service = await factory({ key: kek })
40
+ assert.strictEqual(await service.hasSecret('api-key'), true)
41
+ assert.strictEqual(await service.hasSecret('nonexistent'), false)
42
+ })
43
+
44
+ test('getSecret throws for missing key', async () => {
45
+ const service = await factory({ key: kek })
46
+ await assert.rejects(() => service.getSecret('nonexistent'), {
47
+ message: 'Requested secret not found',
48
+ })
49
+ })
50
+
51
+ test('setSecret upserts existing key', async () => {
52
+ const service = await factory({ key: kek })
53
+ await service.setSecret('upsert-key', { v: 1 })
54
+ await service.setSecret('upsert-key', { v: 2 })
55
+ const result = await service.getSecret<{ v: number }>('upsert-key')
56
+ assert.deepEqual(result.reveal(), { v: 2 })
57
+ })
58
+
59
+ test('deleteSecret removes the key', async () => {
60
+ const service = await factory({ key: kek })
61
+ await service.setSecret('to-delete', 'bye')
62
+ assert.strictEqual(await service.hasSecret('to-delete'), true)
63
+ await service.deleteSecret('to-delete')
64
+ assert.strictEqual(await service.hasSecret('to-delete'), false)
65
+ })
66
+
67
+ test('rotateKEK re-wraps all secrets', async () => {
68
+ const newKEK = 'new-key-encryption-key-rotated!'
69
+ const oldService = await factory({ key: kek })
70
+ await oldService.setSecret('rotate-test', { important: 'data' })
71
+
72
+ const rotatedService = await factory({
73
+ key: newKEK,
74
+ keyVersion: 2,
75
+ previousKey: kek,
76
+ })
77
+
78
+ const before = await rotatedService.getSecret<{
79
+ important: string
80
+ }>('rotate-test')
81
+ assert.deepEqual(before.reveal(), { important: 'data' })
82
+
83
+ assert.ok(rotatedService.rotateKEK)
84
+ const count = await rotatedService.rotateKEK!()
85
+ assert.ok(count > 0)
86
+
87
+ const newOnlyService = await factory({
88
+ key: newKEK,
89
+ keyVersion: 2,
90
+ })
91
+ const after = await newOnlyService.getSecret<{
92
+ important: string
93
+ }>('rotate-test')
94
+ assert.deepEqual(after.reveal(), { important: 'data' })
95
+ })
96
+
97
+ test('rotateKEK throws without previousKey', async () => {
98
+ const service = await factory({ key: kek })
99
+ assert.ok(service.rotateKEK)
100
+ await assert.rejects(() => service.rotateKEK!(), {
101
+ message: 'No previousKey configured — nothing to rotate from',
102
+ })
103
+ })
104
+ })
105
+ }
@@ -0,0 +1,62 @@
1
+ import { describe, test, before } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import type { SessionStore } from '../../services/session-store.js'
5
+ import type { CoreUserSession } from '../../types/core.types.js'
6
+ import type { ServiceTestConfig } from '../service-tests.js'
7
+
8
+ type AppSession = CoreUserSession & Record<string, unknown>
9
+
10
+ /** Conformance suite for `sessionStore`. Runs only when a backend supplies one. */
11
+ export const defineSessionStoreTests = (
12
+ name: string,
13
+ sessionStore: NonNullable<ServiceTestConfig['services']['sessionStore']>
14
+ ): void => {
15
+ const factory = sessionStore
16
+ describe(`SessionStore [${name}]`, () => {
17
+ let store: SessionStore
18
+
19
+ before(async () => {
20
+ store = await factory()
21
+ })
22
+
23
+ test('get returns undefined for unknown user', async () => {
24
+ const result = await store.get('unknown-user')
25
+ assert.equal(result, undefined)
26
+ })
27
+
28
+ test('set and get round-trip', async () => {
29
+ const session: AppSession = {
30
+ userId: 'user-1',
31
+ organizationId: 'org-1',
32
+ }
33
+ await store.set('user-1', session)
34
+
35
+ const result = await store.get('user-1')
36
+ assert.deepEqual(result, session)
37
+ })
38
+
39
+ test('set overwrites previous session', async () => {
40
+ const asAdmin: AppSession = { userId: 'user-2', role: 'admin' }
41
+ const asMember: AppSession = { userId: 'user-2', role: 'member' }
42
+ await store.set('user-2', asAdmin)
43
+ await store.set('user-2', asMember)
44
+
45
+ const result = await store.get('user-2')
46
+ assert.deepEqual(result, { userId: 'user-2', role: 'member' })
47
+ })
48
+
49
+ test('clear removes session', async () => {
50
+ await store.set('user-3', { userId: 'user-3' })
51
+ assert.ok(await store.get('user-3'))
52
+
53
+ await store.clear('user-3')
54
+ const result = await store.get('user-3')
55
+ assert.equal(result, undefined)
56
+ })
57
+
58
+ test('clear is no-op for unknown user', async () => {
59
+ await store.clear('nonexistent')
60
+ })
61
+ })
62
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, test, before } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import type { WorkflowRunService } from '../../wirings/workflow/workflow.types.js'
5
+ import type { ServiceTestConfig } from '../service-tests.js'
6
+
7
+ /** Conformance suite for `workflowRunService`. Runs only when a backend supplies one. */
8
+ export const defineWorkflowRunServiceTests = (
9
+ name: string,
10
+ workflowRunService: NonNullable<
11
+ ServiceTestConfig['services']['workflowRunService']
12
+ >
13
+ ): void => {
14
+ const factory = workflowRunService
15
+ describe(`WorkflowRunService [${name}]`, () => {
16
+ let runService: WorkflowRunService
17
+
18
+ before(async () => {
19
+ runService = await factory()
20
+ })
21
+
22
+ test('listRuns returns runs', async () => {
23
+ const runs = await runService.listRuns()
24
+ assert.ok(Array.isArray(runs))
25
+ assert.ok(runs.length > 0)
26
+ })
27
+
28
+ test('listRuns with filter', async () => {
29
+ const runs = await runService.listRuns({
30
+ workflowName: 'test-workflow',
31
+ })
32
+ assert.ok(runs.every((r) => r.workflow === 'test-workflow'))
33
+ })
34
+
35
+ test('getDistinctWorkflowNames', async () => {
36
+ const names = await runService.getDistinctWorkflowNames()
37
+ assert.ok(Array.isArray(names))
38
+ assert.ok(names.includes('test-workflow'))
39
+ })
40
+
41
+ test('deleteRun', async () => {
42
+ const runs = await runService.listRuns({
43
+ workflowName: 'status-workflow',
44
+ })
45
+ assert.ok(runs.length > 0)
46
+
47
+ const deleted = await runService.deleteRun(runs[0]!.id)
48
+ assert.equal(deleted, true)
49
+
50
+ const afterDelete = await runService.getRun(runs[0]!.id)
51
+ assert.equal(afterDelete, null)
52
+ })
53
+
54
+ test('deleteRun returns false for missing', async () => {
55
+ const deleted = await runService.deleteRun('non-existent-id')
56
+ assert.equal(deleted, false)
57
+ })
58
+ })
59
+ }
@@ -0,0 +1,308 @@
1
+ import { describe, test, before } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import type { PikkuWorkflowService } from '../../wirings/workflow/pikku-workflow-service.js'
5
+ import type { ServiceTestConfig } from '../service-tests.js'
6
+
7
+ /** Conformance suite for `workflowService`. Runs only when a backend supplies one. */
8
+ export const defineWorkflowServiceTests = (
9
+ name: string,
10
+ workflowService: NonNullable<ServiceTestConfig['services']['workflowService']>
11
+ ): void => {
12
+ const factory = workflowService
13
+ const wire = { type: 'test' }
14
+
15
+ describe(`WorkflowService [${name}]`, () => {
16
+ let service: PikkuWorkflowService
17
+
18
+ before(async () => {
19
+ service = await factory()
20
+ })
21
+
22
+ test('createRun and getRun', async () => {
23
+ const runId = await service.createRun(
24
+ 'test-workflow',
25
+ { key: 'value' },
26
+ false,
27
+ 'hash-1',
28
+ wire
29
+ )
30
+
31
+ assert.ok(runId)
32
+ const run = await service.getRun(runId)
33
+ assert.ok(run)
34
+ assert.equal(run.workflow, 'test-workflow')
35
+ assert.equal(run.status, 'running')
36
+ assert.deepEqual(run.input, { key: 'value' })
37
+ assert.equal(run.graphHash, 'hash-1')
38
+ })
39
+
40
+ test('updateRunStatus', async () => {
41
+ const runId = await service.createRun(
42
+ 'status-workflow',
43
+ {},
44
+ false,
45
+ 'hash-2',
46
+ wire
47
+ )
48
+ await service.updateRunStatus(runId, 'completed', { result: 'done' })
49
+
50
+ const run = await service.getRun(runId)
51
+ assert.ok(run)
52
+ assert.equal(run.status, 'completed')
53
+ assert.deepEqual(run.output, { result: 'done' })
54
+ })
55
+
56
+ test('insertStepState and getStepState', async () => {
57
+ const runId = await service.createRun(
58
+ 'step-workflow',
59
+ {},
60
+ false,
61
+ 'hash-3',
62
+ wire
63
+ )
64
+
65
+ const step = await service.insertStepState(
66
+ runId,
67
+ 'step-1',
68
+ 'myRpc',
69
+ { data: 'test' },
70
+ { retries: 3, retryDelay: '1000' }
71
+ )
72
+
73
+ assert.ok(step.stepId)
74
+ assert.equal(step.status, 'pending')
75
+ assert.equal(step.attemptCount, 1)
76
+ assert.equal(step.retries, 3)
77
+ assert.equal(step.retryDelay, '1000')
78
+
79
+ const fetched = await service.getStepState(runId, 'step-1')
80
+ assert.equal(fetched.stepId, step.stepId)
81
+ assert.equal(fetched.status, 'pending')
82
+ assert.equal(fetched.attemptCount, 1)
83
+ })
84
+
85
+ test('setStepRunning, setStepResult', async () => {
86
+ const runId = await service.createRun(
87
+ 'result-workflow',
88
+ {},
89
+ false,
90
+ 'hash-4',
91
+ wire
92
+ )
93
+ const step = await service.insertStepState(
94
+ runId,
95
+ 'result-step',
96
+ 'myRpc',
97
+ {}
98
+ )
99
+
100
+ await service.setStepRunning(step.stepId)
101
+ let fetched = await service.getStepState(runId, 'result-step')
102
+ assert.equal(fetched.status, 'running')
103
+
104
+ await service.setStepResult(step.stepId, { answer: 42 })
105
+ fetched = await service.getStepState(runId, 'result-step')
106
+ assert.equal(fetched.status, 'succeeded')
107
+ assert.deepEqual(fetched.result, { answer: 42 })
108
+ })
109
+
110
+ test('setStepError', async () => {
111
+ const runId = await service.createRun(
112
+ 'error-workflow',
113
+ {},
114
+ false,
115
+ 'hash-5',
116
+ wire
117
+ )
118
+ const step = await service.insertStepState(
119
+ runId,
120
+ 'error-step',
121
+ 'myRpc',
122
+ {}
123
+ )
124
+
125
+ await service.setStepRunning(step.stepId)
126
+ await service.setStepError(step.stepId, new Error('boom'))
127
+
128
+ const fetched = await service.getStepState(runId, 'error-step')
129
+ assert.equal(fetched.status, 'failed')
130
+ assert.equal(fetched.error?.message, 'boom')
131
+ })
132
+
133
+ test('createRetryAttempt', async () => {
134
+ const runId = await service.createRun(
135
+ 'retry-workflow',
136
+ {},
137
+ false,
138
+ 'hash-6',
139
+ wire
140
+ )
141
+ const step = await service.insertStepState(
142
+ runId,
143
+ 'retry-step',
144
+ 'myRpc',
145
+ {},
146
+ { retries: 2 }
147
+ )
148
+
149
+ await service.setStepError(step.stepId, new Error('fail'))
150
+ const retried = await service.createRetryAttempt(step.stepId, 'pending')
151
+ assert.equal(retried.status, 'pending')
152
+ assert.equal(retried.attemptCount, 2)
153
+ assert.equal(retried.error, undefined)
154
+ assert.equal(retried.result, undefined)
155
+ })
156
+
157
+ test('getRunStatus reports per-step duration and attempts', async () => {
158
+ const runId = await service.createRun(
159
+ 'status-timing-workflow',
160
+ {},
161
+ false,
162
+ 'hash-timing',
163
+ wire
164
+ )
165
+ const step = await service.insertStepState(
166
+ runId,
167
+ 'timed-step',
168
+ 'myRpc',
169
+ {}
170
+ )
171
+ await service.setStepRunning(step.stepId)
172
+ await service.setStepResult(step.stepId, { ok: true })
173
+
174
+ const status = await service.getRunStatus(runId)
175
+ assert.ok(status)
176
+ const timed = status.steps.find((s) => s.name === 'timed-step')
177
+ assert.ok(timed, 'step appears in run status')
178
+ // running→succeeded must stamp runningAt/succeededAt so a duration is
179
+ // computable. Regression guard: the kysely store previously updated
180
+ // only the status on transitions, leaving the timestamps null and
181
+ // duration permanently undefined.
182
+ assert.ok(
183
+ timed.duration !== undefined,
184
+ 'duration is computed from stamped transition timestamps'
185
+ )
186
+ assert.ok(timed.duration >= 0, 'duration is non-negative')
187
+ assert.equal(timed.attempts, 1, 'single attempt')
188
+ })
189
+
190
+ test('getRunStatus surfaces retried attempt count', async () => {
191
+ const runId = await service.createRun(
192
+ 'status-retry-workflow',
193
+ {},
194
+ false,
195
+ 'hash-retry-status',
196
+ wire
197
+ )
198
+ const step = await service.insertStepState(
199
+ runId,
200
+ 'flaky-step',
201
+ 'myRpc',
202
+ {},
203
+ { retries: 2 }
204
+ )
205
+ await service.setStepRunning(step.stepId)
206
+ await service.setStepError(step.stepId, new Error('first try'))
207
+ // Guarantee the retry's history row sorts after the failed attempt so
208
+ // getRunStatus picks the latest attempt (it tie-breaks on timestamps).
209
+ await new Promise((resolve) => setTimeout(resolve, 5))
210
+ const retry = await service.createRetryAttempt(step.stepId, 'pending')
211
+ await service.setStepRunning(retry.stepId)
212
+ await service.setStepResult(retry.stepId, { ok: true })
213
+
214
+ const status = await service.getRunStatus(runId)
215
+ assert.ok(status)
216
+ const flaky = status.steps.find((s) => s.name === 'flaky-step')
217
+ assert.ok(flaky, 'retried step collapses to a single status entry')
218
+ assert.equal(flaky.status, 'succeeded', 'latest attempt wins')
219
+ assert.equal(flaky.attempts, 2, 'attempt count surfaces in status')
220
+ })
221
+
222
+ test('getNodeResults', async () => {
223
+ const runId = await service.createRun(
224
+ 'results-workflow',
225
+ {},
226
+ false,
227
+ 'hash-8',
228
+ wire
229
+ )
230
+ const step = await service.insertStepState(runId, 'node-a', 'rpc', {})
231
+ await service.setStepResult(step.stepId, { out: 'hello' })
232
+
233
+ const results = await service.getNodeResults(runId, ['node-a'])
234
+ assert.deepEqual(results['node-a'], { out: 'hello' })
235
+ })
236
+
237
+ test('setBranchTaken and getCompletedGraphState', async () => {
238
+ const runId = await service.createRun(
239
+ 'branch-workflow',
240
+ {},
241
+ false,
242
+ 'hash-9',
243
+ wire
244
+ )
245
+ const step = await service.insertStepState(
246
+ runId,
247
+ 'branch-node',
248
+ 'rpc',
249
+ {}
250
+ )
251
+ await service.setStepResult(step.stepId, {})
252
+ await service.setBranchTaken(step.stepId, 'left')
253
+
254
+ const state = await service.getCompletedGraphState(runId)
255
+ assert.ok(state.completedNodeIds.includes('branch-node'))
256
+ assert.equal(state.branchKeys['branch-node'], 'left')
257
+ })
258
+
259
+ test('updateRunState and getRunState', async () => {
260
+ const runId = await service.createRun(
261
+ 'state-workflow',
262
+ {},
263
+ false,
264
+ 'hash-10',
265
+ wire
266
+ )
267
+
268
+ await service.updateRunState(runId, 'counter', 5)
269
+ await service.updateRunState(runId, 'name', 'test')
270
+
271
+ const state = await service.getRunState(runId)
272
+ assert.equal(state.counter, 5)
273
+ assert.equal(state.name, 'test')
274
+ })
275
+
276
+ test('upsertWorkflowVersion and getWorkflowVersion', async () => {
277
+ await service.upsertWorkflowVersion(
278
+ 'my-workflow',
279
+ 'v1-hash',
280
+ { nodes: ['a', 'b'] },
281
+ 'dsl'
282
+ )
283
+
284
+ const version = await service.getWorkflowVersion('my-workflow', 'v1-hash')
285
+ assert.ok(version)
286
+ assert.deepEqual(version.graph, { nodes: ['a', 'b'] })
287
+ assert.equal(version.source, 'dsl')
288
+ })
289
+
290
+ test('upsertWorkflowVersion duplicate is no-op', async () => {
291
+ await service.upsertWorkflowVersion(
292
+ 'my-workflow',
293
+ 'v1-hash',
294
+ { nodes: ['changed'] },
295
+ 'dsl'
296
+ )
297
+
298
+ const version = await service.getWorkflowVersion('my-workflow', 'v1-hash')
299
+ assert.ok(version)
300
+ assert.deepEqual(version.graph, { nodes: ['a', 'b'] })
301
+ })
302
+
303
+ test('getWorkflowVersion returns null for missing', async () => {
304
+ const version = await service.getWorkflowVersion('missing', 'missing')
305
+ assert.equal(version, null)
306
+ })
307
+ })
308
+ }