@pikku/core 0.12.72 → 0.12.77

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 (848) hide show
  1. package/CHANGELOG.md +1171 -0
  2. package/dist/column-form.d.ts +32 -0
  3. package/dist/column-form.js +42 -0
  4. package/dist/crypto-utils.d.ts +43 -7
  5. package/dist/crypto-utils.js +163 -42
  6. package/dist/data-classification.d.ts +44 -0
  7. package/dist/dev/hot-reload.js +11 -30
  8. package/dist/dev/module-runner.d.ts +3 -7
  9. package/dist/dev/module-runner.js +4 -10
  10. package/dist/dev/reload-meta.d.ts +8 -20
  11. package/dist/dev/reload-meta.js +9 -29
  12. package/dist/errors/error-handler.d.ts +5 -30
  13. package/dist/errors/error-handler.js +16 -32
  14. package/dist/errors/errors.d.ts +32 -151
  15. package/dist/errors/errors.js +55 -157
  16. package/dist/function/abort-scope.d.ts +47 -0
  17. package/dist/function/abort-scope.js +63 -0
  18. package/dist/function/function-runner.js +37 -32
  19. package/dist/function/functions.types.d.ts +57 -136
  20. package/dist/function/functions.types.js +0 -58
  21. package/dist/function/index.d.ts +1 -1
  22. package/dist/function/list.types.d.ts +12 -62
  23. package/dist/function/list.types.js +4 -25
  24. package/dist/handle-error.d.ts +0 -13
  25. package/dist/handle-error.js +0 -18
  26. package/dist/index.d.ts +11 -5
  27. package/dist/index.js +7 -2
  28. package/dist/middleware/auth-apikey.d.ts +3 -18
  29. package/dist/middleware/auth-apikey.js +0 -17
  30. package/dist/middleware/auth-bearer.d.ts +6 -41
  31. package/dist/middleware/auth-bearer.js +5 -41
  32. package/dist/middleware/auth-cookie.d.ts +5 -27
  33. package/dist/middleware/auth-cookie.js +2 -26
  34. package/dist/middleware/cors.d.ts +7 -34
  35. package/dist/middleware/cors.js +7 -34
  36. package/dist/middleware/remote-auth.d.ts +3 -1
  37. package/dist/middleware/remote-auth.js +4 -3
  38. package/dist/middleware/telemetry.d.ts +8 -33
  39. package/dist/middleware/telemetry.js +2 -31
  40. package/dist/middleware-runner.d.ts +4 -55
  41. package/dist/middleware-runner.js +5 -74
  42. package/dist/permissions.d.ts +3 -44
  43. package/dist/permissions.js +19 -71
  44. package/dist/pikku-request.d.ts +0 -6
  45. package/dist/pikku-request.js +0 -6
  46. package/dist/pikku-state.d.ts +0 -26
  47. package/dist/pikku-state.js +2 -30
  48. package/dist/remote.d.ts +3 -5
  49. package/dist/remote.js +9 -8
  50. package/dist/schema.d.ts +5 -39
  51. package/dist/schema.js +5 -39
  52. package/dist/scopes.d.ts +4 -23
  53. package/dist/scopes.js +7 -48
  54. package/dist/secret-value.d.ts +56 -0
  55. package/dist/secret-value.js +46 -0
  56. package/dist/services/ai-agent-runner-service.d.ts +20 -0
  57. package/dist/services/ai-embedding-service.d.ts +2 -25
  58. package/dist/services/audit-service.d.ts +74 -4
  59. package/dist/services/audit-service.js +8 -7
  60. package/dist/services/content-service.d.ts +1 -46
  61. package/dist/services/credential-service.d.ts +3 -40
  62. package/dist/services/credential-wire-service.d.ts +5 -0
  63. package/dist/services/credential-wire-service.js +9 -1
  64. package/dist/services/deployment-service.d.ts +3 -9
  65. package/dist/services/email-service.d.ts +2 -1
  66. package/dist/services/gateway-service.d.ts +0 -15
  67. package/dist/services/http-personas.d.ts +80 -0
  68. package/dist/services/http-personas.js +233 -0
  69. package/dist/services/in-memory-queue-service.d.ts +0 -14
  70. package/dist/services/in-memory-queue-service.js +1 -15
  71. package/dist/services/in-memory-trigger-service.d.ts +0 -18
  72. package/dist/services/in-memory-trigger-service.js +1 -18
  73. package/dist/services/in-memory-workflow-service.d.ts +0 -16
  74. package/dist/services/in-memory-workflow-service.js +4 -33
  75. package/dist/services/index.d.ts +8 -9
  76. package/dist/services/index.js +3 -6
  77. package/dist/services/istanbul-coverage-service.d.ts +1 -5
  78. package/dist/services/istanbul-coverage-service.js +2 -8
  79. package/dist/services/jwt-service.d.ts +1 -16
  80. package/dist/services/local-content-request-handler.d.ts +29 -0
  81. package/dist/services/local-content-request-handler.js +176 -0
  82. package/dist/services/local-content.d.ts +13 -2
  83. package/dist/services/local-content.js +40 -13
  84. package/dist/services/local-gateway-service.d.ts +0 -16
  85. package/dist/services/local-gateway-service.js +2 -17
  86. package/dist/services/local-secrets.d.ts +4 -7
  87. package/dist/services/local-secrets.js +7 -7
  88. package/dist/services/logger-console.d.ts +3 -7
  89. package/dist/services/logger-console.js +3 -7
  90. package/dist/services/logger.d.ts +22 -40
  91. package/dist/services/meta-service.d.ts +23 -26
  92. package/dist/services/meta-service.js +22 -36
  93. package/dist/services/personas-service.d.ts +134 -0
  94. package/dist/services/personas-service.js +40 -0
  95. package/dist/services/pikku-user-id.js +0 -4
  96. package/dist/services/queue-webhook-service.d.ts +2 -36
  97. package/dist/services/queue-webhook-service.js +10 -42
  98. package/dist/services/scheduler-service.d.ts +1 -50
  99. package/dist/services/scheduler-service.js +0 -10
  100. package/dist/services/schema-service.d.ts +1 -24
  101. package/dist/services/scope-service.d.ts +49 -34
  102. package/dist/services/scoped-secret-service.d.ts +4 -7
  103. package/dist/services/scoped-secret-service.js +0 -4
  104. package/dist/services/secret-host-binding.d.ts +8 -0
  105. package/dist/services/secret-host-binding.js +36 -0
  106. package/dist/services/secret-service.d.ts +12 -35
  107. package/dist/services/secretless.d.ts +6 -0
  108. package/dist/services/secretless.js +21 -0
  109. package/dist/services/stub-tracker.d.ts +7 -18
  110. package/dist/services/stub-tracker.js +8 -18
  111. package/dist/services/system-role-guard.d.ts +33 -0
  112. package/dist/services/system-role-guard.js +38 -0
  113. package/dist/services/trigger-service.d.ts +0 -12
  114. package/dist/services/typed-secret-service.d.ts +5 -11
  115. package/dist/services/typed-secret-service.js +1 -7
  116. package/dist/services/v8-coverage-service.d.ts +2 -3
  117. package/dist/services/v8-coverage-service.js +1 -2
  118. package/dist/services/variables-service.d.ts +1 -8
  119. package/dist/services/webhook-service.d.ts +21 -64
  120. package/dist/services/webhook-service.js +6 -20
  121. package/dist/services/workflow-service.d.ts +3 -15
  122. package/dist/testing/service-tests.js +6 -23
  123. package/dist/time-utils.d.ts +0 -16
  124. package/dist/time-utils.js +1 -19
  125. package/dist/types/core.types.d.ts +120 -219
  126. package/dist/types/core.types.js +0 -42
  127. package/dist/types/state.types.d.ts +4 -9
  128. package/dist/utils/hmac.d.ts +4 -10
  129. package/dist/utils/hmac.js +4 -10
  130. package/dist/utils/safe-fetch.d.ts +7 -35
  131. package/dist/utils/safe-fetch.js +13 -53
  132. package/dist/utils.d.ts +1 -6
  133. package/dist/utils.js +6 -15
  134. package/dist/wirings/actor-flow/actor-flow.types.d.ts +1 -34
  135. package/dist/wirings/actor-flow/index.d.ts +0 -9
  136. package/dist/wirings/actor-flow/run-conversation.d.ts +5 -5
  137. package/dist/wirings/actor-flow/run-conversation.js +14 -7
  138. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +0 -5
  139. package/dist/wirings/ai-agent/ai-agent-agui.js +46 -12
  140. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +7 -0
  141. package/dist/wirings/ai-agent/ai-agent-helpers.js +7 -0
  142. package/dist/wirings/ai-agent/ai-agent-interrupt.d.ts +153 -0
  143. package/dist/wirings/ai-agent/ai-agent-interrupt.js +256 -0
  144. package/dist/wirings/ai-agent/ai-agent-memory.js +0 -2
  145. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +0 -9
  146. package/dist/wirings/ai-agent/ai-agent-model-config.js +1 -9
  147. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -99
  148. package/dist/wirings/ai-agent/ai-agent-prepare.js +65 -132
  149. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +2 -1
  150. package/dist/wirings/ai-agent/ai-agent-registry.js +5 -1
  151. package/dist/wirings/ai-agent/ai-agent-runner.js +63 -22
  152. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +25 -2
  153. package/dist/wirings/ai-agent/ai-agent-stream.js +180 -64
  154. package/dist/wirings/ai-agent/ai-agent.types.d.ts +124 -4
  155. package/dist/wirings/ai-agent/index.d.ts +6 -4
  156. package/dist/wirings/ai-agent/index.js +5 -4
  157. package/dist/wirings/ai-agent/voice-input.d.ts +59 -1
  158. package/dist/wirings/ai-agent/voice-input.js +90 -12
  159. package/dist/wirings/ai-agent/voice-output.d.ts +69 -1
  160. package/dist/wirings/ai-agent/voice-output.js +162 -50
  161. package/dist/wirings/channel/channel-common.d.ts +7 -20
  162. package/dist/wirings/channel/channel-common.js +7 -21
  163. package/dist/wirings/channel/channel-handler.js +25 -6
  164. package/dist/wirings/channel/channel-host-rpc.d.ts +25 -0
  165. package/dist/wirings/channel/channel-host-rpc.js +38 -0
  166. package/dist/wirings/channel/channel-middleware-runner.d.ts +0 -12
  167. package/dist/wirings/channel/channel-middleware-runner.js +0 -12
  168. package/dist/wirings/channel/channel-rpc-registry.d.ts +31 -0
  169. package/dist/wirings/channel/channel-rpc-registry.js +89 -0
  170. package/dist/wirings/channel/channel-rpc-responder.d.ts +15 -0
  171. package/dist/wirings/channel/channel-rpc-responder.js +71 -0
  172. package/dist/wirings/channel/channel-rpc-service.d.ts +40 -0
  173. package/dist/wirings/channel/channel-rpc-service.js +106 -0
  174. package/dist/wirings/channel/channel-rpc-validators.d.ts +14 -0
  175. package/dist/wirings/channel/channel-rpc-validators.js +30 -0
  176. package/dist/wirings/channel/channel-rpc.d.ts +5 -0
  177. package/dist/wirings/channel/channel-rpc.js +5 -0
  178. package/dist/wirings/channel/channel-rpc.types.d.ts +90 -0
  179. package/dist/wirings/channel/channel-rpc.types.js +50 -0
  180. package/dist/wirings/channel/channel-runner.d.ts +0 -4
  181. package/dist/wirings/channel/channel-runner.js +0 -14
  182. package/dist/wirings/channel/channel-store.d.ts +0 -10
  183. package/dist/wirings/channel/channel.types.d.ts +12 -1
  184. package/dist/wirings/channel/define-channel-routes.d.ts +0 -20
  185. package/dist/wirings/channel/define-channel-routes.js +0 -20
  186. package/dist/wirings/channel/eventhub-service.d.ts +0 -18
  187. package/dist/wirings/channel/index.d.ts +4 -1
  188. package/dist/wirings/channel/index.js +2 -0
  189. package/dist/wirings/channel/local/local-channel-runner.js +3 -1
  190. package/dist/wirings/channel/local/local-eventhub-service.d.ts +0 -33
  191. package/dist/wirings/channel/local/local-eventhub-service.js +2 -36
  192. package/dist/wirings/channel/log-channels.d.ts +0 -4
  193. package/dist/wirings/channel/log-channels.js +0 -4
  194. package/dist/wirings/channel/pikku-abstract-channel-handler.js +6 -0
  195. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -5
  196. package/dist/wirings/cli/channel/cli-approval.d.ts +41 -0
  197. package/dist/wirings/cli/channel/cli-approval.js +81 -0
  198. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +0 -4
  199. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -25
  200. package/dist/wirings/cli/channel/cli-raw-channel-runner.d.ts +47 -9
  201. package/dist/wirings/cli/channel/cli-raw-channel-runner.js +24 -16
  202. package/dist/wirings/cli/channel/cli-raw-client-runner.d.ts +38 -0
  203. package/dist/wirings/cli/channel/cli-raw-client-runner.js +129 -0
  204. package/dist/wirings/cli/channel/index.d.ts +5 -0
  205. package/dist/wirings/cli/channel/index.js +2 -0
  206. package/dist/wirings/cli/cli-runner.d.ts +20 -20
  207. package/dist/wirings/cli/cli-runner.js +28 -89
  208. package/dist/wirings/cli/cli.types.d.ts +14 -3
  209. package/dist/wirings/cli/command-parser.d.ts +1 -10
  210. package/dist/wirings/cli/command-parser.js +10 -87
  211. package/dist/wirings/cli/define-cli-commands.d.ts +1 -17
  212. package/dist/wirings/cli/define-cli-commands.js +1 -17
  213. package/dist/wirings/credential/credential.types.d.ts +0 -12
  214. package/dist/wirings/credential/define-credential.d.ts +48 -0
  215. package/dist/wirings/credential/define-credential.js +47 -0
  216. package/dist/wirings/credential/index.d.ts +1 -1
  217. package/dist/wirings/credential/index.js +1 -1
  218. package/dist/wirings/credential/validate-credential-definitions.d.ts +2 -4
  219. package/dist/wirings/gateway/gateway-runner.d.ts +1 -20
  220. package/dist/wirings/gateway/gateway-runner.js +8 -105
  221. package/dist/wirings/gateway/gateway.types.d.ts +7 -80
  222. package/dist/wirings/http/http-routes.d.ts +0 -63
  223. package/dist/wirings/http/http-routes.js +0 -63
  224. package/dist/wirings/http/http-runner.d.ts +0 -99
  225. package/dist/wirings/http/http-runner.js +9 -165
  226. package/dist/wirings/http/http.types.d.ts +14 -55
  227. package/dist/wirings/http/log-http-routes.d.ts +0 -4
  228. package/dist/wirings/http/log-http-routes.js +0 -4
  229. package/dist/wirings/http/pikku-fetch-http-request.d.ts +0 -40
  230. package/dist/wirings/http/pikku-fetch-http-request.js +0 -58
  231. package/dist/wirings/http/pikku-fetch-http-response.js +0 -3
  232. package/dist/wirings/http/routers/path-to-regex.js +2 -13
  233. package/dist/wirings/http/web-request.d.ts +0 -8
  234. package/dist/wirings/http/web-request.js +25 -17
  235. package/dist/wirings/mcp/mcp-runner.d.ts +1 -4
  236. package/dist/wirings/mcp/mcp-runner.js +1 -14
  237. package/dist/wirings/mcp/mcp.types.d.ts +2 -35
  238. package/dist/wirings/oauth2/oauth2.types.d.ts +0 -28
  239. package/dist/wirings/oauth2/oauth2.types.js +0 -3
  240. package/dist/wirings/persona/define-personas.d.ts +32 -0
  241. package/dist/wirings/persona/define-personas.js +31 -0
  242. package/dist/wirings/persona/index.d.ts +21 -0
  243. package/dist/wirings/persona/index.js +17 -0
  244. package/dist/wirings/persona/persona-email.d.ts +37 -0
  245. package/dist/wirings/persona/persona-email.js +69 -0
  246. package/dist/wirings/persona/persona-environments.d.ts +45 -0
  247. package/dist/wirings/persona/persona-environments.js +81 -0
  248. package/dist/wirings/persona/persona-mailbox.d.ts +101 -0
  249. package/dist/wirings/persona/persona-mailbox.js +53 -0
  250. package/dist/wirings/persona/persona.types.d.ts +136 -0
  251. package/dist/wirings/persona/persona.types.js +1 -0
  252. package/dist/wirings/persona/validate-personas.d.ts +53 -0
  253. package/dist/wirings/persona/validate-personas.js +94 -0
  254. package/dist/wirings/queue/index.d.ts +3 -0
  255. package/dist/wirings/queue/index.js +2 -3
  256. package/dist/wirings/queue/queue-identity.d.ts +28 -0
  257. package/dist/wirings/queue/queue-identity.js +103 -0
  258. package/dist/wirings/queue/queue-runner.d.ts +0 -19
  259. package/dist/wirings/queue/queue-runner.js +9 -30
  260. package/dist/wirings/queue/queue.types.d.ts +20 -90
  261. package/dist/wirings/queue/register-queue-helper.d.ts +0 -12
  262. package/dist/wirings/queue/register-queue-helper.js +0 -11
  263. package/dist/wirings/queue/signed-queue-service.d.ts +17 -0
  264. package/dist/wirings/queue/signed-queue-service.js +42 -0
  265. package/dist/wirings/queue/validate-worker-config.d.ts +2 -23
  266. package/dist/wirings/queue/validate-worker-config.js +0 -14
  267. package/dist/wirings/role/define-system-role.d.ts +32 -0
  268. package/dist/wirings/role/define-system-role.js +31 -0
  269. package/dist/wirings/role/index.d.ts +3 -0
  270. package/dist/wirings/role/index.js +2 -0
  271. package/dist/wirings/role/role.types.d.ts +43 -0
  272. package/dist/wirings/role/role.types.js +1 -0
  273. package/dist/wirings/role/validate-role-definitions.d.ts +21 -0
  274. package/dist/wirings/role/validate-role-definitions.js +71 -0
  275. package/dist/wirings/rpc/addon-runner.d.ts +0 -19
  276. package/dist/wirings/rpc/addon-runner.js +0 -51
  277. package/dist/wirings/rpc/remote-addon-auth.d.ts +3 -13
  278. package/dist/wirings/rpc/remote-addon-auth.js +7 -11
  279. package/dist/wirings/rpc/rpc-runner.d.ts +11 -18
  280. package/dist/wirings/rpc/rpc-runner.js +88 -105
  281. package/dist/wirings/rpc/rpc-types.d.ts +7 -6
  282. package/dist/wirings/rpc/wire-addon.d.ts +25 -0
  283. package/dist/wirings/rpc/wire-addon.js +62 -0
  284. package/dist/wirings/rpc/wire-remote-addon.d.ts +3 -28
  285. package/dist/wirings/rpc/wire-remote-addon.js +0 -8
  286. package/dist/wirings/scheduler/log-schedulers.d.ts +0 -4
  287. package/dist/wirings/scheduler/log-schedulers.js +0 -4
  288. package/dist/wirings/scheduler/scheduler-runner.d.ts +0 -1
  289. package/dist/wirings/scheduler/scheduler-runner.js +0 -1
  290. package/dist/wirings/scheduler/scheduler.types.d.ts +1 -14
  291. package/dist/wirings/scope/define-scope.d.ts +32 -0
  292. package/dist/wirings/scope/define-scope.js +31 -0
  293. package/dist/wirings/scope/index.d.ts +1 -1
  294. package/dist/wirings/scope/index.js +1 -1
  295. package/dist/wirings/scope/scope.types.d.ts +7 -9
  296. package/dist/wirings/scope/validate-scope-definitions.d.ts +2 -21
  297. package/dist/wirings/scope/validate-scope-definitions.js +3 -21
  298. package/dist/wirings/secret/index.d.ts +1 -1
  299. package/dist/wirings/secret/index.js +1 -1
  300. package/dist/wirings/secret/secret.types.d.ts +19 -15
  301. package/dist/wirings/secret/secret.types.js +1 -1
  302. package/dist/wirings/secret/validate-secret-definitions.d.ts +2 -4
  303. package/dist/wirings/trigger/trigger-runner.d.ts +0 -27
  304. package/dist/wirings/trigger/trigger-runner.js +1 -24
  305. package/dist/wirings/trigger/trigger.types.d.ts +1 -82
  306. package/dist/wirings/trigger/trigger.types.js +0 -34
  307. package/dist/wirings/variable/index.d.ts +1 -1
  308. package/dist/wirings/variable/index.js +1 -1
  309. package/dist/wirings/variable/validate-variable-definitions.d.ts +2 -4
  310. package/dist/wirings/variable/variable.types.d.ts +1 -13
  311. package/dist/wirings/variable/variable.types.js +1 -1
  312. package/dist/wirings/virtual-user/index.d.ts +30 -0
  313. package/dist/wirings/virtual-user/index.js +10 -0
  314. package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +54 -0
  315. package/dist/wirings/virtual-user/prepare-virtual-user-run.js +49 -0
  316. package/dist/wirings/virtual-user/run-virtual-user.d.ts +92 -0
  317. package/dist/wirings/virtual-user/run-virtual-user.js +478 -0
  318. package/dist/wirings/virtual-user/virtual-user-agents.d.ts +38 -0
  319. package/dist/wirings/virtual-user/virtual-user-agents.js +24 -0
  320. package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +92 -0
  321. package/dist/wirings/virtual-user/virtual-user-catalogue.js +134 -0
  322. package/dist/wirings/virtual-user/virtual-user-derive.d.ts +26 -0
  323. package/dist/wirings/virtual-user/virtual-user-derive.js +137 -0
  324. package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +79 -0
  325. package/dist/wirings/virtual-user/virtual-user-dispositions.js +128 -0
  326. package/dist/wirings/virtual-user/virtual-user-intents.d.ts +78 -0
  327. package/dist/wirings/virtual-user/virtual-user-intents.js +142 -0
  328. package/dist/wirings/virtual-user/virtual-user-rng.d.ts +24 -0
  329. package/dist/wirings/virtual-user/virtual-user-rng.js +44 -0
  330. package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +90 -0
  331. package/dist/wirings/virtual-user/virtual-user-run-store.js +1 -0
  332. package/dist/wirings/virtual-user/virtual-user-target.d.ts +21 -0
  333. package/dist/wirings/virtual-user/virtual-user-target.js +34 -0
  334. package/dist/wirings/virtual-user/virtual-user.types.d.ts +199 -0
  335. package/dist/wirings/virtual-user/virtual-user.types.js +8 -0
  336. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +19 -15
  337. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +0 -4
  338. package/dist/wirings/workflow/dsl/workflow-runner.js +0 -4
  339. package/dist/wirings/workflow/feature.d.ts +0 -19
  340. package/dist/wirings/workflow/feature.js +0 -19
  341. package/dist/wirings/workflow/graph/graph-node.d.ts +0 -98
  342. package/dist/wirings/workflow/graph/graph-node.js +0 -34
  343. package/dist/wirings/workflow/graph/graph-runner.js +6 -41
  344. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +0 -4
  345. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -58
  346. package/dist/wirings/workflow/graph/workflow-graph.types.js +0 -6
  347. package/dist/wirings/workflow/index.d.ts +5 -7
  348. package/dist/wirings/workflow/index.js +3 -17
  349. package/dist/wirings/workflow/pikku-scenario-service.d.ts +87 -5
  350. package/dist/wirings/workflow/pikku-scenario-service.js +204 -43
  351. package/dist/wirings/workflow/pikku-workflow-service.d.ts +7 -459
  352. package/dist/wirings/workflow/pikku-workflow-service.js +58 -551
  353. package/dist/wirings/workflow/run-timeline.d.ts +0 -47
  354. package/dist/wirings/workflow/run-timeline.js +0 -22
  355. package/dist/wirings/workflow/scenario-cookie-jar.d.ts +0 -23
  356. package/dist/wirings/workflow/scenario-cookie-jar.js +0 -16
  357. package/dist/wirings/workflow/scenario-poll.d.ts +0 -15
  358. package/dist/wirings/workflow/scenario-poll.js +0 -12
  359. package/dist/wirings/workflow/scenario-prose.d.ts +0 -28
  360. package/dist/wirings/workflow/scenario-prose.js +1 -19
  361. package/dist/wirings/workflow/scenario-step-guards.d.ts +0 -13
  362. package/dist/wirings/workflow/scenario-step-guards.js +1 -14
  363. package/dist/wirings/workflow/scenario-step.types.d.ts +84 -12
  364. package/dist/wirings/workflow/scenario-step.types.js +5 -1
  365. package/dist/wirings/workflow/scenario-surface.d.ts +16 -0
  366. package/dist/wirings/workflow/scenario-surface.js +56 -0
  367. package/dist/wirings/workflow/workflow-invocation-id.d.ts +0 -18
  368. package/dist/wirings/workflow/workflow-invocation-id.js +2 -22
  369. package/dist/wirings/workflow/workflow-queue-workers.d.ts +0 -20
  370. package/dist/wirings/workflow/workflow-queue-workers.js +0 -19
  371. package/dist/wirings/workflow/workflow.types.d.ts +5 -195
  372. package/knowledge/decisions/index.md +19 -0
  373. package/knowledge/decisions/internals/a-secret-that-fails-to-decrypt-fails-the-whole-read.md +49 -0
  374. package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-and-not-a-queued-job.md +48 -0
  375. package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +23 -0
  376. package/knowledge/decisions/internals/actor-flow-drives-the-target-through-a-transport-seam.md +24 -0
  377. package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +25 -0
  378. package/knowledge/decisions/internals/addon-package-roots-resolve-by-walking-node-module-search-paths.md +26 -0
  379. package/knowledge/decisions/internals/addon-singleton-services-are-cached-per-namespace-not-per-package.md +33 -0
  380. package/knowledge/decisions/internals/addon-workflow-names-are-prefixed-with-the-consumer-namespace.md +28 -0
  381. package/knowledge/decisions/internals/ai-agent-agui-bridge-obeys-the-client-ordering-contract.md +29 -0
  382. package/knowledge/decisions/internals/ai-agent-audio-chunks-carry-the-format-the-provider-returned.md +20 -0
  383. package/knowledge/decisions/internals/ai-agent-credential-suspensions-hide-the-tool-result.md +26 -0
  384. package/knowledge/decisions/internals/ai-agent-delegate-and-supervise-hide-different-text.md +26 -0
  385. package/knowledge/decisions/internals/ai-agent-llm-tool-arguments-have-nulls-stripped.md +23 -0
  386. package/knowledge/decisions/internals/ai-agent-model-config-stays-a-single-resolution-seam.md +25 -0
  387. package/knowledge/decisions/internals/ai-agent-onerror-hooks-cannot-change-the-failure.md +22 -0
  388. package/knowledge/decisions/internals/ai-agent-runner-methods-must-keep-their-receiver.md +22 -0
  389. package/knowledge/decisions/internals/ai-agent-stream-persistence-is-best-effort.md +27 -0
  390. package/knowledge/decisions/internals/ai-agent-sub-agents-inherit-the-parent-context-block.md +26 -0
  391. package/knowledge/decisions/internals/ai-agent-tool-execute-failures-are-logged-unconditionally.md +25 -0
  392. package/knowledge/decisions/internals/ai-agent-voice-input-transcribes-audio-parts-in-place.md +22 -0
  393. package/knowledge/decisions/internals/ai-agent-working-memory-is-persisted-only-when-valid.md +25 -0
  394. package/knowledge/decisions/internals/channel-message-handlers-accept-three-config-shapes.md +30 -0
  395. package/knowledge/decisions/internals/channel-middleware-caches-only-statically-resolved-middleware.md +31 -0
  396. package/knowledge/decisions/internals/channel-state-is-per-socket-session-state-is-per-user.md +29 -0
  397. package/knowledge/decisions/internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md +28 -0
  398. package/knowledge/decisions/internals/cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md +27 -0
  399. package/knowledge/decisions/internals/cli-parse-errors-are-routed-by-message-prefix.md +28 -0
  400. package/knowledge/decisions/internals/cli-stdout-is-reserved-for-machine-readable-output.md +34 -0
  401. package/knowledge/decisions/internals/cli-unknown-long-options-warn-instead-of-failing.md +29 -0
  402. package/knowledge/decisions/internals/core-column-form-is-an-axis-of-its-own.md +84 -0
  403. package/knowledge/decisions/internals/core-data-classification-brand-is-an-optional-property.md +41 -0
  404. package/knowledge/decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md +44 -0
  405. package/knowledge/decisions/internals/core-hot-reload-merges-generated-meta-never-replaces-it.md +39 -0
  406. package/knowledge/decisions/internals/core-hot-reload-owns-its-module-registry.md +42 -0
  407. package/knowledge/decisions/internals/core-middleware-order-is-scope-then-priority.md +39 -0
  408. package/knowledge/decisions/internals/core-schema-defaults-apply-on-every-transport.md +43 -0
  409. package/knowledge/decisions/internals/core-scopes-are-an-and-gate-separate-from-permissions.md +38 -0
  410. package/knowledge/decisions/internals/core-state-is-a-global-map-written-only-at-registration-time.md +44 -0
  411. package/knowledge/decisions/internals/email-meta-is-read-uncached-because-codegen-rewrites-it-mid-session.md +27 -0
  412. package/knowledge/decisions/internals/gateway-adapters-resolve-lazily-and-are-promise-cached.md +32 -0
  413. package/knowledge/decisions/internals/gateway-webhook-challenges-echo-bytes-not-json.md +28 -0
  414. package/knowledge/decisions/internals/gateway-wiring-is-a-meta-wiring-over-http-and-channels.md +31 -0
  415. package/knowledge/decisions/internals/generated-src-paths-in-pikku-meta-are-absolute.md +26 -0
  416. package/knowledge/decisions/internals/http-request-bodies-are-read-once-and-shared.md +32 -0
  417. package/knowledge/decisions/internals/http-route-groups-cascade-config-in-a-fixed-order.md +28 -0
  418. package/knowledge/decisions/internals/http-router-matches-normalized-paths-but-returns-registered-ones.md +30 -0
  419. package/knowledge/decisions/internals/http-runner-logs-through-a-trace-scoped-logger-functions-do-not.md +26 -0
  420. package/knowledge/decisions/internals/http-set-cookie-headers-are-appended-never-joined.md +28 -0
  421. package/knowledge/decisions/internals/http-sse-streams-flush-headers-only-after-middleware.md +32 -0
  422. package/knowledge/decisions/internals/http-wiring-without-metadata-is-skipped-not-fatal.md +26 -0
  423. package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +25 -0
  424. package/knowledge/decisions/internals/in-memory-workflow-history-aliases-the-live-step-object.md +26 -0
  425. package/knowledge/decisions/internals/index.md +117 -0
  426. package/knowledge/decisions/internals/istanbul-statement-counts-attach-to-the-start-line-only.md +25 -0
  427. package/knowledge/decisions/internals/local-trigger-and-gateway-services-assume-a-single-process.md +26 -0
  428. package/knowledge/decisions/internals/node-only-builtins-are-imported-dynamically.md +24 -0
  429. package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +53 -0
  430. package/knowledge/decisions/internals/queue-group-concurrency-keeps-one-shared-queue-fair.md +28 -0
  431. package/knowledge/decisions/internals/queue-jobs-always-carry-an-explicit-attempts-count.md +27 -0
  432. package/knowledge/decisions/internals/remote-addons-dispatch-over-http-instead-of-local-meta.md +31 -0
  433. package/knowledge/decisions/internals/rpc-names-resolve-through-package-scope-before-root.md +32 -0
  434. package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +27 -0
  435. package/knowledge/decisions/internals/scenario-meta-lives-apart-from-app-meta-but-merges-when-read-off-disk.md +26 -0
  436. package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +27 -0
  437. package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +48 -0
  438. package/knowledge/decisions/internals/scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md +30 -0
  439. package/knowledge/decisions/internals/serverless-channel-disconnect-must-tolerate-a-missing-channel.md +28 -0
  440. package/knowledge/decisions/internals/the-dev-queue-copies-prod-timing-and-serialization-semantics.md +30 -0
  441. package/knowledge/decisions/internals/the-embedding-model-is-pinned-per-service-and-doc-query-embedding-is-split.md +29 -0
  442. package/knowledge/decisions/internals/the-in-memory-workflow-service-is-inline-only-and-single-process.md +27 -0
  443. package/knowledge/decisions/internals/the-kek-salt-is-scoped-to-the-key-version.md +40 -0
  444. package/knowledge/decisions/internals/the-schema-service-is-never-stubbed.md +26 -0
  445. package/knowledge/decisions/internals/trigger-declaration-is-split-from-trigger-source.md +33 -0
  446. package/knowledge/decisions/internals/typed-secret-service-caches-for-the-process-lifetime.md +26 -0
  447. package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +43 -0
  448. package/knowledge/decisions/internals/webhook-delivery-history-records-every-attempt-best-effort.md +26 -0
  449. package/knowledge/decisions/internals/webhook-service-collaborators-are-constructor-args-not-locator-lookups.md +25 -0
  450. package/knowledge/decisions/internals/whether-a-run-is-inline-is-read-from-the-run-record.md +58 -0
  451. package/knowledge/decisions/internals/workflow-approval-expiry-is-decided-from-a-recorded-deadline.md +34 -0
  452. package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +42 -0
  453. package/knowledge/decisions/internals/workflow-dsl-meta-separates-runtime-expressions-from-literals.md +38 -0
  454. package/knowledge/decisions/internals/workflow-features-resolve-scenarios-by-object-identity.md +29 -0
  455. package/knowledge/decisions/internals/workflow-graph-inline-and-queued-runs-share-one-planner.md +42 -0
  456. package/knowledge/decisions/internals/workflow-graph-node-notes-are-excluded-from-the-graph-hash.md +25 -0
  457. package/knowledge/decisions/internals/workflow-inline-runs-report-their-run-id-before-they-can-fail.md +29 -0
  458. package/knowledge/decisions/internals/workflow-invocation-id-is-the-dedupe-key-not-step-id.md +43 -0
  459. package/knowledge/decisions/internals/workflow-queued-step-dispatch-requires-an-explicit-opt-in.md +29 -0
  460. package/knowledge/decisions/internals/workflow-queues-are-per-workflow-by-default.md +42 -0
  461. package/knowledge/decisions/internals/workflow-repeated-step-names-get-an-ordinal-suffix.md +33 -0
  462. package/knowledge/decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md +32 -0
  463. package/knowledge/decisions/internals/workflow-retries-are-owned-by-the-workflow-not-the-queue.md +31 -0
  464. package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +39 -0
  465. package/knowledge/decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md +29 -0
  466. package/knowledge/decisions/internals/workflow-run-polling-backs-off-to-the-callers-ceiling.md +33 -0
  467. package/knowledge/decisions/internals/workflow-run-timeline-is-a-pure-fold-over-durable-history.md +37 -0
  468. package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +50 -0
  469. package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +43 -0
  470. package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +32 -0
  471. package/knowledge/decisions/internals/workflow-scenario-quarantine-reason-lives-in-code.md +18 -0
  472. package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +34 -0
  473. package/knowledge/decisions/internals/workflow-step-compensation-runs-as-its-own-durable-step.md +26 -0
  474. package/knowledge/decisions/internals/workflow-step-dispatch-failure-is-transient-not-a-run-failure.md +33 -0
  475. package/knowledge/decisions/internals/workflow-step-lock-is-held-only-to-claim-the-step.md +27 -0
  476. package/knowledge/decisions/internals/workflow-step-rpc-name-is-provenance-only.md +34 -0
  477. package/knowledge/decisions/internals/workflow-suspend-and-approval-reasons-are-durable-step-identities.md +38 -0
  478. package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +30 -0
  479. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +26 -0
  480. package/knowledge/decisions/security/actor-flow-missing-approval-decisions-default-to-denied.md +22 -0
  481. package/knowledge/decisions/security/actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md +27 -0
  482. package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +27 -0
  483. package/knowledge/decisions/security/addon-auth-and-tags-only-tighten.md +43 -0
  484. package/knowledge/decisions/security/addon-config-gates-apply-only-at-the-namespaced-rpc-boundary.md +52 -0
  485. package/knowledge/decisions/security/addon-scopes-are-resolved-where-the-function-runs.md +46 -0
  486. package/knowledge/decisions/security/ai-agent-approval-forwarding-requires-a-symbol-brand.md +27 -0
  487. package/knowledge/decisions/security/ai-agent-credential-requests-are-symbol-branded.md +37 -0
  488. package/knowledge/decisions/security/ai-agent-gate-requires-a-session-only-when-auth-is-true.md +31 -0
  489. package/knowledge/decisions/security/ai-agent-ownership-failures-never-echo-the-resource.md +23 -0
  490. package/knowledge/decisions/security/ai-agent-resume-re-runs-the-authorization-gate.md +22 -0
  491. package/knowledge/decisions/security/ai-agent-sessionless-deployments-have-no-thread-ownership.md +39 -0
  492. package/knowledge/decisions/security/ai-agent-thread-ownership-composes-the-session-principal.md +30 -0
  493. package/knowledge/decisions/security/ai-agent-tool-filtering-reads-the-live-function-config.md +24 -0
  494. package/knowledge/decisions/security/an-empty-owners-constraint-matches-nothing.md +30 -0
  495. package/knowledge/decisions/security/an-exposed-ungated-function-is-a-codegen-warning.md +49 -0
  496. package/knowledge/decisions/security/console-addon-privileged-functions-gate-themselves.md +76 -0
  497. package/knowledge/decisions/security/core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md +39 -0
  498. package/knowledge/decisions/security/core-secrets-use-a-per-secret-dek-wrapped-by-a-kek.md +37 -0
  499. package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +31 -0
  500. package/knowledge/decisions/security/gateway-middleware-sessions-must-be-bridged-onto-the-wire.md +30 -0
  501. package/knowledge/decisions/security/global-permissions-and-function-permissions-are-independent-gates.md +40 -0
  502. package/knowledge/decisions/security/http-error-detail-is-withheld-from-clients-in-production.md +33 -0
  503. package/knowledge/decisions/security/http-request-bodies-are-bounded-before-they-are-buffered.md +46 -0
  504. package/knowledge/decisions/security/index.md +55 -0
  505. package/knowledge/decisions/security/mcp-internal-error-details-are-double-gated-on-production.md +27 -0
  506. package/knowledge/decisions/security/passphrases-are-stretched-key-material-is-expanded.md +40 -0
  507. package/knowledge/decisions/security/permission-auth-filtering-requires-live-permission-functions.md +31 -0
  508. package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +26 -0
  509. package/knowledge/decisions/security/queue-job-identities-are-signed-at-enqueue.md +69 -0
  510. package/knowledge/decisions/security/queue-jobs-carry-the-producers-pikku-user-id.md +39 -0
  511. package/knowledge/decisions/security/remote-addon-tokens-are-client-credentials-not-mesh-trust.md +34 -0
  512. package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +49 -0
  513. package/knowledge/decisions/security/scenario-step-functions-are-never-externally-invocable.md +30 -0
  514. package/knowledge/decisions/security/scope-resolution-happens-at-the-session-boundary-and-sync-never-deletes.md +28 -0
  515. package/knowledge/decisions/security/self-authentication-is-declared-not-detected.md +34 -0
  516. package/knowledge/decisions/security/signed-content-urls-bind-the-request-path.md +37 -0
  517. package/knowledge/decisions/security/webhook-bodies-are-signed-before-they-are-enqueued.md +25 -0
  518. package/knowledge/decisions/security/workflow-actor-steps-always-use-the-real-transport.md +34 -0
  519. package/knowledge/decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md +40 -0
  520. package/knowledge/decisions/security/workflow-queued-steps-rehydrate-their-session-from-the-run-wire.md +32 -0
  521. package/knowledge/decisions/security/workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md +32 -0
  522. package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +31 -0
  523. package/knowledge/index.md +24 -0
  524. package/knowledge/questions/index.md +15 -0
  525. package/package.json +6 -2
  526. package/run-tests.sh +0 -0
  527. package/src/column-form.test.ts +97 -0
  528. package/src/column-form.ts +58 -0
  529. package/src/crypto-utils.test.ts +460 -19
  530. package/src/crypto-utils.ts +306 -59
  531. package/src/data-classification.ts +45 -7
  532. package/src/dev/hot-reload.test.ts +0 -4
  533. package/src/dev/hot-reload.ts +11 -30
  534. package/src/dev/module-runner.ts +7 -32
  535. package/src/dev/reload-meta.ts +9 -29
  536. package/src/errors/error-handler.ts +20 -35
  537. package/src/errors/error.test.ts +30 -1
  538. package/src/errors/errors.ts +73 -157
  539. package/src/function/abort-scope.test.ts +97 -0
  540. package/src/function/abort-scope.ts +80 -0
  541. package/src/function/function-runner.test.ts +0 -7
  542. package/src/function/function-runner.ts +62 -32
  543. package/src/function/functions.types.ts +95 -138
  544. package/src/function/index.ts +1 -0
  545. package/src/function/list.types.test.ts +3 -25
  546. package/src/function/list.types.ts +12 -62
  547. package/src/handle-error.ts +0 -18
  548. package/src/index.ts +84 -3
  549. package/src/middleware/auth-apikey.test.ts +0 -1
  550. package/src/middleware/auth-apikey.ts +0 -17
  551. package/src/middleware/auth-bearer.test.ts +3 -5
  552. package/src/middleware/auth-bearer.ts +5 -41
  553. package/src/middleware/auth-cookie.test.ts +0 -6
  554. package/src/middleware/auth-cookie.ts +2 -26
  555. package/src/middleware/cors.test.ts +34 -0
  556. package/src/middleware/cors.ts +12 -33
  557. package/src/middleware/remote-auth.test.ts +26 -10
  558. package/src/middleware/remote-auth.ts +11 -3
  559. package/src/middleware/telemetry.ts +2 -31
  560. package/src/middleware-runner.test.ts +0 -2
  561. package/src/middleware-runner.ts +5 -74
  562. package/src/permissions.test.ts +30 -0
  563. package/src/permissions.ts +24 -74
  564. package/src/pikku-request.ts +0 -6
  565. package/src/pikku-state.ts +2 -30
  566. package/src/production-barrels-stay-lean.test.ts +110 -0
  567. package/src/remote.test.ts +173 -0
  568. package/src/remote.ts +18 -8
  569. package/src/schema.ts +5 -39
  570. package/src/scopes.ts +7 -48
  571. package/src/secret-value.test.ts +204 -0
  572. package/src/secret-value.ts +111 -0
  573. package/src/services/ai-agent-runner-service.ts +20 -0
  574. package/src/services/ai-embedding-service.ts +3 -25
  575. package/src/services/audit-service.ts +88 -11
  576. package/src/services/content-service.ts +1 -46
  577. package/src/services/credential-service.ts +3 -40
  578. package/src/services/credential-wire-service.test.ts +0 -2
  579. package/src/services/credential-wire-service.ts +9 -1
  580. package/src/services/deployment-service.ts +3 -9
  581. package/src/services/email-service.ts +3 -1
  582. package/src/services/gateway-service.ts +0 -15
  583. package/src/services/{http-scenario-actors-converse.test.ts → http-personas-converse.test.ts} +38 -9
  584. package/src/services/{http-scenario-actors.test.ts → http-personas.test.ts} +39 -22
  585. package/src/services/{http-scenario-actors.ts → http-personas.ts} +85 -45
  586. package/src/services/in-memory-queue-service.ts +1 -15
  587. package/src/services/in-memory-trigger-service.ts +1 -18
  588. package/src/services/in-memory-workflow-service.test.ts +0 -13
  589. package/src/services/in-memory-workflow-service.ts +4 -38
  590. package/src/services/index.ts +23 -18
  591. package/src/services/istanbul-coverage-service.ts +2 -8
  592. package/src/services/jwt-service.ts +1 -16
  593. package/src/services/local-content-request-handler.test.ts +202 -0
  594. package/src/services/local-content-request-handler.ts +267 -0
  595. package/src/services/local-content.test.ts +159 -27
  596. package/src/services/local-content.ts +55 -23
  597. package/src/services/local-gateway-service.ts +2 -17
  598. package/src/services/local-secrets.test.ts +20 -5
  599. package/src/services/local-secrets.ts +15 -11
  600. package/src/services/logger-console.test.ts +0 -1
  601. package/src/services/logger-console.ts +3 -7
  602. package/src/services/logger.ts +31 -46
  603. package/src/services/meta-service.test.ts +1 -5
  604. package/src/services/meta-service.ts +41 -61
  605. package/src/services/{scenario-actors-service.ts → personas-service.ts} +48 -43
  606. package/src/services/pikku-user-id.ts +0 -4
  607. package/src/services/queue-webhook-service.test.ts +2 -1
  608. package/src/services/queue-webhook-service.ts +10 -42
  609. package/src/services/scheduler-service.ts +1 -50
  610. package/src/services/schema-service.ts +1 -24
  611. package/src/services/scope-service.ts +50 -34
  612. package/src/services/scoped-secret-service.ts +4 -7
  613. package/src/services/secret-host-binding.test.ts +138 -0
  614. package/src/services/secret-host-binding.ts +51 -0
  615. package/src/services/secret-service.ts +12 -35
  616. package/src/services/secretless.test.ts +54 -0
  617. package/src/services/secretless.ts +29 -0
  618. package/src/services/stub-tracker.ts +8 -18
  619. package/src/services/system-role-guard.test.ts +93 -0
  620. package/src/services/system-role-guard.ts +71 -0
  621. package/src/services/trigger-service.ts +0 -12
  622. package/src/services/typed-secret-service.ts +12 -14
  623. package/src/services/v8-coverage-service.ts +3 -6
  624. package/src/services/variables-service.ts +1 -8
  625. package/src/services/webhook-service.ts +23 -64
  626. package/src/services/workflow-service.ts +3 -20
  627. package/src/testing/service-tests.ts +6 -32
  628. package/src/time-utils.ts +1 -19
  629. package/src/types/core.types.ts +137 -229
  630. package/src/types/state.types.ts +7 -9
  631. package/src/utils/hmac.ts +4 -10
  632. package/src/utils/safe-fetch.ts +13 -54
  633. package/src/utils.test.ts +11 -2
  634. package/src/utils.ts +6 -15
  635. package/src/wirings/actor-flow/actor-flow.types.ts +1 -34
  636. package/src/wirings/actor-flow/index.ts +0 -9
  637. package/src/wirings/actor-flow/run-conversation.test.ts +11 -6
  638. package/src/wirings/actor-flow/run-conversation.ts +19 -12
  639. package/src/wirings/ai-agent/ai-agent-agui.test.ts +91 -10
  640. package/src/wirings/ai-agent/ai-agent-agui.ts +49 -17
  641. package/src/wirings/ai-agent/ai-agent-helpers.ts +20 -0
  642. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +842 -0
  643. package/src/wirings/ai-agent/ai-agent-interrupt.ts +399 -0
  644. package/src/wirings/ai-agent/ai-agent-memory.ts +0 -2
  645. package/src/wirings/ai-agent/ai-agent-model-config.ts +1 -9
  646. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +202 -31
  647. package/src/wirings/ai-agent/ai-agent-prepare.ts +89 -139
  648. package/src/wirings/ai-agent/ai-agent-registry.test.ts +191 -6
  649. package/src/wirings/ai-agent/ai-agent-registry.ts +18 -1
  650. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +0 -2
  651. package/src/wirings/ai-agent/ai-agent-runner.test.ts +11 -9
  652. package/src/wirings/ai-agent/ai-agent-runner.ts +85 -35
  653. package/src/wirings/ai-agent/ai-agent-stream.test.ts +205 -103
  654. package/src/wirings/ai-agent/ai-agent-stream.ts +224 -76
  655. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +301 -0
  656. package/src/wirings/ai-agent/ai-agent.types.ts +130 -5
  657. package/src/wirings/ai-agent/index.ts +37 -3
  658. package/src/wirings/ai-agent/voice-input.test.ts +137 -7
  659. package/src/wirings/ai-agent/voice-input.ts +96 -12
  660. package/src/wirings/ai-agent/voice-output.test.ts +512 -0
  661. package/src/wirings/ai-agent/voice-output.ts +243 -56
  662. package/src/wirings/channel/channel-common.ts +15 -20
  663. package/src/wirings/channel/channel-handler.test.ts +50 -0
  664. package/src/wirings/channel/channel-handler.ts +32 -11
  665. package/src/wirings/channel/channel-host-rpc.test.ts +150 -0
  666. package/src/wirings/channel/channel-host-rpc.ts +69 -0
  667. package/src/wirings/channel/channel-middleware-runner.test.ts +0 -1
  668. package/src/wirings/channel/channel-middleware-runner.ts +0 -12
  669. package/src/wirings/channel/channel-rpc-registry.ts +116 -0
  670. package/src/wirings/channel/channel-rpc-responder.ts +117 -0
  671. package/src/wirings/channel/channel-rpc-service.ts +146 -0
  672. package/src/wirings/channel/channel-rpc-validators.ts +65 -0
  673. package/src/wirings/channel/channel-rpc.test.ts +820 -0
  674. package/src/wirings/channel/channel-rpc.ts +5 -0
  675. package/src/wirings/channel/channel-rpc.types.ts +150 -0
  676. package/src/wirings/channel/channel-runner.ts +0 -14
  677. package/src/wirings/channel/channel-store.ts +0 -10
  678. package/src/wirings/channel/channel.types.ts +19 -8
  679. package/src/wirings/channel/define-channel-routes.ts +0 -20
  680. package/src/wirings/channel/eventhub-service.ts +0 -18
  681. package/src/wirings/channel/index.ts +35 -0
  682. package/src/wirings/channel/local/local-channel-handler.ts +3 -1
  683. package/src/wirings/channel/local/local-channel-runner.test.ts +0 -10
  684. package/src/wirings/channel/local/local-channel-runner.ts +3 -1
  685. package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -13
  686. package/src/wirings/channel/local/local-eventhub-service.ts +2 -37
  687. package/src/wirings/channel/log-channels.ts +0 -4
  688. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +83 -2
  689. package/src/wirings/channel/pikku-abstract-channel-handler.ts +7 -0
  690. package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -5
  691. package/src/wirings/cli/channel/cli-approval.test.ts +177 -0
  692. package/src/wirings/cli/channel/cli-approval.ts +135 -0
  693. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -26
  694. package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +169 -0
  695. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +59 -16
  696. package/src/wirings/cli/channel/cli-raw-client-runner.test.ts +480 -0
  697. package/src/wirings/cli/channel/cli-raw-client-runner.ts +185 -0
  698. package/src/wirings/cli/channel/index.ts +13 -0
  699. package/src/wirings/cli/cli-runner.test.ts +0 -1
  700. package/src/wirings/cli/cli-runner.ts +46 -88
  701. package/src/wirings/cli/cli.types.ts +14 -3
  702. package/src/wirings/cli/command-parser.test.ts +0 -4
  703. package/src/wirings/cli/command-parser.ts +11 -91
  704. package/src/wirings/cli/define-cli-commands.ts +1 -17
  705. package/src/wirings/credential/credential.types.ts +0 -12
  706. package/src/wirings/credential/{wire-credential.ts → define-credential.ts} +7 -7
  707. package/src/wirings/credential/index.ts +1 -1
  708. package/src/wirings/credential/validate-credential-definitions.ts +2 -4
  709. package/src/wirings/gateway/gateway-runner.test.ts +1 -21
  710. package/src/wirings/gateway/gateway-runner.ts +8 -110
  711. package/src/wirings/gateway/gateway.types.ts +7 -80
  712. package/src/wirings/http/http-routes.test.ts +0 -3
  713. package/src/wirings/http/http-routes.ts +0 -86
  714. package/src/wirings/http/http-runner.test.ts +0 -1
  715. package/src/wirings/http/http-runner.ts +8 -167
  716. package/src/wirings/http/http.types.ts +15 -62
  717. package/src/wirings/http/log-http-routes.ts +0 -4
  718. package/src/wirings/http/pikku-fetch-http-request.test.ts +2 -10
  719. package/src/wirings/http/pikku-fetch-http-request.ts +0 -58
  720. package/src/wirings/http/pikku-fetch-http-response.test.ts +1 -1
  721. package/src/wirings/http/pikku-fetch-http-response.ts +0 -3
  722. package/src/wirings/http/routers/path-to-regex.test.ts +4 -17
  723. package/src/wirings/http/routers/path-to-regex.ts +2 -13
  724. package/src/wirings/http/web-request.test.ts +33 -2
  725. package/src/wirings/http/web-request.ts +30 -17
  726. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -1
  727. package/src/wirings/mcp/mcp-runner.ts +2 -17
  728. package/src/wirings/mcp/mcp.types.ts +7 -42
  729. package/src/wirings/oauth2/oauth2.types.ts +0 -30
  730. package/src/wirings/persona/define-personas.ts +33 -0
  731. package/src/wirings/persona/index.ts +62 -0
  732. package/src/wirings/persona/persona-email.ts +87 -0
  733. package/src/wirings/persona/persona-environments.test.ts +183 -0
  734. package/src/wirings/persona/persona-environments.ts +138 -0
  735. package/src/wirings/persona/persona-mailbox.ts +156 -0
  736. package/src/wirings/persona/persona.test.ts +220 -0
  737. package/src/wirings/persona/persona.types.ts +142 -0
  738. package/src/wirings/persona/validate-personas.ts +133 -0
  739. package/src/wirings/queue/index.ts +13 -3
  740. package/src/wirings/queue/queue-identity.test.ts +454 -0
  741. package/src/wirings/queue/queue-identity.ts +176 -0
  742. package/src/wirings/queue/queue-runner.ts +12 -31
  743. package/src/wirings/queue/queue.types.ts +25 -90
  744. package/src/wirings/queue/register-queue-helper.ts +0 -14
  745. package/src/wirings/queue/signed-queue-service.ts +60 -0
  746. package/src/wirings/queue/validate-worker-config.ts +2 -28
  747. package/src/wirings/role/define-system-role.ts +33 -0
  748. package/src/wirings/role/index.ts +13 -0
  749. package/src/wirings/role/role.test.ts +104 -0
  750. package/src/wirings/role/role.types.ts +47 -0
  751. package/src/wirings/role/validate-role-definitions.ts +93 -0
  752. package/src/wirings/rpc/addon-auth-tags.test.ts +223 -0
  753. package/src/wirings/rpc/addon-runner.ts +0 -56
  754. package/src/wirings/rpc/addon-scopes.test.ts +225 -0
  755. package/src/wirings/rpc/remote-addon-auth.ts +9 -16
  756. package/src/wirings/rpc/rpc-runner.test.ts +192 -6
  757. package/src/wirings/rpc/rpc-runner.ts +145 -127
  758. package/src/wirings/rpc/rpc-types.ts +11 -6
  759. package/src/wirings/rpc/wire-addon.test.ts +43 -1
  760. package/src/wirings/rpc/wire-addon.ts +99 -0
  761. package/src/wirings/rpc/wire-remote-addon.ts +9 -29
  762. package/src/wirings/scheduler/log-schedulers.ts +0 -4
  763. package/src/wirings/scheduler/scheduler-runner.test.ts +1 -8
  764. package/src/wirings/scheduler/scheduler-runner.ts +0 -2
  765. package/src/wirings/scheduler/scheduler.types.ts +1 -14
  766. package/src/wirings/scope/{wire-scope.ts → define-scope.ts} +5 -6
  767. package/src/wirings/scope/index.ts +1 -1
  768. package/src/wirings/scope/scope.test.ts +1 -2
  769. package/src/wirings/scope/scope.types.ts +7 -9
  770. package/src/wirings/scope/validate-scope-definitions.ts +3 -21
  771. package/src/wirings/secret/index.ts +1 -1
  772. package/src/wirings/secret/secret.types.ts +19 -15
  773. package/src/wirings/secret/validate-secret-definitions.ts +2 -4
  774. package/src/wirings/trigger/trigger-runner.ts +1 -27
  775. package/src/wirings/trigger/trigger.types.ts +1 -82
  776. package/src/wirings/variable/index.ts +1 -1
  777. package/src/wirings/variable/validate-variable-definitions.ts +2 -4
  778. package/src/wirings/variable/variable.types.ts +1 -13
  779. package/src/wirings/virtual-user/index.ts +88 -0
  780. package/src/wirings/virtual-user/prepare-virtual-user-run.test.ts +115 -0
  781. package/src/wirings/virtual-user/prepare-virtual-user-run.ts +95 -0
  782. package/src/wirings/virtual-user/run-virtual-user.test.ts +765 -0
  783. package/src/wirings/virtual-user/run-virtual-user.ts +671 -0
  784. package/src/wirings/virtual-user/virtual-user-agents.test.ts +65 -0
  785. package/src/wirings/virtual-user/virtual-user-agents.ts +57 -0
  786. package/src/wirings/virtual-user/virtual-user-catalogue.test.ts +215 -0
  787. package/src/wirings/virtual-user/virtual-user-catalogue.ts +184 -0
  788. package/src/wirings/virtual-user/virtual-user-derive.test.ts +398 -0
  789. package/src/wirings/virtual-user/virtual-user-derive.ts +173 -0
  790. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +63 -0
  791. package/src/wirings/virtual-user/virtual-user-dispositions.ts +213 -0
  792. package/src/wirings/virtual-user/virtual-user-intents.test.ts +208 -0
  793. package/src/wirings/virtual-user/virtual-user-intents.ts +185 -0
  794. package/src/wirings/virtual-user/virtual-user-rng.test.ts +72 -0
  795. package/src/wirings/virtual-user/virtual-user-rng.ts +50 -0
  796. package/src/wirings/virtual-user/virtual-user-run-store.ts +98 -0
  797. package/src/wirings/virtual-user/virtual-user-target.ts +47 -0
  798. package/src/wirings/virtual-user/virtual-user.types.ts +219 -0
  799. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +19 -20
  800. package/src/wirings/workflow/dsl/workflow-runner.ts +0 -4
  801. package/src/wirings/workflow/feature.ts +0 -19
  802. package/src/wirings/workflow/graph/graph-node.ts +0 -136
  803. package/src/wirings/workflow/graph/graph-runner.test.ts +20 -19
  804. package/src/wirings/workflow/graph/graph-runner.ts +6 -41
  805. package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -4
  806. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -58
  807. package/src/wirings/workflow/index.ts +10 -44
  808. package/src/wirings/workflow/pikku-scenario-service.ts +235 -61
  809. package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -39
  810. package/src/wirings/workflow/pikku-workflow-service.ts +77 -674
  811. package/src/wirings/workflow/run-timeline.test.ts +7 -19
  812. package/src/wirings/workflow/run-timeline.ts +0 -56
  813. package/src/wirings/workflow/scenario-cookie-jar.test.ts +0 -1
  814. package/src/wirings/workflow/scenario-cookie-jar.ts +0 -25
  815. package/src/wirings/workflow/scenario-expectations.test.ts +2 -7
  816. package/src/wirings/workflow/scenario-hooks.test.ts +2 -7
  817. package/src/wirings/workflow/scenario-poll.test.ts +0 -2
  818. package/src/wirings/workflow/scenario-poll.ts +0 -15
  819. package/src/wirings/workflow/scenario-prose.test.ts +5 -7
  820. package/src/wirings/workflow/scenario-prose.ts +1 -29
  821. package/src/wirings/workflow/scenario-service.test.ts +2 -10
  822. package/src/wirings/workflow/scenario-step-guards.ts +1 -14
  823. package/src/wirings/workflow/scenario-step.test.ts +163 -19
  824. package/src/wirings/workflow/scenario-step.types.ts +94 -12
  825. package/src/wirings/workflow/scenario-surface.test.ts +146 -0
  826. package/src/wirings/workflow/scenario-surface.ts +71 -0
  827. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +14 -15
  828. package/src/wirings/workflow/workflow-dispatch-payload.test.ts +0 -4
  829. package/src/wirings/workflow/workflow-inline-authority.test.ts +169 -0
  830. package/src/wirings/workflow/workflow-invocation-id.test.ts +0 -2
  831. package/src/wirings/workflow/workflow-invocation-id.ts +2 -22
  832. package/src/wirings/workflow/workflow-mirror.test.ts +0 -7
  833. package/src/wirings/workflow/workflow-on-error.test.ts +0 -9
  834. package/src/wirings/workflow/workflow-queue-workers.ts +0 -21
  835. package/src/wirings/workflow/workflow-replay-snapshot.test.ts +8 -7
  836. package/src/wirings/workflow/workflow-retry-policy.test.ts +0 -5
  837. package/src/wirings/workflow/workflow-run-context.test.ts +5 -10
  838. package/src/wirings/workflow/workflow-run-polling.test.ts +0 -5
  839. package/src/wirings/workflow/workflow-step-ordinal.test.ts +19 -4
  840. package/src/wirings/workflow/workflow-step-session.test.ts +0 -7
  841. package/src/wirings/workflow/workflow.types.ts +5 -201
  842. package/tsconfig.tsbuildinfo +1 -1
  843. package/tsconfig.type-tests.json +12 -0
  844. package/src/middleware/timeout.ts +0 -22
  845. package/src/pikku-response.ts +0 -5
  846. package/src/wirings/mcp/mcp-endpoint-registry.test.d.ts +0 -1
  847. package/src/wirings/workflow/dsl/index.ts +0 -36
  848. package/src/wirings/workflow/graph/index.ts +0 -15
