@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
@@ -34,17 +34,7 @@ import { RPCNotFoundError } from '../rpc/rpc-runner.js';
34
34
  import { ChildWorkflowStartedException } from './graph/graph-runner.js';
35
35
  import { deriveInvocationId } from './workflow-invocation-id.js';
36
36
  import { buildRunTimeline, reconstructStateAt, } from './run-timeline.js';
37
- /**
38
- * Default number of retries for a workflow step when none is specified. The
39
- * workflow — not the queue — owns retry policy; a step inherits this unless it
40
- * sets its own `retries` (including `retries: 0` to opt out entirely). Picked >0
41
- * so a transient failure (a DB blip, a downstream restart, a deploy) is ridden
42
- * out by default; safe because every step gets a stable `invocationId` to dedupe on.
43
- */
44
37
  export const DEFAULT_STEP_RETRIES = 5;
45
- /**
46
- * Exception thrown when workflow needs to pause for async step
47
- */
48
38
  export class WorkflowAsyncException extends Error {
49
39
  runId;
50
40
  stepName;
@@ -55,9 +45,6 @@ export class WorkflowAsyncException extends Error {
55
45
  this.name = 'WorkflowAsyncException';
56
46
  }
57
47
  }
58
- /**
59
- * Exception thrown when workflow is cancelled
60
- */
61
48
  export class WorkflowCancelledException extends Error {
62
49
  runId;
63
50
  reason;
@@ -68,9 +55,6 @@ export class WorkflowCancelledException extends Error {
68
55
  this.name = 'WorkflowCancelledException';
69
56
  }
70
57
  }
71
- /**
72
- * Exception thrown when workflow is suspended
73
- */
74
58
  export class WorkflowSuspendedException extends Error {
75
59
  runId;
76
60
  reason;
@@ -81,14 +65,6 @@ export class WorkflowSuspendedException extends Error {
81
65
  this.name = 'WorkflowSuspendedException';
82
66
  }
83
67
  }
84
- /**
85
- * Thrown when a step (or the orchestrator) could not be enqueued — the queue
86
- * itself failed (e.g. pg-boss is momentarily down), NOT the step's own logic.
87
- * This is transient infrastructure failure: the run is left untouched (the step
88
- * stays `pending`, the run stays running) and the orchestrator job is rethrown
89
- * so the queue redelivers it and the workflow replays from its snapshot. Treat
90
- * it as non-terminal — never mark the run `failed` for it.
91
- */
92
68
  export class WorkflowDispatchException extends Error {
93
69
  runId;
94
70
  stepName;
@@ -99,9 +75,6 @@ export class WorkflowDispatchException extends Error {
99
75
  this.name = 'WorkflowDispatchException';
100
76
  }
101
77
  }