@@ -70,7 +70,7 @@ import {
70
70
  runFromMeta,
71
71
  } from './graph/graph-runner.js'
72
72
  import type { WorkflowService } from '../../services/workflow-service.js'
73
- import type { ScenarioActors } from '../../services/scenario-actors-service.js'
73
+ import type { ScenarioPersonas } from '../../services/personas-service.js'
74
74
  import {
75
75
  PikkuError,
76
76
  addError,
@@ -92,18 +92,8 @@ import type {
92
92
  PikkuWorkerConfig,
93
93
  } from '../queue/queue.types.js'
94
94
 
95
- /**
96
- * Default number of retries for a workflow step when none is specified. The
97
- * workflow — not the queue — owns retry policy; a step inherits this unless it
98
- * sets its own `retries` (including `retries: 0` to opt out entirely). Picked >0
99
- * so a transient failure (a DB blip, a downstream restart, a deploy) is ridden
100
- * out by default; safe because every step gets a stable `invocationId` to dedupe on.
101
- */
102
95
  export const DEFAULT_STEP_RETRIES = 5
103
96
 
104
- /**
105
- * Exception thrown when workflow needs to pause for async step
106
- */
107
97
  export class WorkflowAsyncException extends Error {
108
98
  constructor(
109
99
  public readonly runId: string,
@@ -114,9 +104,6 @@ export class WorkflowAsyncException extends Error {
114
104
  }
115
105
  }
116
106
 
117
- /**
118
- * Exception thrown when workflow is cancelled
119
- */
120
107
  export class WorkflowCancelledException extends Error {
121
108
  constructor(
122
109
  public readonly runId: string,
@@ -127,9 +114,6 @@ export class WorkflowCancelledException extends Error {
127
114
  }
128
115
  }
129
116
 
130
- /**
131
- * Exception thrown when workflow is suspended
132
- */
133
117
  export class WorkflowSuspendedException extends Error {
134
118
  constructor(
135
119
  public readonly runId: string,
@@ -140,14 +124,6 @@ export class WorkflowSuspendedException extends Error {
140
124
  }
141
125
  }
142
126
 
143
- /**
144
- * Thrown when a step (or the orchestrator) could not be enqueued — the queue
145
- * itself failed (e.g. pg-boss is momentarily down), NOT the step's own logic.
146
- * This is transient infrastructure failure: the run is left untouched (the step
147
- * stays `pending`, the run stays running) and the orchestrator job is rethrown
148
- * so the queue redelivers it and the workflow replays from its snapshot. Treat
149
- * it as non-terminal — never mark the run `failed` for it.
150
- */
151
127
  export class WorkflowDispatchException extends Error {
152
128
  constructor(
153
129
  public readonly runId: string,
@@ -162,9 +138,6 @@ export class WorkflowDispatchException extends Error {
162
138
  }
163
139
  }
164
140
 
165
- /**
166
- * Error class for workflow not found
167
- */
168
141
  export class WorkflowNotFoundError extends PikkuError {
169
142
  constructor(name: string) {
170
143
  super(`Workflow not found: ${name}`)
@@ -207,12 +180,6 @@ addError(WorkflowRunCancelledError, {
207
180
  message: 'Workflow was cancelled.',
208
181
  })
209
182
 
210
- /**
211
- * A decision arrived for an approval gate that has already resolved. The gate
212
- * caches its outcome as the step result and never re-reads run state, so the
213
- * decision could not take effect — it is rejected rather than accepted and
214
- * dropped.
215
- */
216
183
  export class WorkflowApprovalResolvedError extends PikkuError {
217
184
  public payload: {
218
185
  reason: string
@@ -242,11 +209,6 @@ const WORKFLOW_END_STATES: ReadonlySet<string> = new Set([
242
209
  'suspended',
243
210
  ])
244
211
 
245
- /**
246
- * Everything an extension needs to run something of its own around a run's
247
- * function: the registration it was resolved from, the run itself (so `input`
248
- * is the same object the function is called with), and the wire it is given.
249
- */
250
212
  export interface RunLifecycleContext {
251
213
  runId: string
252
214
  run: WorkflowRun
@@ -256,12 +218,6 @@ export interface RunLifecycleContext {
256
218
  packageName: string | null
257
219
  }
258
220
 
259
- /**
260
- * The slice of the run engine an extension is allowed to drive, handed to it at
261
- * construction. It exists so that recording a durable step stays available to
262
- * an extension without `inlineStep` and friends becoming public API on every
263
- * workflow service a production app instantiates.
264
- */
265
221
  export interface WorkflowRunEngine {
266
222
  inlineStep(
267
223
  runId: string,
@@ -281,25 +237,13 @@ export interface WorkflowRunEngine {
281
237
  verifyStepName(stepName: unknown): void
282
238
  }
283
239
 
284
- /**
285
- * A capability layered onto a run without being a workflow service itself.
286
- *
287
- * The engine names nothing about what an extension is for: a bundler drops an
288
- * unused *module* but never an unused class member, so the alternative — a
289
- * subclass carrying the capability — puts it in every server built on Pikku
290
- * whether or not the app ever uses it. Scenarios are the one implementation
291
- * today (`PikkuScenarioService` in `@pikku/core/scenario`).
292
- */
293
240
  export interface WorkflowRunExtension {
294
- /** Per-run state resolved once the run has an id. */
295
241
  attachRunContext(
296
242
  runId: string,
297
243
  workflowMeta: any,
298
244
  options?: Record<string, any>
299
245
  ): Promise<void>
300
- /** Release whatever `attachRunContext` stored. */
301
246
  detachRunContext(runId: string): void
302
- /** Contribute extra members to the wire a run's function is invoked with. */
303
247
  decorateRunWire(
304
248
  wire: PikkuWire,
305
249
  context: {
@@ -308,7 +252,6 @@ export interface WorkflowRunExtension {
308
252
  workflowWire: PikkuWorkflowWire
309
253
  }
310
254
  ): void
311
- /** Contribute extra members to the workflow wire itself, in place. */
312
255
  decorateWorkflowWire(
313
256
  workflowWire: PikkuWorkflowWire,
314
257
  context: {
@@ -318,18 +261,7 @@ export interface WorkflowRunExtension {
318
261
  addonNamespace?: string | null
319
262
  }
320
263
  ): void
321
- /**
322
- * Called immediately before the run's function, inside the run lock. Throwing
323
- * skips the function and fails the run.
324
- */
325
264
  onBeforeRunFunc(context: RunLifecycleContext): Promise<void>
326
- /**
327
- * Called once the run reaches a terminal state, in a `finally`, so it runs
328
- * whether the function passed or threw. `interrupted` means the run suspended
329
- * or went async and is still mid-flight. Throwing here propagates, replacing
330
- * a pending failure, so an extension that must not mask one handles that
331
- * itself.
332
- */
333
265
  onAfterRunFunc(
334
266
  context: RunLifecycleContext,
335
267
  outcome: 'completed' | 'failed' | 'interrupted',
@@ -337,54 +269,25 @@ export interface WorkflowRunExtension {
337
269
  ): Promise<void>
338
270
  }
339
271
 
340
- /**
341
- * States a run never leaves. `suspended` is deliberately absent: a suspended
342
- * run stops a poll loop but can still be resumed, so anything the process holds
343
- * for it has to survive.
344
- */
345
272
  const WORKFLOW_TERMINAL_STATES: ReadonlySet<string> = new Set([
346
273
  'completed',
347
274
  'failed',
348
275
  'cancelled',
349
276
  ])
350
277
 
351
- /** First wait when polling a run, before the backoff starts widening it. */
352
278
  const WORKFLOW_POLL_MIN_MS = 10
353
279
 
354
- /** How much each successive wait grows, up to the caller's ceiling. */
355
280
  const WORKFLOW_POLL_FACTOR = 1.6
356
281
 
357
- /**
358
- * Ceiling for the wait on an inline sub-workflow. Lower than a top-level run's
359
- * default, because the parent step is blocked on it and every wait here is
360
- * added latency in the middle of a workflow rather than at its edge.
361
- */
362
282
  const WORKFLOW_CHILD_POLL_MAX_MS = 500
363
283
 
364
- /**
365
- * Abstract workflow state service
366
- * Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
367
- * Combines orchestration and step execution
368
- */
369
- /**
370
- * Everything the engine holds in memory for a run that is executing in this
371
- * process. One entry, one lifetime: created when the run starts executing here
372
- * and dropped when nothing is holding it open any more.
373
- *
374
- * The `replay` half is rebuilt from scratch on every orchestrator tick; the
375
- * rest outlives individual ticks and belongs to whoever started the run.
376
- */
377
284
  type RunContext = {
378
- /** Executing straight through in-process, without a queue. */
379
- inline: boolean
285
+ activeExecutions: number
286
+ inline?: boolean
287
+ ordinals: Map<string, number>
288
+ lastStep?: string
380
289
  replay?: {
381
- /** How many times this walk has reached each logical step name. */
382
- ordinals: Map<string, number>
383
- /** The step key the walk last reached — the next step's predecessor. */
384
- lastStep?: string
385
- /** Every step of the run as this replay found it, keyed by step name. */
386
290
  steps?: Map<string, StepState>
387
- /** The run as this replay found it. Only its immutable half is reused. */
388
291
  run?: WorkflowRun
389
292
  }
390
293
  }
@@ -397,20 +300,39 @@ export abstract class PikkuWorkflowService implements WorkflowService {
397
300
  private contextFor(runId: string): RunContext {
398
301
  let context = this.runContexts.get(runId)
399
302
  if (!context) {
400
- context = { inline: false }
303
+ context = { activeExecutions: 0, ordinals: new Map() }
401
304
  this.runContexts.set(runId, context)
402
305
  }
403
306
  return context
404
307
  }
405
308
 
406
- /** Drop a run's context once nothing is holding it open. */
407
309
  private releaseContext(runId: string): void {
408
310
  const context = this.runContexts.get(runId)
409
311
  if (!context) return
410
- if (context.inline || context.replay) return
312
+ if (context.activeExecutions > 0) return
411
313
  this.runContexts.delete(runId)
412
314
  }
413
315
 
316
+ private enterExecution(runId: string): RunContext {
317
+ const context = this.contextFor(runId)
318
+ context.activeExecutions++
319
+ return context
320
+ }
321
+
322
+ private exitExecution(runId: string): void {
323
+ const context = this.runContexts.get(runId)
324
+ if (!context) return
325
+ if (context.activeExecutions > 0) {
326
+ context.activeExecutions--
327
+ }
328
+ if (context.activeExecutions === 0) {
329
+ context.replay = undefined
330
+ context.ordinals = new Map()
331
+ context.lastStep = undefined
332
+ }
333
+ this.releaseContext(runId)
334
+ }
335
+
414
336
  protected get logger() {
415
337
  return getSingletonServices()?.logger
416
338
  }
@@ -437,17 +359,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
437
359
  }
438
360
  }
439
361
 
440
- /**
441
- * Perform a state write, then shadow it to the mirror.
442
- *
443
- * The mirror is an observability sink, never a second source of truth, and
444
- * both halves of that follow from this one shape: it is only ever told about
445
- * a write that already landed, and a mirror that is down or throwing cannot
446
- * fail — or even be seen by — the workflow it is watching.
447
- *
448
- * @param write - the authoritative write; its result is what the caller gets
449
- * @param mirror - shadows the write, given the live mirror and what was written
450
- */
451
362
  private async mirrored<T>(
452
363
  write: () => Promise<T>,
453
364
  mirror: (mirror: WorkflowRunMirror, written: T) => Promise<void>
@@ -462,24 +373,17 @@ export abstract class PikkuWorkflowService implements WorkflowService {
462
373
  `[pikku] WorkflowRunMirror write failed: ${err?.message ?? err}`
463
374
  )
464
375
  } catch {
465
- // logger unavailable (e.g. singleton services not initialized) — swallow
376
+ // knowledge: decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md
466
377
  }
467
378
  }
468
379
  }
469
380
  return written
470
381
  }
471
382
 
472
- /**
473
- * Wire the queue-based orchestrator/step/sleeper workers.
474
- * Subclasses that orchestrate without queues (e.g. Durable Objects) should
475
- * pass `wireQueues: false` to the base constructor and skip this entirely.
476
- * Call this explicitly after adding addons dynamically.
477
- */
478
383
  public wireQueueWorkers(): void {
479
384
  const functions = pikkuState(null, 'function', 'functions')
480
385
  const functionsMeta = pikkuState(null, 'function', 'meta')
481
386
 
482
- // Minimal meta for internal workflow functions (satisfies FunctionMeta)
483
387
  const mkMeta = (funcId: string) => ({
484
388
  pikkuFuncId: funcId,
485
389
  sessionless: true,
@@ -507,10 +411,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
507
411
  }
508
412
  }
509
413
 
510
- // Under 'shared-groups' every workflow runs through these two queues and is
511
- // kept from hogging them by the per-group cap, so the per-workflow queues
512
- // below are left unconsumed — one set of pollers for the whole system
513
- // instead of one per workflow.
514
414
  const sharedGroups = this.queueStrategy === 'shared-groups'
515
415
  const sharedQueueConfig: PikkuWorkerConfig | undefined = sharedGroups
516
416
  ? {
@@ -519,7 +419,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
519
419
  }
520
420
  : undefined
521
421
 
522
- // Register shared queue workers for monolith deployments
523
422
  registerWorkflowFunc(
524
423
  'pikkuWorkflowOrchestrator',
525
424
  { func: pikkuWorkflowOrchestratorFunc },
@@ -533,7 +432,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
533
432
  sharedQueueConfig
534
433
  )
535
434
 
536
- // Register per-workflow queue workers (root + addon packages)
537
435
  const registerQueueWorkers = (queueMeta: Record<string, any>) => {
538
436
  for (const [queueName, meta] of Object.entries(queueMeta)) {
539
437
  if (functions.has(meta.pikkuFuncId)) continue
@@ -567,13 +465,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
567
465
  }
568
466
  }
569
467
 
570
- // Workflows exist but no per-workflow orchestrator queue was registered:
571
- // the generated queue meta never reached the runtime (most often the
572
- // bootstrap doesn't import the queue-workers meta, so `queue.meta` is
573
- // empty). Everything still "works" — dispatch silently falls back to the
574
- // single shared orchestrator queue — but the isolation is gone: one slow
575
- // workflow step head-of-line-blocks every other workflow behind it. That
576
- // is invisible until a queue starves, so say so loudly at wiring time.
577
468
  const workflowCount = Object.keys(
578
469
  pikkuState(null, 'workflows', 'meta') ?? {}
579
470
  ).length
@@ -598,27 +489,26 @@ export abstract class PikkuWorkflowService implements WorkflowService {
598
489
  }
599
490
  }
600
491
 
601
- /**
602
- * Check if a run is executing inline (without queues)
603
- */
604
- protected isInline(runId: string): boolean {
605
- return this.runContexts.get(runId)?.inline === true
492
+ protected async isInline(runId: string): Promise<boolean> {
493
+ const context = this.runContexts.get(runId)
494
+ if (context?.inline !== undefined) {
495
+ return context.inline
496
+ }
497
+ const inline = (await this.getRunIdentity(runId))?.inline === true
498
+ if (context) {
499
+ context.inline = inline
500
+ }
501
+ return inline
606
502
  }
607
503
 
608
- /**
609
- * Register a run as inline (for graph-runner to use)
610
- */
611
504
  public registerInlineRun(runId: string): void {
612
505
  this.contextFor(runId).inline = true
613
506
  }
614
507
 
615
- /**
616
- * Unregister a run from inline tracking
617
- */
618
508
  public unregisterInlineRun(runId: string): void {
619
509
  const context = this.runContexts.get(runId)
620
510
  if (!context) return
621
- context.inline = false
511
+ context.inline = undefined
622
512
  this.releaseContext(runId)
623
513
  }
624
514
 
@@ -676,17 +566,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
676
566
  }
677
567
  ): Promise<string>
678
568
 
679
- /**
680
- * Get a workflow run by ID
681
- * @param id - Run ID
682
- * @returns Workflow run or null if not found
683
- */
684
569
  abstract getRun(id: string): Promise<WorkflowRun | null>
685
570
 
686
- /**
687
- * Get minimal workflow run status with step summaries.
688
- * Used by the public API — the console addon provides the full verbose view.
689
- */
690
571
  async getRunStatus(id: string): Promise<WorkflowRunStatus | null> {
691
572
  const run = await this.getRun(id)
692
573
  if (!run) return null
@@ -694,7 +575,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
694
575
  const history = await this.getRunHistory(id)
695
576
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
696
577
 
697
- // Build step summaries from history (latest attempt per step)
698
578
  const stepMap = new Map<
699
579
  string,
700
580
  {
@@ -741,24 +621,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
741
621
  }
742
622
  }
743
623
 
744
- /**
745
- * Build the run's time-travel event stream from durable history.
746
- * @param id - Run ID
747
- * @returns Ordered timeline, or null if the run doesn't exist
748
- */
749
624
  public async getRunTimeline(id: string): Promise<RunTimeline | null> {
750
625
  const run = await this.getRun(id)
751
626
  if (!run) return null
752
627
  return buildRunTimeline(await this.getRunHistory(id))
753
628
  }
754
629
 
755
- /**
756
- * Reconstruct the run's state at a point in its timeline.
757
- * @param id - Run ID
758
- * @param at - A seq index (inclusive) or a Date (inclusive); omit for the
759
- * final state.
760
- * @returns Reconstructed state, or null if the run doesn't exist
761
- */
762
630
  public async reconstructRunStateAt(
763
631
  id: string,
764
632
  at?: number | Date
@@ -768,20 +636,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
768
636
  return reconstructStateAt(timeline, at ?? timeline.length - 1)
769
637
  }
770
638
 
771
- /**
772
- * Get workflow run history (all step attempts in chronological order)
773
- * @param runId - Run ID
774
- * @returns Array of step states with step names, ordered oldest to newest
775
- */
776
639
  abstract getRunHistory(
777
640
  runId: string
778
641
  ): Promise<Array<StepState & { stepName: string }>>
779
642
 
780
- /**
781
- * Update workflow run status
782
- * @param id - Run ID
783
- * @param status - New status
784
- */
785
643
  public async updateRunStatus(
786
644
  id: string,
787
645
  status: WorkflowStatus,
@@ -793,9 +651,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
793
651
  (mirror) => mirror.updateRunStatus(id, status, output, error)
794
652
  )
795
653
  if (WORKFLOW_TERMINAL_STATES.has(status)) {
796
- // The run is over: release whatever this process opened for it. Queued
797
- // runs never pass through the inline path that does this, so their
798
- // context was held for the life of the process.
799
654
  this.runExtension?.detachRunContext(id)
800
655
  this.releaseContext(id)
801
656
  }
@@ -808,19 +663,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
808
663
  error?: SerializedError
809
664
  ): Promise<void>
810
665
 
811
- /**
812
- * Insert initial step state (called by orchestrator)
813
- * Creates pending step in both workflow_step and workflow_step_history
814
- * @param runId - Run ID
815
- * @param stepName - Step cache key
816
- * @param rpcName - The name this step was dispatched by: an RPC for a
817
- * `workflow.do` step, a step function for a scenario step, null for a
818
- * closure. Nothing dispatches off this value — it is recorded so a reader
819
- * can join a step back to the function that ran it.
820
- * @param data - Step input data
821
- * @param stepOptions - Step options (retries, retryDelay)
822
- * @returns Step state with generated stepId
823
- */
824
666
  public async insertStepState(
825
667
  runId: string,
826
668
  stepName: string,
@@ -853,19 +695,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
853
695
  fromStepName?: string
854
696
  ): Promise<StepState>
855
697
 
856
- /**
857
- * Get step state by cache key (read-only)
858
- * @param runId - Run ID
859
- * @param stepName - Step cache key (from workflow.do)
860
- * @returns Step state with attemptCount calculated from history
861
- */
862
698
  abstract getStepState(runId: string, stepName: string): Promise<StepState>
863
699
 
864
- /**
865
- * Mark step as running
866
- * Updates both workflow_step and workflow_step_history
867
- * @param stepId - Step ID
868
- */
869
700
  public async setStepRunning(stepId: string): Promise<void> {
870
701
  await this.mirrored(
871
702
  () => this.setStepRunningImpl(stepId),
@@ -875,11 +706,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
875
706
 
876
707
  protected abstract setStepRunningImpl(stepId: string): Promise<void>
877
708
 
878
- /**
879
- * Mark step as scheduled (queued for execution)
880
- * Updates both workflow_step and workflow_step_history
881
- * @param stepId - Step ID
882
- */
883
709
  public async setStepScheduled(stepId: string): Promise<void> {
884
710
  await this.mirrored(
885
711
  () => this.setStepScheduledImpl(stepId),
@@ -889,12 +715,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
889
715
 
890
716
  protected abstract setStepScheduledImpl(stepId: string): Promise<void>
891
717
 
892
- /**
893
- * Store step result and mark as succeeded
894
- * Updates both workflow_step and workflow_step_history
895
- * @param stepId - Step ID
896
- * @param result - Step result
897
- */
898
718
  public async setStepResult(stepId: string, result: any): Promise<void> {
899
719
  await this.mirrored(
900
720
  () => this.setStepResultImpl(stepId, result),
@@ -907,11 +727,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
907
727
  result: any
908
728
  ): Promise<void>
909
729
 
910
- /**
911
- * Set the child workflow run ID on a step
912
- * @param stepId - Step ID
913
- * @param childRunId - Child workflow run ID
914
- */
915
730
  public async setStepChildRunId(
916
731
  stepId: string,
917
732
  childRunId: string
@@ -927,12 +742,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
927
742
  childRunId: string
928
743
  ): Promise<void>
929
744
 
930
- /**
931
- * Store step error and mark as failed
932
- * Updates both workflow_step and workflow_step_history
933
- * @param stepId - Step ID
934
- * @param error - Error object
935
- */
936
745
  public async setStepError(stepId: string, error: Error): Promise<void> {
937
746
  await this.mirrored(
938
747
  () => this.setStepErrorImpl(stepId, error),
@@ -953,14 +762,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
953
762
  error: Error
954
763
  ): Promise<void>
955
764
 
956
- /**
957
- * Create a new retry attempt for a failed step
958
- * Inserts new pending step in both workflow_step and workflow_step_history
959
- * Resets status to 'pending' with new stepId
960
- * Copies metadata (rpcName, data, retries, retryDelay) from failed attempt
961
- * @param failedStepId - Failed step ID to copy from
962
- * @returns New step state for the retry attempt
963
- */
964
765
  public async createRetryAttempt(
965
766
  failedStepId: string,
966
767
  status: 'pending' | 'running'
@@ -980,64 +781,22 @@ export abstract class PikkuWorkflowService implements WorkflowService {
980
781
  status: 'pending' | 'running'
981
782
  ): Promise<StepState>
982
783
 
983
- /**
984
- * Execute function within a run lock to prevent concurrent modifications
985
- * @param id - Run ID
986
- * @param fn - Function to execute
987
- * @returns Function result
988
- */
989
784
  abstract withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>
990
785
 
991
- /**
992
- * Execute function within a step lock to prevent concurrent step execution
993
- * @param runId - Run ID
994
- * @param stepName - Step name
995
- * @param fn - Function to execute
996
- * @returns Function result
997
- */
998
786
  abstract withStepLock<T>(
999
787
  runId: string,
1000
788
  stepName: string,
1001
789
  fn: () => Promise<T>
1002
790
  ): Promise<T>
1003
791
 
1004
- /**
1005
- * Close any open connections
1006
- */
1007
792
  abstract close(): Promise<void>
1008
793
 
1009
- // ============================================================================
1010
- // Workflow Graph Methods
1011
- // ============================================================================
1012
-
1013
- /**
1014
- * Get completed graph state (lightweight - no results)
1015
- * @param runId - Run ID
1016
- * @returns Completed node IDs and their branch keys
1017
- */
1018
794
  abstract getCompletedGraphState(runId: string): Promise<{
1019
795
  completedNodeIds: string[]
1020
796
  failedNodeIds: string[]
1021
797
  branchKeys: Record<string, string>
1022
798
  }>
1023
799
 
1024
- /**
1025
- * Filter candidate nodes to only those without existing steps
1026
- * @param runId - Run ID
1027
- * @param nodeIds - Candidate node IDs to check
1028
- * @returns Node IDs that don't have a step yet
1029
- */
1030
- abstract getNodesWithoutSteps(
1031
- runId: string,
1032
- nodeIds: string[]
1033
- ): Promise<string[]>
1034
-
1035
- /**
1036
- * List every step instance of a run (any status) with its predecessor.
1037
- * Drives bounded graph revisits: the runner counts instances per logical node
1038
- * and treats each `fromStepName → node` as a once-fired transition.
1039
- * @param runId - Run ID
1040
- */
1041
800
  abstract getStepInstances(runId: string): Promise<
1042
801
  Array<{
1043
802
  stepName: string
@@ -1046,22 +805,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1046
805
  }>
1047
806
  >
1048
807
 
1049
- /**
1050
- * Get results for specific nodes
1051
- * @param runId - Run ID
1052
- * @param nodeIds - Node IDs to fetch results for
1053
- * @returns Map of nodeId to result
1054
- */
1055
808
  abstract getNodeResults(
1056
809
  runId: string,
1057
810
  nodeIds: string[]
1058
811
  ): Promise<Record<string, any>>
1059
812
 
1060
- /**
1061
- * Set the branch key for a graph node step
1062
- * @param stepId - Step ID
1063
- * @param branchKey - Branch key selected by graph.branch()
1064
- */
1065
813
  public async setBranchTaken(
1066
814
  stepId: string,
1067
815
  branchKey: string
@@ -1077,12 +825,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1077
825
  branchKey: string
1078
826
  ): Promise<void>
1079
827
 
1080
- /**
1081
- * Update a state variable in the workflow run's state
1082
- * @param runId - Run ID
1083
- * @param name - Variable name
1084
- * @param value - Value to store
1085
- */
1086
828
  public async updateRunState(
1087
829
  runId: string,
1088
830
  name: string,
@@ -1100,11 +842,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1100
842
  value: unknown
1101
843
  ): Promise<void>
1102
844
 
1103
- /**
1104
- * Get the entire state object for a workflow run
1105
- * @param runId - Run ID
1106
- * @returns The state object with all variables
1107
- */
1108
845
  abstract getRunState(runId: string): Promise<Record<string, unknown>>
1109
846
 
1110
847
  public async upsertWorkflowVersion(
@@ -1152,14 +889,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1152
889
  graphHash: string
1153
890
  ): Promise<{ graph: any; source: string } | null>
1154
891
 
1155
- // ============================================================================
1156
- // Workflow Lifecycle Methods
1157
- // ============================================================================
1158
-
1159
- /**
1160
- * Resume a paused workflow by triggering the orchestrator
1161
- * @param runId - Run ID
1162
- */
1163
892
  public async resumeWorkflow(
1164
893
  runId: string,
1165
894
  workflowName?: string
@@ -1169,12 +898,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1169
898
  const run = await this.getRun(runId)
1170
899
  workflowName = run?.workflow
1171
900
  }
1172
- // Carry an explicit retry policy on the orchestrator job too. Orchestrator
1173
- // runs are idempotent (they replay from the snapshot, returning cached step
1174
- // results), so redelivery is always safe — and it's what lets a transient
1175
- // dispatch/infra failure recover: the job is rethrown and retried instead of
1176
- // the run hanging. Passing `attempts` per-job overrides the queue default, so
1177
- // this holds even when the orchestrator queue is configured `retry_limit 0`.
1178
901
  await queueService.add(
1179
902
  this.getOrchestratorQueueName(workflowName),
1180
903
  { runId },
@@ -1185,22 +908,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1185
908
  )
1186
909
  }
1187
910
 
1188
- /**
1189
- * Resolve a step's retry policy into queue job options. The workflow is the
1190
- * sole source of truth for retries: an explicitly-set `retries` (including 0)
1191
- * is always honored, an unset one defaults to {@link DEFAULT_STEP_RETRIES},
1192
- * and we ALWAYS pass `attempts` so the queue can never fall back to its own
1193
- * default — which would re-run a step the workflow said not to retry. Backoff
1194
- * defaults to exponential whenever there's at least one retry, so retries ride
1195
- * out a transient outage instead of firing instantly.
1196
- */
1197
911
  protected resolveStepJobOptions(
1198
912
  stepOptions?: WorkflowStepOptions
1199
913
  ): JobOptions {
1200
914
  const retries = stepOptions?.retries ?? DEFAULT_STEP_RETRIES
1201
915
  const retryDelay = stepOptions?.retryDelay
1202
- // A concrete retryDelay (15000, '15s') is a fixed backoff; only the literal
1203
- // 'exponential' — or no delay at all — selects exponential.
1204
916
  const backoff =
1205
917
  retryDelay !== undefined && retryDelay !== 'exponential'
1206
918
  ? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
@@ -1224,18 +936,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1224
936
  { runId, stepName, rpcName, data, fromStepName },
1225
937
  {
1226
938
  ...this.resolveStepJobOptions(stepOptions),
1227
- // Group by step function, mirroring how per-step queues split them —
1228
- // one slow step function can't monopolise the shared step worker.
1229
939
  group: this.getJobGroup(rpcName),
1230
940
  }
1231
941
  )
1232
942
  }
1233
943
 
1234
- /**
1235
- * Execute a workflow sleep step completion
1236
- * Sets the step result to null and resumes the workflow
1237
- * @param data - Sleeper input data
1238
- */
1239
944
  public async executeWorkflowSleepCompleted(
1240
945
  runId: string,
1241
946
  stepId: string
@@ -1244,11 +949,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1244
949
  await this.resumeWorkflow(runId)
1245
950
  }
1246
951
 
1247
- /**
1248
- * Schedule orchestrator retry with delay
1249
- * @param runId - Run ID
1250
- * @param retryDelay - Delay in milliseconds or duration string (optional)
1251
- */
1252
952
  protected async scheduleOrchestratorRetry(
1253
953
  runId: string,
1254
954
  retryDelay?: number | string,
@@ -1271,20 +971,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1271
971
  )
1272
972
  }
1273
973
 
1274
- /**
1275
- * Dispatch a workflow step to be executed asynchronously.
1276
- *
1277
- * Default implementation enqueues a step worker job via the queue service.
1278
- * Subclasses with non-queue transports (e.g. Durable Objects) override this
1279
- * to dispatch via their own mechanism (RPC to a step worker, etc.).
1280
- *
1281
- * On return, the workflow is paused via `WorkflowAsyncException` thrown by
1282
- * the caller; the step transport is responsible for calling back into the
1283
- * orchestrator (via `resumeWorkflow` or equivalent) when the step completes.
1284
- *
1285
- * @returns true if dispatch was async (caller should pause), false to fall
1286
- * through to the inline execution path.
1287
- */
1288
974
  protected async dispatchStep(
1289
975
  runId: string,
1290
976
  stepName: string,
@@ -1293,10 +979,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1293
979
  stepOptions?: WorkflowStepOptions,
1294
980
  fromStepName?: string
1295
981
  ): Promise<boolean> {
1296
- // Step execution is decided purely by the function's `workflowQueued` flag
1297
- // (default false). Only a function explicitly marked `workflowQueued: true`
1298
- // dispatches via the queue. If the queue service is not configured that is
1299
- // a hard error — there is no inline fallback.
1300
982
  const functionsMeta = pikkuState(null, 'function', 'meta')
1301
983
  const rpcFuncId = pikkuState(null, 'rpc', 'meta')[rpcName]
1302
984
  const rpcMeta =
@@ -1320,31 +1002,20 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1320
1002
  }
1321
1003
  )
1322
1004
  } catch (cause) {
1323
- // The queue is down/unreachable — NOT a step failure. Surface it as a
1324
- // transient dispatch error so the caller leaves the step `pending` and the
1325
- // orchestrator job is retried (replayed from snapshot) rather than the run
1326
- // being marked failed. `add` already throws on failure in every adapter.
1327
1005
  throw new WorkflowDispatchException(runId, stepName, { cause })
1328
1006
  }
1329
1007
  return true
1330
1008
  }
1331
1009
 
1332
- /**
1333
- * Schedule a workflow sleep wakeup at the given duration.
1334
- *
1335
- * Default implementation uses the scheduler service to enqueue a delayed
1336
- * sleeper RPC. Subclasses with native timer primitives (e.g. Durable Object
1337
- * alarms) override this to schedule directly without going through queues.
1338
- *
1339
- * @returns true if the wakeup was scheduled remotely (caller should pause),
1340
- * false to fall through to inline `setTimeout` behavior.
1341
- */
1342
1010
  protected async scheduleSleep(
1343
1011
  runId: string,
1344
1012
  stepId: string,
1345
1013
  duration: number | string
1346
1014
  ): Promise<boolean> {
1347
- if (this.isInline(runId) || !getSingletonServices()?.schedulerService) {
1015
+ if (
1016
+ (await this.isInline(runId)) ||
1017
+ !getSingletonServices()?.schedulerService
1018
+ ) {
1348
1019
  return false
1349
1020
  }
1350
1021
  await getSingletonServices()!.schedulerService!.scheduleRPC(
@@ -1355,12 +1026,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1355
1026
  return true
1356
1027
  }
1357
1028
 
1358
- /**
1359
- * Install the one extension a run may have, built from a handle onto the run
1360
- * engine so that `inlineStep` and friends stay protected rather than becoming
1361
- * public API. Returns the extension, so the caller keeps a typed reference to
1362
- * whatever it just built.
1363
- */
1364
1029
  public setRunExtension<T extends WorkflowRunExtension>(
1365
1030
  create: (engine: WorkflowRunEngine) => T
1366
1031
  ): T {
@@ -1379,15 +1044,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1379
1044
  return this.runExtension
1380
1045
  }
1381
1046
 
1382
- /**
1383
- * Start a new workflow run
1384
- * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
1385
- * @param options.inline - If true, execute workflow directly without queue service
1386
- * @param options.startNode - Starting node ID for graph workflows (from wire config)
1387
- * @param options.onRunCreated - Called with the run id the moment the run exists.
1388
- * An inline run that fails throws instead of returning, so this is the only
1389
- * way a caller can still read that run back — its steps, and which one failed.
1390
- */
1391
1047
  public async startWorkflow<I>(
1392
1048
  name: string,
1393
1049
  input: I,
@@ -1396,11 +1052,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1396
1052
  options?: {
1397
1053
  inline?: boolean
1398
1054
  startNode?: string
1399
- actors?: ScenarioActors
1055
+ actors?: ScenarioPersonas
1400
1056
  onRunCreated?: (runId: string) => void
1401
1057
  }
1402
1058
  ): Promise<{ runId: string }> {
1403
- // Resolve workflow from static meta (root or addon namespace)
1404
1059
  const resolved = resolveWorkflowMeta(name)
1405
1060
  const workflowMeta = resolved?.meta
1406
1061
  const packageName = resolved?.packageName ?? null
@@ -1424,7 +1079,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1424
1079
  )
1425
1080
  }
1426
1081
 
1427
- // DSL workflow - check registration exists
1428
1082
  const registrations = pikkuState(packageName, 'workflows', 'registrations')
1429
1083
  const workflow = registrations.get(resolved?.resolvedName ?? name)
1430
1084
 
@@ -1464,7 +1118,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1464
1118
  error.name !== 'WorkflowAsyncException' &&
1465
1119
  error.name !== 'WorkflowCancelledException' &&
1466
1120
  error.name !== 'WorkflowSuspendedException' &&
1467
- // Transient queue failure — leave the run resumable, don't fail it.
1468
1121
  error.name !== 'WorkflowDispatchException'
1469
1122
  ) {
1470
1123
  await this.updateRunStatus(runId, 'failed', undefined, {
@@ -1472,11 +1125,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1472
1125
  message: error.message,
1473
1126
  stack: error.stack,
1474
1127
  })
1475
- // An expected failure (a PikkuError, e.g. a build gate tripping) —
1476
- // its message is the whole story, so don't dump the stack. The
1477
- // `expected` flag survives the step-boundary rehydration that strips
1478
- // the class. Anything else is an uncaught/unexpected error: log it in
1479
- // full so the trace is there to debug.
1480
1128
  getSingletonServices()!.logger.error(
1481
1129
  `Workflow ${name} (run ${runId}) failed:`,
1482
1130
  isExpectedError(error) ? error.message : error
@@ -1484,8 +1132,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1484
1132
  throw error
1485
1133
  }
1486
1134
  if (error.name === 'WorkflowDispatchException') {
1487
- // Rethrow so the caller (poll loop / starter) sees the transient
1488
- // failure; the run stays running and can be resumed.
1489
1135
  throw error
1490
1136
  }
1491
1137
  } finally {
@@ -1522,15 +1168,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1522
1168
  return run.output
1523
1169
  }
1524
1170
 
1525
- /**
1526
- * Read a run until it reaches an end state, backing off as it drags on.
1527
- *
1528
- * A fixed interval is wrong at both ends: it makes a workflow that finished
1529
- * in milliseconds wait out the whole interval anyway, and it keeps reading a
1530
- * long-running one at full rate for as long as it lasts. Starting short and
1531
- * growing to `maxIntervalMs` returns quick runs promptly while a slow run's
1532
- * read cost grows logarithmically rather than linearly with its duration.
1533
- */
1534
1171
  protected async awaitRunEnd(
1535
1172
  runId: string,
1536
1173
  maxIntervalMs: number
@@ -1549,41 +1186,21 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1549
1186
  }
1550
1187
  }
1551
1188
 
1552
- /**
1553
- * Wait between two reads of a run.
1554
- *
1555
- * Its own method so the backoff schedule can be asserted on directly. Timing
1556
- * a poll loop by the clock measures the host's scheduler as much as the
1557
- * policy — `setTimeout(40)` routinely returns late on a loaded runner — which
1558
- * makes the obvious test both slow and flaky.
1559
- */
1560
1189
  protected async waitBeforeNextRead(ms: number): Promise<void> {
1561
1190
  await new Promise((resolve) => setTimeout(resolve, ms))
1562
1191
  }
1563
1192
 
1564
- /**
1565
- * Every step of a run in one read, or `null` if this backend has no bulk read.
1566
- *
1567
- * A replay walks the DSL body from the top, and each step it passes asks for
1568
- * its own row — so a run of N steps costs N reads per replay and O(N^2) over
1569
- * its lifetime. Backends that can answer this in a single query collapse that
1570
- * to one read per replay.
1571
- */
1572
1193
  protected async listStepStates(
1573
1194
  _runId: string
1574
1195
  ): Promise<Array<StepState & { stepName: string }> | null> {
1575
1196
  return null
1576
1197
  }
1577
1198
 
1578
- /**
1579
- * Begin a replay pass: fresh ordinal counters, and one read of the steps the
1580
- * run has already taken so the walk back to where it left off is served from
1581
- * memory. Safe because a pass reaches each step key at most once, and the
1582
- * steps it replays past are `succeeded` and therefore immutable.
1583
- */
1584
1199
  private async beginReplay(runId: string): Promise<void> {
1585
- const context = this.contextFor(runId)
1586
- context.replay = { ordinals: new Map() }
1200
+ const context = this.enterExecution(runId)
1201
+ context.ordinals = new Map()
1202
+ context.lastStep = undefined
1203
+ context.replay = {}
1587
1204
  const steps = await this.listStepStates(runId)
1588
1205
  if (steps) {
1589
1206
  context.replay.steps = new Map(steps.map((step) => [step.stepName, step]))
@@ -1591,16 +1208,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1591
1208
  }
1592
1209
 
1593
1210
  private endReplay(runId: string): void {
1594
- const context = this.runContexts.get(runId)
1595
- if (!context) return
1596
- context.replay = undefined
1597
- this.releaseContext(runId)
1211
+ this.exitExecution(runId)
1598
1212
  }
1599
1213
 
1600
- /**
1601
- * The step row for `stepName`, creating it if the run has not reached it
1602
- * before. Served from the replay snapshot when one is loaded.
1603
- */
1604
1214
  private async loadOrCreateStep(
1605
1215
  runId: string,
1606
1216
  stepName: string,
@@ -1616,7 +1226,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1616
1226
  try {
1617
1227
  return await this.getStepState(runId, stepName)
1618
1228
  } catch {
1619
- // No row yet — fall through and create it.
1229
+ // knowledge: decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md
1620
1230
  }
1621
1231
  }
1622
1232
 
@@ -1624,9 +1234,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1624
1234
  try {
1625
1235
  step = await create()
1626
1236
  } catch (error) {
1627
- // A concurrent replay of this run created the row after the snapshot was
1628
- // taken. Its state is the truth; if it isn't really there, the insert
1629
- // failed for its own reasons and that error is the one worth seeing.
1630
1237
  try {
1631
1238
  step = await this.getStepState(runId, stepName)
1632
1239
  } catch {
@@ -1637,15 +1244,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1637
1244
  return step
1638
1245
  }
1639
1246
 
1640
- /**
1641
- * The run's immutable half — which workflow it is, the wire it was started
1642
- * on, its input. `getRun` is otherwise called several times per step for
1643
- * answers that were all fixed at creation, so a replay reads it once and
1644
- * hands the same object to everyone who only needs that half.
1645
- *
1646
- * Anyone who needs `status`, `output`, `error` or `state` must call `getRun`:
1647
- * those move while the run executes, and a cached copy would be a lie.
1648
- */
1649
1247
  private async getRunIdentity(runId: string): Promise<WorkflowRun | null> {
1650
1248
  const replay = this.runContexts.get(runId)?.replay
1651
1249
  if (replay?.run) {
@@ -1658,33 +1256,21 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1658
1256
  return run
1659
1257
  }
1660
1258
 
1661
- /** The step the DSL walk last reached (the predecessor for the next step). */
1662
1259
  private lastStepName(runId: string): string | undefined {
1663
- return this.runContexts.get(runId)?.replay?.lastStep
1260
+ return this.runContexts.get(runId)?.lastStep
1664
1261
  }
1665
1262
 
1666
- /**
1667
- * Physical, replay-stable key for the Nth reach of `logicalStepName` in a run:
1668
- * bare name for the first reach (ordinal 0, unchanged behavior), `name#N` for
1669
- * repeats — so the same literal step name can be invoked multiple times without
1670
- * the rows clobbering. Deterministic given a deterministic DSL body.
1671
- */
1672
1263
  private nextStepKey(runId: string, logicalStepName: string): string {
1673
1264
  const context = this.contextFor(runId)
1674
- const replay: NonNullable<RunContext['replay']> = (context.replay ??= {
1675
- ordinals: new Map(),
1676
- })
1677
- const ordinal = replay.ordinals.get(logicalStepName) ?? 0
1678
- replay.ordinals.set(logicalStepName, ordinal + 1)
1265
+ const ordinal = context.ordinals.get(logicalStepName) ?? 0
1266
+ context.ordinals.set(logicalStepName, ordinal + 1)
1679
1267
  const stepName =
1680
1268
  ordinal === 0 ? logicalStepName : `${logicalStepName}#${ordinal}`
1681
- replay.lastStep = stepName
1269
+ context.lastStep = stepName
1682
1270
  return stepName
1683
1271
  }
1684
1272
 
1685
1273
  public async runWorkflowJob(runId: string, rpcService: any): Promise<void> {
1686
- // Fresh ordinal counters per replay so step keys are deterministic, and one
1687
- // read of the steps the run has already taken.
1688
1274
  await this.beginReplay(runId)
1689
1275
  try {
1690
1276
  await this.runWorkflowJobInner(runId, rpcService)
@@ -1697,8 +1283,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1697
1283
  runId: string,
1698
1284
  rpcService: any
1699
1285
  ): Promise<void> {
1700
- // Caches the run for the rest of this replay, so the steps it walks don't
1701
- // each re-read the workflow name and wire it already has.
1702
1286
  const run = await this.getRunIdentity(runId)
1703
1287
  if (!run) {
1704
1288
  throw new WorkflowRunNotFoundError(runId)
@@ -1772,9 +1356,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1772
1356
  packageName: pkgName,
1773
1357
  }
1774
1358
 
1775
- // `interrupted` means the run has not reached a terminal state — it is
1776
- // suspended or waiting — so teardown would run while the run is still
1777
- // mid-flight.
1778
1359
  let outcome: 'completed' | 'failed' | 'interrupted' = 'completed'
1779
1360
  let failure: any
1780
1361
  try {
@@ -1896,10 +1477,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1896
1477
  await runFromMeta(this, run.id, version.graph, rpcService)
1897
1478
  }
1898
1479
 
1899
- /**
1900
- * Execute a single workflow step (called by worker)
1901
- * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
1902
- */
1903
1480
  public async executeWorkflowStep(
1904
1481
  runId: string,
1905
1482
  stepName: string,
@@ -1907,19 +1484,32 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1907
1484
  data: any,
1908
1485
  rpcService: any
1909
1486
  ): Promise<void> {
1910
- // Claim the step under the lock ONLY (atomic check-and-mark-running). Do NOT
1911
- // hold the advisory lock — and its pooled connection — across execution: once
1912
- // a step is 'running' the guard below makes any concurrent worker return
1913
- // early, so the work + result persistence run with the lock released. Holding
1914
- // the lock across executeGraphStep (network I/O + more pool queries) let
1915
- // concurrent steps exhaust the connection pool and self-deadlock.
1487
+ this.enterExecution(runId)
1488
+ try {
1489
+ await this.executeWorkflowStepInner(
1490
+ runId,
1491
+ stepName,
1492
+ rpcName,
1493
+ data,
1494
+ rpcService
1495
+ )
1496
+ } finally {
1497
+ this.exitExecution(runId)
1498
+ }
1499
+ }
1500
+
1501
+ private async executeWorkflowStepInner(
1502
+ runId: string,
1503
+ stepName: string,
1504
+ rpcName: string,
1505
+ data: any,
1506
+ rpcService: any
1507
+ ): Promise<void> {
1916
1508
  const claimed = await this.withStepLock(runId, stepName, async () => {
1917
1509
  const stepState = await this.getStepState(runId, stepName)
1918
- // Already succeeded, or already claimed by another worker — nothing to do.
1919
1510
  if (stepState.status === 'succeeded' || stepState.status === 'running') {
1920
1511
  return null
1921
1512
  }
1922
- // A 'failed' status means this is a retry — start a fresh 'running' attempt.
1923
1513
  if (stepState.status === 'failed') {
1924
1514
  return this.createRetryAttempt(stepState.stepId, 'running')
1925
1515
  }
@@ -1929,7 +1519,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1929
1519
  return stepState
1930
1520
  })
1931
1521
 
1932
- // Nothing to execute: already succeeded, or another worker owns this step.
1933
1522
  if (!claimed) {
1934
1523
  return
1935
1524
  }
@@ -1947,8 +1536,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1947
1536
  const workflowMeta = meta[run.workflow]
1948
1537
 
1949
1538
  const isGraphWorkflow = workflowMeta?.source === 'graph'
1950
- // Map the physical step key back to its logical node: a revisit instance
1951
- // is `node#N` (ordinal), which isn't a literal key in `nodes`.
1952
1539
  let graphNodeId: string | undefined
1953
1540
  if (isGraphWorkflow && workflowMeta?.nodes) {
1954
1541
  if (stepName in workflowMeta.nodes) {
@@ -1971,7 +1558,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1971
1558
  run.workflow
1972
1559
  )
1973
1560
  } else {
1974
- // Check if rpcName refers to a sub-workflow
1975
1561
  const subWorkflowMeta = meta[rpcName]
1976
1562
  if (subWorkflowMeta) {
1977
1563
  const childWire: WorkflowRunWire = {
@@ -2019,10 +1605,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2019
1605
  }
2020
1606
  }
2021
1607
 
2022
- // Store result and mark succeeded
2023
1608
  await this.setStepResult(stepState.stepId, result)
2024
1609
 
2025
- // Resume orchestrator to continue workflow
2026
1610
  await this.resumeWorkflow(runId)
2027
1611
  } catch (error: any) {
2028
1612
  if (error instanceof ChildWorkflowStartedException) {
@@ -2041,32 +1625,24 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2041
1625
  return
2042
1626
  }
2043
1627
 
2044
- // Store error and mark failed
2045
1628
  await this.setStepError(stepState.stepId, error)
2046
1629
 
2047
1630
  const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1
2048
1631
  const retriesExhausted = stepState.attemptCount >= maxAttempts
2049
1632
 
2050
1633
  if (retriesExhausted) {
2051
- // No more retries - resume orchestrator to mark workflow as failed
2052
1634
  await this.resumeWorkflow(runId)
2053
1635
  }
2054
1636
 
2055
- // Always throw so queue knows the job failed and can retry if needed
2056
1637
  throw error
2057
1638
  }
2058
1639
  }
2059
1640
 
2060
- /**
2061
- * Orchestrate workflow execution (called by orchestrator)
2062
- * Runs workflow job and handles async exceptions
2063
- */
2064
1641
  public async orchestrateWorkflow(
2065
1642
  runId: string,
2066
1643
  rpcService: any
2067
1644
  ): Promise<void> {
2068
1645
  try {
2069
- // Run workflow job (replays with caching)
2070
1646
  await this.runWorkflowJob(runId, rpcService)
2071
1647
  } catch (error: any) {
2072
1648
  if (
@@ -2078,9 +1654,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2078
1654
  }
2079
1655
 
2080
1656
  if (error.name === 'WorkflowDispatchException') {
2081
- // Transient: the queue was unreachable, not a workflow failure. Leave the
2082
- // run running and rethrow so the orchestrator job is redelivered and the
2083
- // workflow replays from its snapshot. Do NOT mark the run failed.
2084
1657
  getSingletonServices()!.logger.warn(
2085
1658
  `Workflow run ${runId} could not dispatch a step (queue unavailable); leaving run for orchestrator retry`,
2086
1659
  error
@@ -2108,11 +1681,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2108
1681
  return getSingletonServices()!.queueService!
2109
1682
  }
2110
1683
 
2111
- /**
2112
- * Invoke a step's RPC with the workflow-step wire (step identity + provenance).
2113
- * Identical for the queue executor and the inline executor — the only thing
2114
- * that differs between transports is who calls it, not the call itself.
2115
- */
2116
1684
  private async invokeStepRpc(
2117
1685
  runId: string,
2118
1686
  stepName: string,
@@ -2122,8 +1690,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2122
1690
  rpcService: any,
2123
1691
  knownRun?: WorkflowRun | null
2124
1692
  ): Promise<any> {
2125
- // Carry the run's pikkuUserId onto the step wire so authed steps rehydrate their
2126
- // session on the queued path too (the bare job wire lacks it; inline already has it).
2127
1693
  const run = knownRun ?? (await this.getRunIdentity(runId))
2128
1694
  return rpcService.rpcWithWire(rpcName, data, {
2129
1695
  ...(run?.wire?.pikkuUserId ? { pikkuUserId: run.wire.pikkuUserId } : {}),
@@ -2139,16 +1705,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2139
1705
  })
2140
1706
  }
2141
1707
 
2142
- /**
2143
- * Inline (straight-through) step execution with an in-process retry loop —
2144
- * shared by inline RPC steps and inline function steps. Same scaffolding
2145
- * (running → result, or fail → retry-attempt → backoff → retry) wrapped
2146
- * around a step-specific `doWork` body. Stays O(K): no suspend/replay.
2147
- *
2148
- * `onError` is an optional hook for terminal errors that must NOT retry
2149
- * (e.g. RPC-not-found → suspend the run for redeploy). If it throws, the
2150
- * loop exits immediately without recording a step error or retrying.
2151
- */
2152
1708
  private async runInlineRetryLoop(
2153
1709
  stepState: StepState,
2154
1710
  retries: number,
@@ -2166,11 +1722,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2166
1722
  } catch (error: any) {
2167
1723
  if (onError) await onError(error)
2168
1724
 
2169
- // Record the error (marks step as failed)
2170
1725
  await this.setStepError(currentStepState.stepId, error)
2171
1726
 
2172
1727
  if (currentStepState.attemptCount < retries) {
2173
- // Create a new pending retry attempt, then back off if configured.
2174
1728
  currentStepState = await this.createRetryAttempt(
2175
1729
  currentStepState.stepId,
2176
1730
  'pending'
@@ -2180,22 +1734,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2180
1734
  setTimeout(resolve, getDurationInMilliseconds(retryDelay))
2181
1735
  )
2182
1736
  }
2183
- // Continue loop to retry
2184
1737
  } else {
2185
- // No more retries, fail the workflow
2186
1738
  throw error
2187
1739
  }
2188
1740
  }
2189
1741
  }
2190
1742
  }
2191
1743
 
2192
- /**
2193
- * Run a failed step's compensation handler as a durable step of its own.
2194
- *
2195
- * Durable rather than a bare invoke so a replay does not compensate twice —
2196
- * a handler is typically a refund or a rollback. `onError` is deliberately
2197
- * not forwarded: a compensation handler cannot itself compensate.
2198
- */
2199
1744
  private async runStepCompensation(
2200
1745
  runId: string,
2201
1746
  stepName: string,
@@ -2221,20 +1766,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2221
1766
  rpcService: any,
2222
1767
  stepOptions?: WorkflowStepOptions
2223
1768
  ): Promise<any> {
2224
- // Capture the predecessor before nextStepKey advances the lineage to us.
2225
1769
  const fromStepName = this.lastStepName(runId)
2226
1770
  const stepName = this.nextStepKey(runId, logicalStepName)
2227
- // Resolve the retry policy ONCE here so the value persisted on the step
2228
- // (which drives `retriesExhausted`) is the same one the queue dispatch turns
2229
- // into `attempts`. Without this the queue could retry N times while the
2230
- // engine thinks retries are already exhausted (or vice-versa).
2231
1771
  const resolvedStepOptions: WorkflowStepOptions = {
2232
1772
  retries: stepOptions?.retries ?? DEFAULT_STEP_RETRIES,
2233
1773
  retryDelay: stepOptions?.retryDelay,
2234
1774
  actor: stepOptions?.actor,
2235
1775
  onError: stepOptions?.onError,
2236
1776
  }
2237
- // Reuse the step if the run already reached it, otherwise create it.
2238
1777
  const stepState = await this.loadOrCreateStep(runId, stepName, () =>
2239
1778
  this.insertStepState(
2240
1779
  runId,
@@ -2247,18 +1786,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2247
1786
  )
2248
1787
 
2249
1788
  if (stepState.status === 'succeeded') {
2250
- // Return cached result
2251
1789
  return stepState.result
2252
1790
  }
2253
1791
 
2254
1792
  if (stepState.status === 'failed') {
2255
- // Step failed with retries exhausted - throw error to fail the workflow
2256
1793
  const error = new Error(
2257
1794
  stepState.error?.message ||
2258
1795
  `Step '${stepName}' failed after exhausting all retries`
2259
1796
  )
2260
- // Compensation, mirroring a graph node's onError: run the handler, then
2261
- // still throw — the workflow fails either way.
2262
1797
  if (resolvedStepOptions.onError) {
2263
1798
  await this.runStepCompensation(
2264
1799
  runId,
@@ -2268,7 +1803,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2268
1803
  error
2269
1804
  )
2270
1805
  }
2271
- // Preserve original error properties if available
2272
1806
  if (stepState.error) {
2273
1807
  Object.assign(error, stepState.error)
2274
1808
  }
@@ -2276,19 +1810,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2276
1810
  }
2277
1811
 
2278
1812
  if (stepState.status === 'scheduled') {
2279
- // Step is already scheduled, pause workflow
2280
1813
  throw new WorkflowAsyncException(runId, stepName)
2281
1814
  }
2282
1815
 
2283
- // Hand off to subclass-overridable transport. Default behavior enqueues
2284
- // via the queue service; DO-style subclasses RPC to a step worker.
2285
- // Dispatch BEFORE marking the step `scheduled`: if the queue is down,
2286
- // dispatchStep throws WorkflowDispatchException and the step stays `pending`,
2287
- // so the orchestrator's next replay re-dispatches it. Marking `scheduled`
2288
- // first would strand the step (replay sees `scheduled`, pauses, never
2289
- // re-enqueues the job that was never created).
2290
- // Actor steps never queue: they are outbound HTTP calls made by the
2291
- // runner itself, and the actor's session lives on this process.
2292
1816
  const dispatched = resolvedStepOptions.actor
2293
1817
  ? false
2294
1818
  : await this.dispatchStep(
@@ -2304,8 +1828,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2304
1828
  throw new WorkflowAsyncException(runId, stepName)
2305
1829
  }
2306
1830
 
2307
- // Inline (no transport available) - execute locally with the shared retry
2308
- // loop. The body resolves to a sub-workflow result or a plain RPC result.
2309
1831
  const retries = resolvedStepOptions.retries ?? this.getConfig().retries
2310
1832
  const retryDelay = resolvedStepOptions.retryDelay
2311
1833
 
@@ -2314,13 +1836,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2314
1836
  retries,
2315
1837
  retryDelay,
2316
1838
  async (currentStepState) => {
2317
- // Actor step: send through the actor's authenticated client over the
2318
- // REAL transport. Never falls back to internal dispatch — that would
2319
- // bypass auth and fake a green health check.
2320
1839
  if (resolvedStepOptions.actor) {
2321
1840
  return resolvedStepOptions.actor.invoke(rpcName, data)
2322
1841
  }
2323
- // Check if the name refers to a workflow
2324
1842
  const workflowMeta = pikkuState(null, 'workflows', 'meta')[rpcName]
2325
1843
  if (workflowMeta) {
2326
1844
  const childWire = {
@@ -2337,7 +1855,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2337
1855
  { inline: true }
2338
1856
  )
2339
1857
  await this.setStepChildRunId(currentStepState.stepId, childRunId)
2340
- // Poll until child workflow completes
2341
1858
  const childRun = await this.awaitRunEnd(
2342
1859
  childRunId,
2343
1860
  WORKFLOW_CHILD_POLL_MAX_MS
@@ -2376,25 +1893,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2376
1893
  logicalStepName: string,
2377
1894
  fn: Function,
2378
1895
  stepOptions?: WorkflowStepOptions,
2379
- /**
2380
- * The input this step was called with, recorded on the run so a reporter can
2381
- * name the values under test. A closure step has none; a scenario step does.
2382
- */
2383
1896
  data: any = null,
2384
- /**
2385
- * The name this step was dispatched by, for the kinds of inline step that
2386
- * have one. A closure step has no name; a scenario step is a step RPC, so
2387
- * it records the step function that ran — which is the only way to join a
2388
- * step back to its declaration when its durable name was built at runtime
2389
- * (a step called in a loop reaches the run as `sees @pikku/addon-todos`,
2390
- * declared as `sees ${packageName}`).
2391
- */
2392
1897
  rpcName: string | null = null
2393
1898
  ): Promise<any> {
2394
1899
  const fromStepName = this.lastStepName(runId)
2395
1900
  const stepName = this.nextStepKey(runId, logicalStepName)
2396
- // Reuse the step if the run already reached it, otherwise create it
2397
- // (inline, so never dispatched).
2398
1901
  const stepState = await this.loadOrCreateStep(runId, stepName, () =>
2399
1902
  this.insertStepState(
2400
1903
  runId,
@@ -2407,20 +1910,15 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2407
1910
  )
2408
1911
 
2409
1912
  if (stepState.status === 'succeeded') {
2410
- // Return cached result
2411
1913
  return stepState.result
2412
1914
  }
2413
1915
 
2414
- // Execute inline function
2415
1916
  const retries = stepOptions?.retries ?? this.getConfig().retries
2416
1917
  const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay
2417
1918
 
2418
- // Check if we're running inline (in-memory) or remote (queue-based)
2419
- if (this.isInline(runId)) {
2420
- // Inline mode - execute with the shared in-process retry loop.
1919
+ if (await this.isInline(runId)) {
2421
1920
  return this.runInlineRetryLoop(stepState, retries, retryDelay, () => fn())
2422
1921
  } else {
2423
- // Remote mode - single attempt, then suspend for orchestrator-driven retry.
2424
1922
  let currentStepState = stepState
2425
1923
  try {
2426
1924
  await this.setStepRunning(currentStepState.stepId)
@@ -2428,22 +1926,16 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2428
1926
  await this.setStepResult(currentStepState.stepId, result)
2429
1927
  return result
2430
1928
  } catch (error: any) {
2431
- // Record the error (marks step as failed)
2432
1929
  await this.setStepError(currentStepState.stepId, error)
2433
1930
 
2434
- // Check if we should retry
2435
1931
  if (currentStepState.attemptCount < retries) {
2436
- // Create a new pending retry attempt (copies metadata from failed step)
2437
1932
  await this.createRetryAttempt(currentStepState.stepId, 'pending')
2438
1933
 
2439
- // Schedule orchestrator to retry after delay
2440
1934
  await this.scheduleOrchestratorRetry(runId, retryDelay)
2441
1935
 
2442
- // Pause workflow - orchestrator will replay and pick up new attempt
2443
1936
  throw new WorkflowAsyncException(runId, stepName)
2444
1937
  }
2445
1938
 
2446
- // No more retries, fail the workflow
2447
1939
  throw error
2448
1940
  }
2449
1941
  }
@@ -2456,8 +1948,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2456
1948
  ) {
2457
1949
  const fromStepName = this.lastStepName(runId)
2458
1950
  const stepName = this.nextStepKey(runId, logicalStepName)
2459
- // Reuse the step if the run already reached it, otherwise create it
2460
- // (sleep step, no RPC).
2461
1951
  const stepState = await this.loadOrCreateStep(runId, stepName, () =>
2462
1952
  this.insertStepState(
2463
1953
  runId,
@@ -2470,20 +1960,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2470
1960
  )
2471
1961
 
2472
1962
  if (stepState.status === 'succeeded') {
2473
- // Sleep already completed, return immediately
2474
1963
  return
2475
1964
  }
2476
1965
 
2477
1966
  if (stepState.status === 'scheduled') {
2478
- // Sleep is already scheduled, pause workflow
2479
1967
  throw new WorkflowAsyncException(runId, stepName)
2480
1968
  }
2481
1969
 
2482
- // Hand off to subclass-overridable transport. Default behavior schedules
2483
- // a delayed sleeper RPC via the scheduler service; DO-style subclasses
2484
- // override to use native timer primitives (e.g. setAlarm). Schedule BEFORE
2485
- // marking `scheduled` so a scheduler outage leaves the step `pending` for
2486
- // re-scheduling on replay instead of stranding it (see rpcStep).
2487
1970
  let scheduled: boolean
2488
1971
  try {
2489
1972
  scheduled = await this.scheduleSleep(runId, stepState.stepId, duration)
@@ -2495,24 +1978,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2495
1978
  throw new WorkflowAsyncException(runId, stepName)
2496
1979
  }
2497
1980
 
2498
- // Inline mode - use setTimeout with actual duration
2499
1981
  await new Promise((resolve) =>
2500
1982
  setTimeout(resolve, getDurationInMilliseconds(duration))
2501
1983
  )
2502
1984
  await this.setStepResult(stepState.stepId, null)
2503
1985
  }
2504
1986
 
2505
- /**
2506
- * Derive the durable step name for a suspend point from its `reason`, so each
2507
- * distinct reason is its own step row — letting one workflow have multiple
2508
- * independent suspends (e.g. wait-for-build, then wait-for-approval), and
2509
- * supporting dynamic reasons in loops (`suspend(`Wait for ${i}`)`) exactly
2510
- * like dynamic `do()` step names. The reason is used raw (it's just a text
2511
- * step name), only namespaced so it can't collide with a `do`/`sleep` step of
2512
- * the same name. Like `do()` / `sleep()`, the reason is the step's stable
2513
- * identity: it MUST be derived deterministically so it's the same every time
2514
- * the workflow replays through that point.
2515
- */
2516
1987
  private getSuspendStepName(reason: string): string {
2517
1988
  return `__workflow_suspend:${reason}`
2518
1989
  }
@@ -2568,16 +2039,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2568
2039
  })
2569
2040
  }
2570
2041
 
2571
- /**
2572
- * Wake a run later by enqueuing a delayed orchestrator pass. Deliberately NOT
2573
- * {@link scheduleSleep}: that resolves the step it is given, which for an
2574
- * approval would resolve the gate itself. This only nudges the run to replay
2575
- * and re-evaluate — the gate stays the sole judge of its own outcome.
2576
- *
2577
- * Best-effort by design. Expiry is decided from the recorded deadline on
2578
- * replay, so losing this wake costs liveness (the run sits until something
2579
- * else resumes it), never correctness.
2580
- */
2581
2042
  private async scheduleRunWake(runId: string, delay: number): Promise<void> {
2582
2043
  try {
2583
2044
  const queueService = this.verifyQueueService()
@@ -2600,21 +2061,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2600
2061
  }
2601
2062
  }
2602
2063
 
2603
- /**
2604
- * Durable step name for an approval gate. Namespaced separately from suspend
2605
- * so the two can't collide, and derived from `reason` for the same reason
2606
- * {@link getSuspendStepName} is: it must be stable across replays.
2607
- */
2608
2064
  private getApprovalStepName(reason: string): string {
2609
2065
  return `__workflow_approval:${reason}`
2610
2066
  }
2611
2067
 
2612
- /**
2613
- * Run-state key holding an approval gate's record. Hex-encoded because the
2614
- * Mongo backend restricts state keys to `/^[a-zA-Z0-9_]+$/` and a `reason` is
2615
- * arbitrary human text. One key per gate, so two gates resolving concurrently
2616
- * can't clobber each other through a read-modify-write.
2617
- */
2618
2068
  private approvalStateKey(stepName: string): string {
2619
2069
  let hex = ''
2620
2070
  for (const byte of new TextEncoder().encode(stepName)) {
@@ -2623,17 +2073,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2623
2073
  return `__approval_${hex}`
2624
2074
  }
2625
2075
 
2626
- /**
2627
- * Record a decision against an approval gate and wake the run. Called from
2628
- * outside the workflow (an HTTP route, an RPC), so the schema value is NOT in
2629
- * scope here — the payload is stored raw and validated on replay inside the
2630
- * workflow body, which is the only place the schema exists. An invalid payload
2631
- * therefore leaves the gate closed rather than failing the run.
2632
- *
2633
- * `reason` addresses the first reach of that gate. An approval reached more
2634
- * than once under the same reason (e.g. in a loop) gets `#N`-suffixed step
2635
- * rows that this cannot currently target.
2636
- */
2637
2076
  public async approveStep(
2638
2077
  runId: string,
2639
2078
  reason: string,
@@ -2642,16 +2081,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2642
2081
  const stepName = this.getApprovalStepName(reason)
2643
2082
  const stateKey = this.approvalStateKey(stepName)
2644
2083
 
2645
- // A resolved gate returns its cached step result and never re-reads state,
2646
- // so a decision recorded now would be silently discarded — most obviously
2647
- // when it loses the race with expiry. Reject instead, so the approver
2648
- // learns their decision did not land.
2649
2084
  let resolved: StepState | undefined
2650
2085
  try {
2651
2086
  resolved = await this.getStepState(runId, stepName)
2652
2087
  } catch {
2653
- // No step row yet: the run has not reached the gate. Recording a decision
2654
- // ahead of it is legitimate — the gate picks it up on arrival.
2088
+ // knowledge: decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md
2655
2089
  }
2656
2090
  if (resolved?.stepId && resolved.status === 'succeeded') {
2657
2091
  const outcome = resolved.result as ApprovalOutcome<unknown> | undefined
@@ -2707,8 +2141,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2707
2141
  )
2708
2142
  }
2709
2143
 
2710
- // Unlike suspend, `succeeded` here means a decision (or expiry) was
2711
- // actually resolved, and the step result IS the return channel.
2712
2144
  if (stepState.status === 'succeeded') {
2713
2145
  return stepState.result as ApprovalOutcome<unknown>
2714
2146
  }
@@ -2723,8 +2155,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2723
2155
 
2724
2156
  if (stepState.status === 'pending') {
2725
2157
  await this.setStepRunning(stepState.stepId)
2726
- // First reach: stamp the deadline and nudge the run awake when it
2727
- // passes. The deadline is what's authoritative — see below.
2728
2158
  if (options.expiry !== undefined && !record.expiresAt) {
2729
2159
  const expiresAt = new Date(
2730
2160
  Date.now() + getDurationInMilliseconds(options.expiry)
@@ -2743,9 +2173,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2743
2173
  record.decision
2744
2174
  )
2745
2175
  if (validation.issues) {
2746
- // Drop the bad decision and re-close the gate, leaving the failure
2747
- // legible to whoever tries next. Failing the run instead would let any
2748
- // caller kill a workflow with a malformed payload.
2749
2176
  await this.updateRunState(runId, stateKey, {
2750
2177
  ...record,
2751
2178
  decision: undefined,
@@ -2767,9 +2194,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2767
2194
  return outcome
2768
2195
  }
2769
2196
 
2770
- // Expiry is decided by comparing against the recorded deadline rather than
2771
- // by the timer having fired, so a duplicate, late, or dropped timer all
2772
- // produce the same answer.
2773
2197
  if (record.expiresAt && Date.now() >= Date.parse(record.expiresAt)) {
2774
2198
  const outcome: ApprovalOutcome<unknown> = { status: 'expired' }
2775
2199
  await this.setStepResult(stepState.stepId, outcome)
@@ -2791,7 +2215,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2791
2215
  runId,
2792
2216
  getRun: async () => (await this.getRun(runId)) as WorkflowRun,
2793
2217
 
2794
- // Implement workflow.do() - RPC form
2795
2218
  do: async (
2796
2219
  stepName: string,
2797
2220
  rpcNameOrFn: any,
@@ -2822,7 +2245,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2822
2245
  }
2823
2246
  },
2824
2247
 
2825
- // Implement workflow.sleep()
2826
2248
  sleep: async (stepName: string, duration: string | number) => {
2827
2249
  this.verifyStepName(stepName)
2828
2250
  await this.sleepStep(
@@ -2871,16 +2293,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2871
2293
  }
2872
2294
  }
2873
2295
 
2874
- /**
2875
- * Get the orchestrator queue name for a specific workflow.
2876
- * Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
2877
- * falls back to the shared orchestrator queue.
2878
- *
2879
- * Reads from `queue.meta` (always populated globally) rather than
2880
- * `queue.registrations` (only populated for queues this unit consumes).
2881
- * In a per-unit deploy the orchestrator unit doesn't consume per-step
2882
- * queues — but it produces to them — so registrations would miss them.
2883
- */
2884
2296
  protected getOrchestratorQueueName(workflowName?: string): string {
2885
2297
  if (workflowName && this.queueStrategy !== 'shared-groups') {
2886
2298
  const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`
@@ -2903,15 +2315,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2903
2315
  return this.getConfig().stepWorkerQueueName
2904
2316
  }
2905
2317
 
2906
- /**
2907
- * Fairness key for a job on a shared queue. Under `'per-workflow'` the queue
2908
- * name already isolates workflows, so no group is needed — returning one
2909
- * anyway would cap a workflow inside its own dedicated queue.
2910
- *
2911
- * The tier repeats the id so a workflow can be given its own limit purely
2912
- * from config, with no per-workflow wiring; an unmatched tier falls back to
2913
- * the default limit.
2914
- */
2915
2318
  protected getJobGroup(id?: string): JobGroup | undefined {
2916
2319
  if (!id || this.queueStrategy !== 'shared-groups') {
2917
2320
  return undefined