102
- /**
103
- * Error class for workflow not found
104
- */
105
78
  export class WorkflowNotFoundError extends PikkuError {
106
79
  constructor(name) {
107
80
  super(`Workflow not found: ${name}`);
@@ -140,12 +113,6 @@ addError(WorkflowRunCancelledError, {
140
113
  status: 409,
141
114
  message: 'Workflow was cancelled.',
142
115
  });
143
- /**
144
- * A decision arrived for an approval gate that has already resolved. The gate
145
- * caches its outcome as the step result and never re-reads run state, so the
146
- * decision could not take effect — it is rejected rather than accepted and
147
- * dropped.
148
- */
149
116
  export class WorkflowApprovalResolvedError extends PikkuError {
150
117
  payload;
151
118
  constructor(reason, outcome) {
@@ -170,25 +137,13 @@ const WORKFLOW_END_STATES = new Set([
170
137
  'cancelled',
171
138
  'suspended',
172
139
  ]);
173
- /**
174
- * States a run never leaves. `suspended` is deliberately absent: a suspended
175
- * run stops a poll loop but can still be resumed, so anything the process holds
176
- * for it has to survive.
177
- */
178
140
  const WORKFLOW_TERMINAL_STATES = new Set([
179
141
  'completed',
180
142
  'failed',
181
143
  'cancelled',
182
144
  ]);
183
- /** First wait when polling a run, before the backoff starts widening it. */
184
145
  const WORKFLOW_POLL_MIN_MS = 10;
185
- /** How much each successive wait grows, up to the caller's ceiling. */
186
146
  const WORKFLOW_POLL_FACTOR = 1.6;
187
- /**
188
- * Ceiling for the wait on an inline sub-workflow. Lower than a top-level run's
189
- * default, because the parent step is blocked on it and every wait here is
190
- * added latency in the middle of a workflow rather than at its edge.
191
- */
192
147
  const WORKFLOW_CHILD_POLL_MAX_MS = 500;
193
148
  export class PikkuWorkflowService {
194
149
  runExtension;
@@ -196,20 +151,38 @@ export class PikkuWorkflowService {
196
151
  contextFor(runId) {
197
152
  let context = this.runContexts.get(runId);
198
153
  if (!context) {
199
- context = { inline: false };
154
+ context = { activeExecutions: 0, ordinals: new Map() };
200
155
  this.runContexts.set(runId, context);
201
156
  }
202
157
  return context;
203
158
  }
204
- /** Drop a run's context once nothing is holding it open. */
205
159
  releaseContext(runId) {
206
160
  const context = this.runContexts.get(runId);
207
161
  if (!context)
208
162
  return;
209
- if (context.inline || context.replay)
163
+ if (context.activeExecutions > 0)
210
164
  return;
211
165
  this.runContexts.delete(runId);
212
166
  }
167
+ enterExecution(runId) {
168
+ const context = this.contextFor(runId);
169
+ context.activeExecutions++;
170
+ return context;
171
+ }
172
+ exitExecution(runId) {
173
+ const context = this.runContexts.get(runId);
174
+ if (!context)
175
+ return;
176
+ if (context.activeExecutions > 0) {
177
+ context.activeExecutions--;
178
+ }
179
+ if (context.activeExecutions === 0) {
180
+ context.replay = undefined;
181
+ context.ordinals = new Map();
182
+ context.lastStep = undefined;
183
+ }
184
+ this.releaseContext(runId);
185
+ }
213
186
  get logger() {
214
187
  return getSingletonServices()?.logger;
215
188
  }
@@ -227,17 +200,6 @@ export class PikkuWorkflowService {
227
200
  this.wireQueueWorkers();
228
201
  }
229
202
  }
230
- /**
231
- * Perform a state write, then shadow it to the mirror.
232
- *
233
- * The mirror is an observability sink, never a second source of truth, and
234
- * both halves of that follow from this one shape: it is only ever told about
235
- * a write that already landed, and a mirror that is down or throwing cannot
236
- * fail — or even be seen by — the workflow it is watching.
237
- *
238
- * @param write - the authoritative write; its result is what the caller gets
239
- * @param mirror - shadows the write, given the live mirror and what was written
240
- */
241
203
  async mirrored(write, mirror) {
242
204
  const written = await write();
243
205
  if (this.mirror) {
@@ -249,22 +211,15 @@ export class PikkuWorkflowService {
249
211
  this.logger?.warn?.(`[pikku] WorkflowRunMirror write failed: ${err?.message ?? err}`);
250
212
  }
251
213
  catch {
252
- // logger unavailable (e.g. singleton services not initialized) — swallow
214
+ // knowledge: decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md
253
215
  }
254
216
  }
255
217
  }
256
218
  return written;
257
219
  }
258
- /**
259
- * Wire the queue-based orchestrator/step/sleeper workers.
260
- * Subclasses that orchestrate without queues (e.g. Durable Objects) should
261
- * pass `wireQueues: false` to the base constructor and skip this entirely.
262
- * Call this explicitly after adding addons dynamically.
263
- */
264
220
  wireQueueWorkers() {
265
221
  const functions = pikkuState(null, 'function', 'functions');
266
222
  const functionsMeta = pikkuState(null, 'function', 'meta');
267
- // Minimal meta for internal workflow functions (satisfies FunctionMeta)
268
223
  const mkMeta = (funcId) => ({
269
224
  pikkuFuncId: funcId,
270
225
  sessionless: true,
@@ -285,10 +240,6 @@ export class PikkuWorkflowService {
285
240
  functionsMeta[funcId] = mkMeta(funcId);
286
241
  }
287
242
  };
288
- // Under 'shared-groups' every workflow runs through these two queues and is
289
- // kept from hogging them by the per-group cap, so the per-workflow queues
290
- // below are left unconsumed — one set of pollers for the whole system
291
- // instead of one per workflow.
292
243
  const sharedGroups = this.queueStrategy === 'shared-groups';
293
244
  const sharedQueueConfig = sharedGroups
294
245
  ? {
@@ -296,10 +247,8 @@ export class PikkuWorkflowService {
296
247
  groupConcurrency: this.queueGroupConcurrency,
297
248
  }
298
249
  : undefined;
299
- // Register shared queue workers for monolith deployments
300
250
  registerWorkflowFunc('pikkuWorkflowOrchestrator', { func: pikkuWorkflowOrchestratorFunc }, 'pikku-workflow-orchestrator', sharedQueueConfig);
301
251
  registerWorkflowFunc('pikkuWorkflowStepWorker', { func: pikkuWorkflowWorkerFunc }, 'pikku-workflow-step-worker', sharedQueueConfig);
302
- // Register per-workflow queue workers (root + addon packages)
303
252
  const registerQueueWorkers = (queueMeta) => {
304
253
  for (const [queueName, meta] of Object.entries(queueMeta)) {
305
254
  if (functions.has(meta.pikkuFuncId))
@@ -324,13 +273,6 @@ export class PikkuWorkflowService {
324
273
  }
325
274
  }
326
275
  }
327
- // Workflows exist but no per-workflow orchestrator queue was registered:
328
- // the generated queue meta never reached the runtime (most often the
329
- // bootstrap doesn't import the queue-workers meta, so `queue.meta` is
330
- // empty). Everything still "works" — dispatch silently falls back to the
331
- // single shared orchestrator queue — but the isolation is gone: one slow
332
- // workflow step head-of-line-blocks every other workflow behind it. That
333
- // is invisible until a queue starves, so say so loudly at wiring time.
334
276
  const workflowCount = Object.keys(pikkuState(null, 'workflows', 'meta') ?? {}).length;
335
277
  const perWorkflowQueues = Object.keys(queueMeta).filter((name) => name.startsWith('wf-orchestrator-')).length;
336
278
  if (workflowCount > 0 && perWorkflowQueues === 0) {
@@ -347,26 +289,25 @@ export class PikkuWorkflowService {
347
289
  functionsMeta.pikkuWorkflowSleeper = mkMeta('pikkuWorkflowSleeper');
348
290
  }
349
291
  }
350
- /**
351
- * Check if a run is executing inline (without queues)
352
- */
353
- isInline(runId) {
354
- return this.runContexts.get(runId)?.inline === true;
292
+ async isInline(runId) {
293
+ const context = this.runContexts.get(runId);
294
+ if (context?.inline !== undefined) {
295
+ return context.inline;
296
+ }
297
+ const inline = (await this.getRunIdentity(runId))?.inline === true;
298
+ if (context) {
299
+ context.inline = inline;
300
+ }
301
+ return inline;
355
302
  }
356
- /**
357
- * Register a run as inline (for graph-runner to use)
358
- */
359
303
  registerInlineRun(runId) {
360
304
  this.contextFor(runId).inline = true;
361
305
  }
362
- /**
363
- * Unregister a run from inline tracking
364
- */
365
306
  unregisterInlineRun(runId) {
366
307
  const context = this.runContexts.get(runId);
367
308
  if (!context)
368
309
  return;
369
- context.inline = false;
310
+ context.inline = undefined;
370
311
  this.releaseContext(runId);
371
312
  }
372
313
  async registerWorkflowVersions() {
@@ -380,17 +321,12 @@ export class PikkuWorkflowService {
380
321
  async createRun(workflowName, input, inline, graphHash, wire, options) {
381
322
  return this.mirrored(() => this.createRunImpl(workflowName, input, inline, graphHash, wire, options), (mirror, runId) => mirror.createRun(runId, workflowName, input, inline, graphHash, wire, options));
382
323
  }
383
- /**
384
- * Get minimal workflow run status with step summaries.
385
- * Used by the public API — the console addon provides the full verbose view.
386
- */
387
324
  async getRunStatus(id) {
388
325
  const run = await this.getRun(id);
389
326
  if (!run)
390
327
  return null;
391
328
  const history = await this.getRunHistory(id);
392
329
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled']);
393
- // Build step summaries from history (latest attempt per step)
394
330
  const stepMap = new Map();
395
331
  for (const step of history) {
396
332
  const existing = stepMap.get(step.stepName);
@@ -425,100 +361,40 @@ export class PikkuWorkflowService {
425
361
  : undefined,
426
362
  };
427
363
  }
428
- /**
429
- * Build the run's time-travel event stream from durable history.
430
- * @param id - Run ID
431
- * @returns Ordered timeline, or null if the run doesn't exist
432
- */
433
364
  async getRunTimeline(id) {
434
365
  const run = await this.getRun(id);
435
366
  if (!run)
436
367
  return null;
437
368
  return buildRunTimeline(await this.getRunHistory(id));
438
369
  }
439
- /**
440
- * Reconstruct the run's state at a point in its timeline.
441
- * @param id - Run ID
442
- * @param at - A seq index (inclusive) or a Date (inclusive); omit for the
443
- * final state.
444
- * @returns Reconstructed state, or null if the run doesn't exist
445
- */
446
370
  async reconstructRunStateAt(id, at) {
447
371
  const timeline = await this.getRunTimeline(id);
448
372
  if (!timeline)
449
373
  return null;
450
374
  return reconstructStateAt(timeline, at ?? timeline.length - 1);
451
375
  }
452
- /**
453
- * Update workflow run status
454
- * @param id - Run ID
455
- * @param status - New status
456
- */
457
376
  async updateRunStatus(id, status, output, error) {
458
377
  await this.mirrored(() => this.updateRunStatusImpl(id, status, output, error), (mirror) => mirror.updateRunStatus(id, status, output, error));
459
378
  if (WORKFLOW_TERMINAL_STATES.has(status)) {
460
- // The run is over: release whatever this process opened for it. Queued
461
- // runs never pass through the inline path that does this, so their
462
- // context was held for the life of the process.
463
379
  this.runExtension?.detachRunContext(id);
464
380
  this.releaseContext(id);
465
381
  }
466
382
  }
467
- /**
468
- * Insert initial step state (called by orchestrator)
469
- * Creates pending step in both workflow_step and workflow_step_history
470
- * @param runId - Run ID
471
- * @param stepName - Step cache key
472
- * @param rpcName - The name this step was dispatched by: an RPC for a
473
- * `workflow.do` step, a step function for a scenario step, null for a
474
- * closure. Nothing dispatches off this value — it is recorded so a reader
475
- * can join a step back to the function that ran it.
476
- * @param data - Step input data
477
- * @param stepOptions - Step options (retries, retryDelay)
478
- * @returns Step state with generated stepId
479
- */
480
383
  async insertStepState(runId, stepName, rpcName, data, stepOptions, fromStepName) {
481
384
  return this.mirrored(() => this.insertStepStateImpl(runId, stepName, rpcName, data, stepOptions, fromStepName), (mirror, step) => mirror.insertStepState(runId, { ...step, stepName, rpcName, data }));
482
385
  }
483
- /**
484
- * Mark step as running
485
- * Updates both workflow_step and workflow_step_history
486
- * @param stepId - Step ID
487
- */
488
386
  async setStepRunning(stepId) {
489
387
  await this.mirrored(() => this.setStepRunningImpl(stepId), (mirror) => mirror.setStepRunning(stepId));
490
388
  }
491
- /**
492
- * Mark step as scheduled (queued for execution)
493
- * Updates both workflow_step and workflow_step_history
494
- * @param stepId - Step ID
495
- */
496
389
  async setStepScheduled(stepId) {
497
390
  await this.mirrored(() => this.setStepScheduledImpl(stepId), (mirror) => mirror.setStepScheduled(stepId));
498
391
  }
499
- /**
500
- * Store step result and mark as succeeded
501
- * Updates both workflow_step and workflow_step_history
502
- * @param stepId - Step ID
503
- * @param result - Step result
504
- */
505
392
  async setStepResult(stepId, result) {
506
393
  await this.mirrored(() => this.setStepResultImpl(stepId, result), (mirror) => mirror.setStepResult(stepId, result));
507
394
  }
508
- /**
509
- * Set the child workflow run ID on a step
510
- * @param stepId - Step ID
511
- * @param childRunId - Child workflow run ID
512
- */
513
395
  async setStepChildRunId(stepId, childRunId) {
514
396
  await this.mirrored(() => this.setStepChildRunIdImpl(stepId, childRunId), (mirror) => mirror.setStepChildRunId(stepId, childRunId));
515
397
  }
516
- /**
517
- * Store step error and mark as failed
518
- * Updates both workflow_step and workflow_step_history
519
- * @param stepId - Step ID
520
- * @param error - Error object
521
- */
522
398
  async setStepError(stepId, error) {
523
399
  await this.mirrored(() => this.setStepErrorImpl(stepId, error), (mirror) => {
524
400
  const serialized = {
@@ -530,34 +406,15 @@ export class PikkuWorkflowService {
530
406
  return mirror.setStepError(stepId, serialized);
531
407
  });
532
408
  }
533
- /**
534
- * Create a new retry attempt for a failed step
535
- * Inserts new pending step in both workflow_step and workflow_step_history
536
- * Resets status to 'pending' with new stepId
537
- * Copies metadata (rpcName, data, retries, retryDelay) from failed attempt
538
- * @param failedStepId - Failed step ID to copy from
539
- * @returns New step state for the retry attempt
540
- */
541
409
  async createRetryAttempt(failedStepId, status) {
542
410
  return this.mirrored(() => this.createRetryAttemptImpl(failedStepId, status), (mirror, newStep) => mirror.createRetryAttempt(failedStepId, {
543
411
  ...newStep,
544
412
  stepName: newStep.stepName ?? '',
545
413
  }));
546
414
  }
547
- /**
548
- * Set the branch key for a graph node step
549
- * @param stepId - Step ID
550
- * @param branchKey - Branch key selected by graph.branch()
551
- */
552
415
  async setBranchTaken(stepId, branchKey) {
553
416
  await this.mirrored(() => this.setBranchTakenImpl(stepId, branchKey), (mirror) => mirror.setBranchTaken(stepId, branchKey));
554
417
  }
555
- /**
556
- * Update a state variable in the workflow run's state
557
- * @param runId - Run ID
558
- * @param name - Variable name
559
- * @param value - Value to store
560
- */
561
418
  async updateRunState(runId, name, value) {
562
419
  await this.mirrored(() => this.updateRunStateImpl(runId, name, value), (mirror) => mirror.updateRunState(runId, name, value));
563
420
  }
@@ -567,44 +424,20 @@ export class PikkuWorkflowService {
567
424
  async updateWorkflowVersionStatus(name, graphHash, status) {
568
425
  await this.mirrored(() => this.updateWorkflowVersionStatusImpl(name, graphHash, status), (mirror) => mirror.updateWorkflowVersionStatus(name, graphHash, status));
569
426
  }
570
- // ============================================================================
571
- // Workflow Lifecycle Methods
572
- // ============================================================================
573
- /**
574
- * Resume a paused workflow by triggering the orchestrator
575
- * @param runId - Run ID
576
- */
577
427
  async resumeWorkflow(runId, workflowName) {
578
428
  const queueService = this.verifyQueueService();
579
429
  if (!workflowName) {
580
430
  const run = await this.getRun(runId);
581
431
  workflowName = run?.workflow;
582
432
  }
583
- // Carry an explicit retry policy on the orchestrator job too. Orchestrator
584
- // runs are idempotent (they replay from the snapshot, returning cached step
585
- // results), so redelivery is always safe — and it's what lets a transient
586
- // dispatch/infra failure recover: the job is rethrown and retried instead of
587
- // the run hanging. Passing `attempts` per-job overrides the queue default, so
588
- // this holds even when the orchestrator queue is configured `retry_limit 0`.
589
433
  await queueService.add(this.getOrchestratorQueueName(workflowName), { runId }, {
590
434
  ...this.resolveStepJobOptions(),
591
435
  group: this.getJobGroup(workflowName),
592
436
  });
593
437
  }
594
- /**
595
- * Resolve a step's retry policy into queue job options. The workflow is the
596
- * sole source of truth for retries: an explicitly-set `retries` (including 0)
597
- * is always honored, an unset one defaults to {@link DEFAULT_STEP_RETRIES},
598
- * and we ALWAYS pass `attempts` so the queue can never fall back to its own
599
- * default — which would re-run a step the workflow said not to retry. Backoff
600
- * defaults to exponential whenever there's at least one retry, so retries ride
601
- * out a transient outage instead of firing instantly.
602
- */
603
438
  resolveStepJobOptions(stepOptions) {
604
439
  const retries = stepOptions?.retries ?? DEFAULT_STEP_RETRIES;
605
440
  const retryDelay = stepOptions?.retryDelay;
606
- // A concrete retryDelay (15000, '15s') is a fixed backoff; only the literal
607
- // 'exponential' — or no delay at all — selects exponential.
608
441
  const backoff = retryDelay !== undefined && retryDelay !== 'exponential'
609
442
  ? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
610
443
  : retries > 0 || retryDelay === 'exponential'
@@ -616,25 +449,13 @@ export class PikkuWorkflowService {
616
449
  const queueService = this.verifyQueueService();
617
450
  await queueService.add(this.getStepWorkerQueueName(rpcName), { runId, stepName, rpcName, data, fromStepName }, {
618
451
  ...this.resolveStepJobOptions(stepOptions),
619
- // Group by step function, mirroring how per-step queues split them —
620
- // one slow step function can't monopolise the shared step worker.
621
452
  group: this.getJobGroup(rpcName),
622
453
  });
623
454
  }
624
- /**
625
- * Execute a workflow sleep step completion
626
- * Sets the step result to null and resumes the workflow
627
- * @param data - Sleeper input data
628
- */
629
455
  async executeWorkflowSleepCompleted(runId, stepId) {
630
456
  await this.setStepResult(stepId, null);
631
457
  await this.resumeWorkflow(runId);
632
458
  }
633
- /**
634
- * Schedule orchestrator retry with delay
635
- * @param runId - Run ID
636
- * @param retryDelay - Delay in milliseconds or duration string (optional)
637
- */
638
459
  async scheduleOrchestratorRetry(runId, retryDelay, workflowName) {
639
460
  const queueService = this.verifyQueueService();
640
461
  if (!workflowName) {
@@ -648,25 +469,7 @@ export class PikkuWorkflowService {
648
469
  group: this.getJobGroup(workflowName),
649
470
  });
650
471
  }
651
- /**
652
- * Dispatch a workflow step to be executed asynchronously.
653
- *
654
- * Default implementation enqueues a step worker job via the queue service.
655
- * Subclasses with non-queue transports (e.g. Durable Objects) override this
656
- * to dispatch via their own mechanism (RPC to a step worker, etc.).
657
- *
658
- * On return, the workflow is paused via `WorkflowAsyncException` thrown by
659
- * the caller; the step transport is responsible for calling back into the
660
- * orchestrator (via `resumeWorkflow` or equivalent) when the step completes.
661
- *
662
- * @returns true if dispatch was async (caller should pause), false to fall
663
- * through to the inline execution path.
664
- */
665
472
  async dispatchStep(runId, stepName, rpcName, data, stepOptions, fromStepName) {
666
- // Step execution is decided purely by the function's `workflowQueued` flag
667
- // (default false). Only a function explicitly marked `workflowQueued: true`
668
- // dispatches via the queue. If the queue service is not configured that is
669
- // a hard error — there is no inline fallback.
670
473
  const functionsMeta = pikkuState(null, 'function', 'meta');
671
474
  const rpcFuncId = pikkuState(null, 'rpc', 'meta')[rpcName];
672
475
  const rpcMeta = typeof rpcFuncId === 'string' ? functionsMeta[rpcFuncId] : undefined;
@@ -684,37 +487,18 @@ export class PikkuWorkflowService {
684
487
  });
685
488
  }
686
489
  catch (cause) {
687
- // The queue is down/unreachable — NOT a step failure. Surface it as a
688
- // transient dispatch error so the caller leaves the step `pending` and the
689
- // orchestrator job is retried (replayed from snapshot) rather than the run
690
- // being marked failed. `add` already throws on failure in every adapter.
691
490
  throw new WorkflowDispatchException(runId, stepName, { cause });
692
491
  }
693
492
  return true;
694
493
  }
695
- /**
696
- * Schedule a workflow sleep wakeup at the given duration.
697
- *
698
- * Default implementation uses the scheduler service to enqueue a delayed
699
- * sleeper RPC. Subclasses with native timer primitives (e.g. Durable Object
700
- * alarms) override this to schedule directly without going through queues.
701
- *
702
- * @returns true if the wakeup was scheduled remotely (caller should pause),
703
- * false to fall through to inline `setTimeout` behavior.
704
- */
705
494
  async scheduleSleep(runId, stepId, duration) {
706
- if (this.isInline(runId) || !getSingletonServices()?.schedulerService) {
495
+ if ((await this.isInline(runId)) ||
496
+ !getSingletonServices()?.schedulerService) {
707
497
  return false;
708
498
  }
709
499
  await getSingletonServices().schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, { runId, stepId });
710
500
  return true;
711
501
  }
712
- /**
713
- * Install the one extension a run may have, built from a handle onto the run
714
- * engine so that `inlineStep` and friends stay protected rather than becoming
715
- * public API. Returns the extension, so the caller keeps a typed reference to
716
- * whatever it just built.
717
- */
718
502
  setRunExtension(create) {
719
503
  const engine = {
720
504
  inlineStep: this.inlineStep.bind(this),
@@ -729,17 +513,7 @@ export class PikkuWorkflowService {
729
513
  getRunExtension() {
730
514
  return this.runExtension;
731
515
  }
732
- /**
733
- * Start a new workflow run
734
- * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
735
- * @param options.inline - If true, execute workflow directly without queue service
736
- * @param options.startNode - Starting node ID for graph workflows (from wire config)
737
- * @param options.onRunCreated - Called with the run id the moment the run exists.
738
- * An inline run that fails throws instead of returning, so this is the only
739
- * way a caller can still read that run back — its steps, and which one failed.
740
- */
741
516
  async startWorkflow(name, input, wire, rpcService, options) {
742
- // Resolve workflow from static meta (root or addon namespace)
743
517
  const resolved = resolveWorkflowMeta(name);
744
518
  const workflowMeta = resolved?.meta;
745
519
  const packageName = resolved?.packageName ?? null;
@@ -750,7 +524,6 @@ export class PikkuWorkflowService {
750
524
  const shouldInline = options?.inline || !getSingletonServices()?.queueService;
751
525
  return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
752
526
  }
753
- // DSL workflow - check registration exists
754
527
  const registrations = pikkuState(packageName, 'workflows', 'registrations');
755
528
  const workflow = registrations.get(resolved?.resolvedName ?? name);
756
529
  if (!workflow) {
@@ -775,24 +548,16 @@ export class PikkuWorkflowService {
775
548
  if (error.name !== 'WorkflowAsyncException' &&
776
549
  error.name !== 'WorkflowCancelledException' &&
777
550
  error.name !== 'WorkflowSuspendedException' &&
778
- // Transient queue failure — leave the run resumable, don't fail it.
779
551
  error.name !== 'WorkflowDispatchException') {
780
552
  await this.updateRunStatus(runId, 'failed', undefined, {
781
553
  name: error.name,
782
554
  message: error.message,
783
555
  stack: error.stack,
784
556
  });
785
- // An expected failure (a PikkuError, e.g. a build gate tripping) —
786
- // its message is the whole story, so don't dump the stack. The
787
- // `expected` flag survives the step-boundary rehydration that strips
788
- // the class. Anything else is an uncaught/unexpected error: log it in
789
- // full so the trace is there to debug.
790
557
  getSingletonServices().logger.error(`Workflow ${name} (run ${runId}) failed:`, isExpectedError(error) ? error.message : error);
791
558
  throw error;
792
559
  }
793
560
  if (error.name === 'WorkflowDispatchException') {
794
- // Rethrow so the caller (poll loop / starter) sees the transient
795
- // failure; the run stays running and can be resumed.
796
561
  throw error;
797
562
  }
798
563
  }
@@ -817,15 +582,6 @@ export class PikkuWorkflowService {
817
582
  }
818
583
  return run.output;
819
584
  }
820
- /**
821
- * Read a run until it reaches an end state, backing off as it drags on.
822
- *
823
- * A fixed interval is wrong at both ends: it makes a workflow that finished
824
- * in milliseconds wait out the whole interval anyway, and it keeps reading a
825
- * long-running one at full rate for as long as it lasts. Starting short and
826
- * growing to `maxIntervalMs` returns quick runs promptly while a slow run's
827
- * read cost grows logarithmically rather than linearly with its duration.
828
- */
829
585
  async awaitRunEnd(runId, maxIntervalMs) {
830
586
  let interval = Math.min(WORKFLOW_POLL_MIN_MS, maxIntervalMs);
831
587
  while (true) {
@@ -840,53 +596,25 @@ export class PikkuWorkflowService {
840
596
  interval = Math.min(interval * WORKFLOW_POLL_FACTOR, maxIntervalMs);
841
597
  }
842
598
  }
843
- /**
844
- * Wait between two reads of a run.
845
- *
846
- * Its own method so the backoff schedule can be asserted on directly. Timing
847
- * a poll loop by the clock measures the host's scheduler as much as the
848
- * policy — `setTimeout(40)` routinely returns late on a loaded runner — which
849
- * makes the obvious test both slow and flaky.
850
- */
851
599
  async waitBeforeNextRead(ms) {
852
600
  await new Promise((resolve) => setTimeout(resolve, ms));
853
601
  }
854
- /**
855
- * Every step of a run in one read, or `null` if this backend has no bulk read.
856
- *
857
- * A replay walks the DSL body from the top, and each step it passes asks for
858
- * its own row — so a run of N steps costs N reads per replay and O(N^2) over
859
- * its lifetime. Backends that can answer this in a single query collapse that
860
- * to one read per replay.
861
- */
862
602
  async listStepStates(_runId) {
863
603
  return null;
864
604
  }
865
- /**
866
- * Begin a replay pass: fresh ordinal counters, and one read of the steps the
867
- * run has already taken so the walk back to where it left off is served from
868
- * memory. Safe because a pass reaches each step key at most once, and the
869
- * steps it replays past are `succeeded` and therefore immutable.
870
- */
871
605
  async beginReplay(runId) {
872
- const context = this.contextFor(runId);
873
- context.replay = { ordinals: new Map() };
606
+ const context = this.enterExecution(runId);
607
+ context.ordinals = new Map();
608
+ context.lastStep = undefined;
609
+ context.replay = {};
874
610
  const steps = await this.listStepStates(runId);
875
611
  if (steps) {
876
612
  context.replay.steps = new Map(steps.map((step) => [step.stepName, step]));
877
613
  }
878
614
  }
879
615
  endReplay(runId) {
880
- const context = this.runContexts.get(runId);
881
- if (!context)
882
- return;
883
- context.replay = undefined;
884
- this.releaseContext(runId);
616
+ this.exitExecution(runId);
885
617
  }
886
- /**
887
- * The step row for `stepName`, creating it if the run has not reached it
888
- * before. Served from the replay snapshot when one is loaded.
889
- */
890
618
  async loadOrCreateStep(runId, stepName, create) {
891
619
  const snapshot = this.runContexts.get(runId)?.replay?.steps;
892
620
  if (snapshot) {
@@ -900,7 +628,7 @@ export class PikkuWorkflowService {
900
628
  return await this.getStepState(runId, stepName);
901
629
  }
902
630
  catch {
903
- // No row yet — fall through and create it.
631
+ // knowledge: decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md
904
632
  }
905
633
  }
906
634
  let step;
@@ -908,9 +636,6 @@ export class PikkuWorkflowService {
908
636
  step = await create();
909
637
  }
910
638
  catch (error) {
911
- // A concurrent replay of this run created the row after the snapshot was
912
- // taken. Its state is the truth; if it isn't really there, the insert
913
- // failed for its own reasons and that error is the one worth seeing.
914
639
  try {
915
640
  step = await this.getStepState(runId, stepName);
916
641
  }
@@ -921,15 +646,6 @@ export class PikkuWorkflowService {
921
646
  snapshot?.set(stepName, step);
922
647
  return step;
923
648
  }
924
- /**
925
- * The run's immutable half — which workflow it is, the wire it was started
926
- * on, its input. `getRun` is otherwise called several times per step for
927
- * answers that were all fixed at creation, so a replay reads it once and
928
- * hands the same object to everyone who only needs that half.
929
- *
930
- * Anyone who needs `status`, `output`, `error` or `state` must call `getRun`:
931
- * those move while the run executes, and a cached copy would be a lie.
932
- */
933
649
  async getRunIdentity(runId) {
934
650
  const replay = this.runContexts.get(runId)?.replay;
935
651
  if (replay?.run) {
@@ -941,30 +657,18 @@ export class PikkuWorkflowService {
941
657
  }
942
658
  return run;
943
659
  }
944
- /** The step the DSL walk last reached (the predecessor for the next step). */
945
660
  lastStepName(runId) {
946
- return this.runContexts.get(runId)?.replay?.lastStep;
947
- }
948
- /**
949
- * Physical, replay-stable key for the Nth reach of `logicalStepName` in a run:
950
- * bare name for the first reach (ordinal 0, unchanged behavior), `name#N` for
951
- * repeats — so the same literal step name can be invoked multiple times without
952
- * the rows clobbering. Deterministic given a deterministic DSL body.
953
- */
661
+ return this.runContexts.get(runId)?.lastStep;
662
+ }
954
663
  nextStepKey(runId, logicalStepName) {
955
664
  const context = this.contextFor(runId);
956
- const replay = (context.replay ??= {
957
- ordinals: new Map(),
958
- });
959
- const ordinal = replay.ordinals.get(logicalStepName) ?? 0;
960
- replay.ordinals.set(logicalStepName, ordinal + 1);
665
+ const ordinal = context.ordinals.get(logicalStepName) ?? 0;
666
+ context.ordinals.set(logicalStepName, ordinal + 1);
961
667
  const stepName = ordinal === 0 ? logicalStepName : `${logicalStepName}#${ordinal}`;
962
- replay.lastStep = stepName;
668
+ context.lastStep = stepName;
963
669
  return stepName;
964
670
  }
965
671
  async runWorkflowJob(runId, rpcService) {
966
- // Fresh ordinal counters per replay so step keys are deterministic, and one
967
- // read of the steps the run has already taken.
968
672
  await this.beginReplay(runId);
969
673
  try {
970
674
  await this.runWorkflowJobInner(runId, rpcService);
@@ -974,8 +678,6 @@ export class PikkuWorkflowService {
974
678
  }
975
679
  }
976
680
  async runWorkflowJobInner(runId, rpcService) {
977
- // Caches the run for the rest of this replay, so the steps it walks don't
978
- // each re-read the workflow name and wire it already has.
979
681
  const run = await this.getRunIdentity(runId);
980
682
  if (!run) {
981
683
  throw new WorkflowRunNotFoundError(runId);
@@ -1031,9 +733,6 @@ export class PikkuWorkflowService {
1031
733
  wire,
1032
734
  packageName: pkgName,
1033
735
  };
1034
- // `interrupted` means the run has not reached a terminal state — it is
1035
- // suspended or waiting — so teardown would run while the run is still
1036
- // mid-flight.
1037
736
  let outcome = 'completed';
1038
737
  let failure;
1039
738
  try {
@@ -1124,24 +823,21 @@ export class PikkuWorkflowService {
1124
823
  }
1125
824
  await runFromMeta(this, run.id, version.graph, rpcService);
1126
825
  }
1127
- /**
1128
- * Execute a single workflow step (called by worker)
1129
- * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
1130
- */
1131
826
  async executeWorkflowStep(runId, stepName, rpcName, data, rpcService) {
1132
- // Claim the step under the lock ONLY (atomic check-and-mark-running). Do NOT
1133
- // hold the advisory lock — and its pooled connection — across execution: once
1134
- // a step is 'running' the guard below makes any concurrent worker return
1135
- // early, so the work + result persistence run with the lock released. Holding
1136
- // the lock across executeGraphStep (network I/O + more pool queries) let
1137
- // concurrent steps exhaust the connection pool and self-deadlock.
827
+ this.enterExecution(runId);
828
+ try {
829
+ await this.executeWorkflowStepInner(runId, stepName, rpcName, data, rpcService);
830
+ }
831
+ finally {
832
+ this.exitExecution(runId);
833
+ }
834
+ }
835
+ async executeWorkflowStepInner(runId, stepName, rpcName, data, rpcService) {
1138
836
  const claimed = await this.withStepLock(runId, stepName, async () => {
1139
837
  const stepState = await this.getStepState(runId, stepName);
1140
- // Already succeeded, or already claimed by another worker — nothing to do.
1141
838
  if (stepState.status === 'succeeded' || stepState.status === 'running') {
1142
839
  return null;
1143
840
  }
1144
- // A 'failed' status means this is a retry — start a fresh 'running' attempt.
1145
841
  if (stepState.status === 'failed') {
1146
842
  return this.createRetryAttempt(stepState.stepId, 'running');
1147
843
  }
@@ -1150,7 +846,6 @@ export class PikkuWorkflowService {
1150
846
  }
1151
847
  return stepState;
1152
848
  });
1153
- // Nothing to execute: already succeeded, or another worker owns this step.
1154
849
  if (!claimed) {
1155
850
  return;
1156
851
  }
@@ -1164,8 +859,6 @@ export class PikkuWorkflowService {
1164
859
  const meta = pikkuState(null, 'workflows', 'meta');
1165
860
  const workflowMeta = meta[run.workflow];
1166
861
  const isGraphWorkflow = workflowMeta?.source === 'graph';
1167
- // Map the physical step key back to its logical node: a revisit instance
1168
- // is `node#N` (ordinal), which isn't a literal key in `nodes`.
1169
862
  let graphNodeId;
1170
863
  if (isGraphWorkflow && workflowMeta?.nodes) {
1171
864
  if (stepName in workflowMeta.nodes) {
@@ -1182,7 +875,6 @@ export class PikkuWorkflowService {
1182
875
  result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, rpcName, data, run.workflow);
1183
876
  }
1184
877
  else {
1185
- // Check if rpcName refers to a sub-workflow
1186
878
  const subWorkflowMeta = meta[rpcName];
1187
879
  if (subWorkflowMeta) {
1188
880
  const childWire = {
@@ -1213,9 +905,7 @@ export class PikkuWorkflowService {
1213
905
  result = await this.invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService, run);
1214
906
  }
1215
907
  }
1216
- // Store result and mark succeeded
1217
908
  await this.setStepResult(stepState.stepId, result);
1218
- // Resume orchestrator to continue workflow
1219
909
  await this.resumeWorkflow(runId);
1220
910
  }
1221
911
  catch (error) {
@@ -1231,25 +921,17 @@ export class PikkuWorkflowService {
1231
921
  });
1232
922
  return;
1233
923
  }
1234
- // Store error and mark failed
1235
924
  await this.setStepError(stepState.stepId, error);
1236
925
  const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1;
1237
926
  const retriesExhausted = stepState.attemptCount >= maxAttempts;
1238
927
  if (retriesExhausted) {
1239
- // No more retries - resume orchestrator to mark workflow as failed
1240
928
  await this.resumeWorkflow(runId);
1241
929
  }
1242
- // Always throw so queue knows the job failed and can retry if needed
1243
930
  throw error;
1244
931
  }
1245
932
  }
1246
- /**
1247
- * Orchestrate workflow execution (called by orchestrator)
1248
- * Runs workflow job and handles async exceptions
1249
- */
1250
933
  async orchestrateWorkflow(runId, rpcService) {
1251
934
  try {
1252
- // Run workflow job (replays with caching)
1253
935
  await this.runWorkflowJob(runId, rpcService);
1254
936
  }
1255
937
  catch (error) {
@@ -1259,9 +941,6 @@ export class PikkuWorkflowService {
1259
941
  return;
1260
942
  }
1261
943
  if (error.name === 'WorkflowDispatchException') {
1262
- // Transient: the queue was unreachable, not a workflow failure. Leave the
1263
- // run running and rethrow so the orchestrator job is redelivered and the
1264
- // workflow replays from its snapshot. Do NOT mark the run failed.
1265
944
  getSingletonServices().logger.warn(`Workflow run ${runId} could not dispatch a step (queue unavailable); leaving run for orchestrator retry`, error);
1266
945
  throw error;
1267
946
  }
@@ -1279,14 +958,7 @@ export class PikkuWorkflowService {
1279
958
  }
1280
959
  return getSingletonServices().queueService;
1281
960
  }
1282
- /**
1283
- * Invoke a step's RPC with the workflow-step wire (step identity + provenance).
1284
- * Identical for the queue executor and the inline executor — the only thing
1285
- * that differs between transports is who calls it, not the call itself.
1286
- */
1287
961
  async invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService, knownRun) {
1288
- // Carry the run's pikkuUserId onto the step wire so authed steps rehydrate their
1289
- // session on the queued path too (the bare job wire lacks it; inline already has it).
1290
962
  const run = knownRun ?? (await this.getRunIdentity(runId));
1291
963
  return rpcService.rpcWithWire(rpcName, data, {
1292
964
  ...(run?.wire?.pikkuUserId ? { pikkuUserId: run.wire.pikkuUserId } : {}),
@@ -1301,16 +973,6 @@ export class PikkuWorkflowService {
1301
973
  },
1302
974
  });
1303
975
  }
1304
- /**
1305
- * Inline (straight-through) step execution with an in-process retry loop —
1306
- * shared by inline RPC steps and inline function steps. Same scaffolding
1307
- * (running → result, or fail → retry-attempt → backoff → retry) wrapped
1308
- * around a step-specific `doWork` body. Stays O(K): no suspend/replay.
1309
- *
1310
- * `onError` is an optional hook for terminal errors that must NOT retry
1311
- * (e.g. RPC-not-found → suspend the run for redeploy). If it throws, the
1312
- * loop exits immediately without recording a step error or retrying.
1313
- */
1314
976
  async runInlineRetryLoop(stepState, retries, retryDelay, doWork, onError) {
1315
977
  let currentStepState = stepState;
1316
978
  while (true) {
@@ -1323,81 +985,49 @@ export class PikkuWorkflowService {
1323
985
  catch (error) {
1324
986
  if (onError)
1325
987
  await onError(error);
1326
- // Record the error (marks step as failed)
1327
988
  await this.setStepError(currentStepState.stepId, error);
1328
989
  if (currentStepState.attemptCount < retries) {
1329
- // Create a new pending retry attempt, then back off if configured.
1330
990
  currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
1331
991
  if (retryDelay) {
1332
992
  await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
1333
993
  }
1334
- // Continue loop to retry
1335
994
  }
1336
995
  else {
1337
- // No more retries, fail the workflow
1338
996
  throw error;
1339
997
  }
1340
998
  }
1341
999
  }
1342
1000
  }
1343
- /**
1344
- * Run a failed step's compensation handler as a durable step of its own.
1345
- *
1346
- * Durable rather than a bare invoke so a replay does not compensate twice —
1347
- * a handler is typically a refund or a rollback. `onError` is deliberately
1348
- * not forwarded: a compensation handler cannot itself compensate.
1349
- */
1350
1001
  async runStepCompensation(runId, stepName, onErrorRpcName, rpcService, error) {
1351
1002
  await this.rpcStep(runId, `${stepName}:onError`, onErrorRpcName, { error: { message: error.message } }, rpcService, { retries: 0 });
1352
1003
  }
1353
1004
  async rpcStep(runId, logicalStepName, rpcName, data, rpcService, stepOptions) {
1354
- // Capture the predecessor before nextStepKey advances the lineage to us.
1355
1005
  const fromStepName = this.lastStepName(runId);
1356
1006
  const stepName = this.nextStepKey(runId, logicalStepName);
1357
- // Resolve the retry policy ONCE here so the value persisted on the step
1358
- // (which drives `retriesExhausted`) is the same one the queue dispatch turns
1359
- // into `attempts`. Without this the queue could retry N times while the
1360
- // engine thinks retries are already exhausted (or vice-versa).
1361
1007
  const resolvedStepOptions = {
1362
1008
  retries: stepOptions?.retries ?? DEFAULT_STEP_RETRIES,
1363
1009
  retryDelay: stepOptions?.retryDelay,
1364
1010
  actor: stepOptions?.actor,
1365
1011
  onError: stepOptions?.onError,
1366
1012
  };
1367
- // Reuse the step if the run already reached it, otherwise create it.
1368
1013
  const stepState = await this.loadOrCreateStep(runId, stepName, () => this.insertStepState(runId, stepName, rpcName, data, resolvedStepOptions, fromStepName));
1369
1014
  if (stepState.status === 'succeeded') {
1370
- // Return cached result
1371
1015
  return stepState.result;
1372
1016
  }
1373
1017
  if (stepState.status === 'failed') {
1374
- // Step failed with retries exhausted - throw error to fail the workflow
1375
1018
  const error = new Error(stepState.error?.message ||
1376
1019
  `Step '${stepName}' failed after exhausting all retries`);
1377
- // Compensation, mirroring a graph node's onError: run the handler, then
1378
- // still throw — the workflow fails either way.
1379
1020
  if (resolvedStepOptions.onError) {
1380
1021
  await this.runStepCompensation(runId, stepName, resolvedStepOptions.onError, rpcService, error);
1381
1022
  }
1382
- // Preserve original error properties if available
1383
1023
  if (stepState.error) {
1384
1024
  Object.assign(error, stepState.error);
1385
1025
  }
1386
1026
  throw error;
1387
1027
  }
1388
1028
  if (stepState.status === 'scheduled') {
1389
- // Step is already scheduled, pause workflow
1390
1029
  throw new WorkflowAsyncException(runId, stepName);
1391
1030
  }
1392
- // Hand off to subclass-overridable transport. Default behavior enqueues
1393
- // via the queue service; DO-style subclasses RPC to a step worker.
1394
- // Dispatch BEFORE marking the step `scheduled`: if the queue is down,
1395
- // dispatchStep throws WorkflowDispatchException and the step stays `pending`,
1396
- // so the orchestrator's next replay re-dispatches it. Marking `scheduled`
1397
- // first would strand the step (replay sees `scheduled`, pauses, never
1398
- // re-enqueues the job that was never created).
1399
- // Actor steps never queue: they are outbound HTTP calls made by the
1400
- // runner itself, and the actor's session lives on this process.
1401
1031
  const dispatched = resolvedStepOptions.actor
1402
1032
  ? false
1403
1033
  : await this.dispatchStep(runId, stepName, rpcName, data, resolvedStepOptions, fromStepName);
@@ -1405,18 +1035,12 @@ export class PikkuWorkflowService {
1405
1035
  await this.setStepScheduled(stepState.stepId);
1406
1036
  throw new WorkflowAsyncException(runId, stepName);
1407
1037
  }
1408
- // Inline (no transport available) - execute locally with the shared retry
1409
- // loop. The body resolves to a sub-workflow result or a plain RPC result.
1410
1038
  const retries = resolvedStepOptions.retries ?? this.getConfig().retries;
1411
1039
  const retryDelay = resolvedStepOptions.retryDelay;
1412
1040
  return this.runInlineRetryLoop(stepState, retries, retryDelay, async (currentStepState) => {
1413
- // Actor step: send through the actor's authenticated client over the
1414
- // REAL transport. Never falls back to internal dispatch — that would
1415
- // bypass auth and fake a green health check.
1416
1041
  if (resolvedStepOptions.actor) {
1417
1042
  return resolvedStepOptions.actor.invoke(rpcName, data);
1418
1043
  }
1419
- // Check if the name refers to a workflow
1420
1044
  const workflowMeta = pikkuState(null, 'workflows', 'meta')[rpcName];
1421
1045
  if (workflowMeta) {
1422
1046
  const childWire = {
@@ -1427,7 +1051,6 @@ export class PikkuWorkflowService {
1427
1051
  };
1428
1052
  const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: true });
1429
1053
  await this.setStepChildRunId(currentStepState.stepId, childRunId);
1430
- // Poll until child workflow completes
1431
1054
  const childRun = await this.awaitRunEnd(childRunId, WORKFLOW_CHILD_POLL_MAX_MS);
1432
1055
  if (childRun.status === 'failed') {
1433
1056
  throw new Error(childRun.error?.message || 'Sub-workflow failed');
@@ -1448,40 +1071,19 @@ export class PikkuWorkflowService {
1448
1071
  }
1449
1072
  });
1450
1073
  }
1451
- async inlineStep(runId, logicalStepName, fn, stepOptions,
1452
- /**
1453
- * The input this step was called with, recorded on the run so a reporter can
1454
- * name the values under test. A closure step has none; a scenario step does.
1455
- */
1456
- data = null,
1457
- /**
1458
- * The name this step was dispatched by, for the kinds of inline step that
1459
- * have one. A closure step has no name; a scenario step is a step RPC, so
1460
- * it records the step function that ran — which is the only way to join a
1461
- * step back to its declaration when its durable name was built at runtime
1462
- * (a step called in a loop reaches the run as `sees @pikku/addon-todos`,
1463
- * declared as `sees ${packageName}`).
1464
- */
1465
- rpcName = null) {
1074
+ async inlineStep(runId, logicalStepName, fn, stepOptions, data = null, rpcName = null) {
1466
1075
  const fromStepName = this.lastStepName(runId);
1467
1076
  const stepName = this.nextStepKey(runId, logicalStepName);
1468
- // Reuse the step if the run already reached it, otherwise create it
1469
- // (inline, so never dispatched).
1470
1077
  const stepState = await this.loadOrCreateStep(runId, stepName, () => this.insertStepState(runId, stepName, rpcName, data, stepOptions, fromStepName));
1471
1078
  if (stepState.status === 'succeeded') {
1472
- // Return cached result
1473
1079
  return stepState.result;
1474
1080
  }
1475
- // Execute inline function
1476
1081
  const retries = stepOptions?.retries ?? this.getConfig().retries;
1477
1082
  const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay;
1478
- // Check if we're running inline (in-memory) or remote (queue-based)
1479
- if (this.isInline(runId)) {
1480
- // Inline mode - execute with the shared in-process retry loop.
1083
+ if (await this.isInline(runId)) {
1481
1084
  return this.runInlineRetryLoop(stepState, retries, retryDelay, () => fn());
1482
1085
  }
1483
1086
  else {
1484
- // Remote mode - single attempt, then suspend for orchestrator-driven retry.
1485
1087
  let currentStepState = stepState;
1486
1088
  try {
1487
1089
  await this.setStepRunning(currentStepState.stepId);
@@ -1490,18 +1092,12 @@ export class PikkuWorkflowService {
1490
1092
  return result;
1491
1093
  }
1492
1094
  catch (error) {
1493
- // Record the error (marks step as failed)
1494
1095
  await this.setStepError(currentStepState.stepId, error);
1495
- // Check if we should retry
1496
1096
  if (currentStepState.attemptCount < retries) {
1497
- // Create a new pending retry attempt (copies metadata from failed step)
1498
1097
  await this.createRetryAttempt(currentStepState.stepId, 'pending');
1499
- // Schedule orchestrator to retry after delay
1500
1098
  await this.scheduleOrchestratorRetry(runId, retryDelay);
1501
- // Pause workflow - orchestrator will replay and pick up new attempt
1502
1099
  throw new WorkflowAsyncException(runId, stepName);
1503
1100
  }
1504
- // No more retries, fail the workflow
1505
1101
  throw error;
1506
1102
  }
1507
1103
  }
@@ -1509,22 +1105,13 @@ export class PikkuWorkflowService {
1509
1105
  async sleepStep(runId, logicalStepName, duration) {
1510
1106
  const fromStepName = this.lastStepName(runId);
1511
1107
  const stepName = this.nextStepKey(runId, logicalStepName);
1512
- // Reuse the step if the run already reached it, otherwise create it
1513
- // (sleep step, no RPC).
1514
1108
  const stepState = await this.loadOrCreateStep(runId, stepName, () => this.insertStepState(runId, stepName, null, { duration }, undefined, fromStepName));
1515
1109
  if (stepState.status === 'succeeded') {
1516
- // Sleep already completed, return immediately
1517
1110
  return;
1518
1111
  }
1519
1112
  if (stepState.status === 'scheduled') {
1520
- // Sleep is already scheduled, pause workflow
1521
1113
  throw new WorkflowAsyncException(runId, stepName);
1522
1114
  }
1523
- // Hand off to subclass-overridable transport. Default behavior schedules
1524
- // a delayed sleeper RPC via the scheduler service; DO-style subclasses
1525
- // override to use native timer primitives (e.g. setAlarm). Schedule BEFORE
1526
- // marking `scheduled` so a scheduler outage leaves the step `pending` for
1527
- // re-scheduling on replay instead of stranding it (see rpcStep).
1528
1115
  let scheduled;
1529
1116
  try {
1530
1117
  scheduled = await this.scheduleSleep(runId, stepState.stepId, duration);
@@ -1536,21 +1123,9 @@ export class PikkuWorkflowService {
1536
1123
  await this.setStepScheduled(stepState.stepId);
1537
1124
  throw new WorkflowAsyncException(runId, stepName);
1538
1125
  }
1539
- // Inline mode - use setTimeout with actual duration
1540
1126
  await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(duration)));
1541
1127
  await this.setStepResult(stepState.stepId, null);
1542
1128
  }
1543
- /**
1544
- * Derive the durable step name for a suspend point from its `reason`, so each
1545
- * distinct reason is its own step row — letting one workflow have multiple
1546
- * independent suspends (e.g. wait-for-build, then wait-for-approval), and
1547
- * supporting dynamic reasons in loops (`suspend(`Wait for ${i}`)`) exactly
1548
- * like dynamic `do()` step names. The reason is used raw (it's just a text
1549
- * step name), only namespaced so it can't collide with a `do`/`sleep` step of
1550
- * the same name. Like `do()` / `sleep()`, the reason is the step's stable
1551
- * identity: it MUST be derived deterministically so it's the same every time
1552
- * the workflow replays through that point.
1553
- */
1554
1129
  getSuspendStepName(reason) {
1555
1130
  return `__workflow_suspend:${reason}`;
1556
1131
  }
@@ -1585,16 +1160,6 @@ export class PikkuWorkflowService {
1585
1160
  throw new WorkflowSuspendedException(runId, reason);
1586
1161
  });
1587
1162
  }
1588
- /**
1589
- * Wake a run later by enqueuing a delayed orchestrator pass. Deliberately NOT
1590
- * {@link scheduleSleep}: that resolves the step it is given, which for an
1591
- * approval would resolve the gate itself. This only nudges the run to replay
1592
- * and re-evaluate — the gate stays the sole judge of its own outcome.
1593
- *
1594
- * Best-effort by design. Expiry is decided from the recorded deadline on
1595
- * replay, so losing this wake costs liveness (the run sits until something
1596
- * else resumes it), never correctness.
1597
- */
1598
1163
  async scheduleRunWake(runId, delay) {
1599
1164
  try {
1600
1165
  const queueService = this.verifyQueueService();
@@ -1611,20 +1176,9 @@ export class PikkuWorkflowService {
1611
1176
  this.logger?.warn(`Failed to schedule approval expiry wake for run ${runId}; expiry will still resolve on the next replay`, error);
1612
1177
  }
1613
1178
  }
1614
- /**
1615
- * Durable step name for an approval gate. Namespaced separately from suspend
1616
- * so the two can't collide, and derived from `reason` for the same reason
1617
- * {@link getSuspendStepName} is: it must be stable across replays.
1618
- */
1619
1179
  getApprovalStepName(reason) {
1620
1180
  return `__workflow_approval:${reason}`;
1621
1181
  }
1622
- /**
1623
- * Run-state key holding an approval gate's record. Hex-encoded because the
1624
- * Mongo backend restricts state keys to `/^[a-zA-Z0-9_]+$/` and a `reason` is
1625
- * arbitrary human text. One key per gate, so two gates resolving concurrently
1626
- * can't clobber each other through a read-modify-write.
1627
- */
1628
1182
  approvalStateKey(stepName) {
1629
1183
  let hex = '';
1630
1184
  for (const byte of new TextEncoder().encode(stepName)) {
@@ -1632,31 +1186,15 @@ export class PikkuWorkflowService {
1632
1186
  }
1633
1187
  return `__approval_${hex}`;
1634
1188
  }
1635
- /**
1636
- * Record a decision against an approval gate and wake the run. Called from
1637
- * outside the workflow (an HTTP route, an RPC), so the schema value is NOT in
1638
- * scope here — the payload is stored raw and validated on replay inside the
1639
- * workflow body, which is the only place the schema exists. An invalid payload
1640
- * therefore leaves the gate closed rather than failing the run.
1641
- *
1642
- * `reason` addresses the first reach of that gate. An approval reached more
1643
- * than once under the same reason (e.g. in a loop) gets `#N`-suffixed step
1644
- * rows that this cannot currently target.
1645
- */
1646
1189
  async approveStep(runId, reason, decision) {
1647
1190
  const stepName = this.getApprovalStepName(reason);
1648
1191
  const stateKey = this.approvalStateKey(stepName);
1649
- // A resolved gate returns its cached step result and never re-reads state,
1650
- // so a decision recorded now would be silently discarded — most obviously
1651
- // when it loses the race with expiry. Reject instead, so the approver
1652
- // learns their decision did not land.
1653
1192
  let resolved;
1654
1193
  try {
1655
1194
  resolved = await this.getStepState(runId, stepName);
1656
1195
  }
1657
1196
  catch {
1658
- // No step row yet: the run has not reached the gate. Recording a decision
1659
- // ahead of it is legitimate — the gate picks it up on arrival.
1197
+ // knowledge: decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md
1660
1198
  }
1661
1199
  if (resolved?.stepId && resolved.status === 'succeeded') {
1662
1200
  const outcome = resolved.result;
@@ -1686,8 +1224,6 @@ export class PikkuWorkflowService {
1686
1224
  if (!stepState.stepId) {
1687
1225
  stepState = await this.insertStepState(runId, approvalStepName, 'pikkuWorkflowApproval', { reason, expiry: options.expiry }, undefined, fromStepName);
1688
1226
  }
1689
- // Unlike suspend, `succeeded` here means a decision (or expiry) was
1690
- // actually resolved, and the step result IS the return channel.
1691
1227
  if (stepState.status === 'succeeded') {
1692
1228
  return stepState.result;
1693
1229
  }
@@ -1695,8 +1231,6 @@ export class PikkuWorkflowService {
1695
1231
  let record = ((await this.getRunState(runId))[stateKey] ?? {});
1696
1232
  if (stepState.status === 'pending') {
1697
1233
  await this.setStepRunning(stepState.stepId);
1698
- // First reach: stamp the deadline and nudge the run awake when it
1699
- // passes. The deadline is what's authoritative — see below.
1700
1234
  if (options.expiry !== undefined && !record.expiresAt) {
1701
1235
  const expiresAt = new Date(Date.now() + getDurationInMilliseconds(options.expiry)).toISOString();
1702
1236
  record = { ...record, expiresAt };
@@ -1707,9 +1241,6 @@ export class PikkuWorkflowService {
1707
1241
  if (record.decision !== undefined) {
1708
1242
  const validation = await options.schema['~standard'].validate(record.decision);
1709
1243
  if (validation.issues) {
1710
- // Drop the bad decision and re-close the gate, leaving the failure
1711
- // legible to whoever tries next. Failing the run instead would let any
1712
- // caller kill a workflow with a malformed payload.
1713
1244
  await this.updateRunState(runId, stateKey, {
1714
1245
  ...record,
1715
1246
  decision: undefined,
@@ -1728,9 +1259,6 @@ export class PikkuWorkflowService {
1728
1259
  await this.setStepResult(stepState.stepId, outcome);
1729
1260
  return outcome;
1730
1261
  }
1731
- // Expiry is decided by comparing against the recorded deadline rather than
1732
- // by the timer having fired, so a duplicate, late, or dropped timer all
1733
- // produce the same answer.
1734
1262
  if (record.expiresAt && Date.now() >= Date.parse(record.expiresAt)) {
1735
1263
  const outcome = { status: 'expired' };
1736
1264
  await this.setStepResult(stepState.stepId, outcome);
@@ -1744,7 +1272,6 @@ export class PikkuWorkflowService {
1744
1272
  name,
1745
1273
  runId,
1746
1274
  getRun: async () => (await this.getRun(runId)),
1747
- // Implement workflow.do() - RPC form
1748
1275
  do: async (stepName, rpcNameOrFn, dataOrOptions, options) => {
1749
1276
  this.verifyStepName(stepName);
1750
1277
  if (typeof rpcNameOrFn === 'string') {
@@ -1757,7 +1284,6 @@ export class PikkuWorkflowService {
1757
1284
  return await this.inlineStep(runId, stepName, rpcNameOrFn, dataOrOptions);
1758
1285
  }
1759
1286
  },
1760
- // Implement workflow.sleep()
1761
1287
  sleep: async (stepName, duration) => {
1762
1288
  this.verifyStepName(stepName);
1763
1289
  await this.sleepStep(runId, stepName, getDurationInMilliseconds(duration));
@@ -1795,16 +1321,6 @@ export class PikkuWorkflowService {
1795
1321
  sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
1796
1322
  };
1797
1323
  }
1798
- /**
1799
- * Get the orchestrator queue name for a specific workflow.
1800
- * Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
1801
- * falls back to the shared orchestrator queue.
1802
- *
1803
- * Reads from `queue.meta` (always populated globally) rather than
1804
- * `queue.registrations` (only populated for queues this unit consumes).
1805
- * In a per-unit deploy the orchestrator unit doesn't consume per-step
1806
- * queues — but it produces to them — so registrations would miss them.
1807
- */
1808
1324
  getOrchestratorQueueName(workflowName) {
1809
1325
  if (workflowName && this.queueStrategy !== 'shared-groups') {
1810
1326
  const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`;
@@ -1825,15 +1341,6 @@ export class PikkuWorkflowService {
1825
1341
  }
1826
1342
  return this.getConfig().stepWorkerQueueName;
1827
1343
  }
1828
- /**
1829
- * Fairness key for a job on a shared queue. Under `'per-workflow'` the queue
1830
- * name already isolates workflows, so no group is needed — returning one
1831
- * anyway would cap a workflow inside its own dedicated queue.
1832
- *
1833
- * The tier repeats the id so a workflow can be given its own limit purely
1834
- * from config, with no per-workflow wiring; an unmatched tier falls back to
1835
- * the default limit.
1836
- */
1837
1344
  getJobGroup(id) {
1838
1345
  if (!id || this.queueStrategy !== 'shared-groups') {
1839
1346
  return undefined;