@pikku/core 0.12.78 → 0.12.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/dist/dev/hot-reload.js +1 -6
  3. package/dist/ecosystem.d.ts +27 -0
  4. package/dist/ecosystem.js +26 -0
  5. package/dist/errors/error-handler.js +4 -2
  6. package/dist/function/function-runner.js +20 -42
  7. package/dist/function/functions.types.d.ts +9 -5
  8. package/dist/index.d.ts +4 -9
  9. package/dist/index.js +3 -8
  10. package/dist/middleware/auth-cookie.d.ts +0 -4
  11. package/dist/middleware/auth-cookie.js +38 -2
  12. package/dist/middleware/cors.js +1 -0
  13. package/dist/middleware/index.d.ts +1 -1
  14. package/dist/middleware/index.js +1 -1
  15. package/dist/middleware/remote-auth.js +14 -3
  16. package/dist/permissions.d.ts +8 -10
  17. package/dist/permissions.js +0 -11
  18. package/dist/pikku-state.js +12 -5
  19. package/dist/schema.js +35 -1
  20. package/dist/services/ai-run-state-service.d.ts +7 -1
  21. package/dist/services/in-memory-ai-run-state-service.d.ts +1 -1
  22. package/dist/services/in-memory-ai-run-state-service.js +2 -1
  23. package/dist/services/in-memory-workflow-service.d.ts +10 -0
  24. package/dist/services/in-memory-workflow-service.js +25 -0
  25. package/dist/services/local-content-request-handler.d.ts +21 -0
  26. package/dist/services/local-content-request-handler.js +72 -53
  27. package/dist/services/local-content.d.ts +6 -0
  28. package/dist/services/local-content.js +14 -1
  29. package/dist/services/workflow-service.d.ts +4 -2
  30. package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
  31. package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
  32. package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
  33. package/dist/testing/service-tests/ai-storage-service-tests.js +226 -0
  34. package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
  35. package/dist/testing/service-tests/channel-store-tests.js +72 -0
  36. package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
  37. package/dist/testing/service-tests/credential-service-tests.js +109 -0
  38. package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
  39. package/dist/testing/service-tests/deployment-service-tests.js +21 -0
  40. package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
  41. package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
  42. package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
  43. package/dist/testing/service-tests/secret-service-tests.js +80 -0
  44. package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
  45. package/dist/testing/service-tests/session-store-tests.js +43 -0
  46. package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
  47. package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
  48. package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
  49. package/dist/testing/service-tests/workflow-service-tests.js +150 -0
  50. package/dist/testing/service-tests.d.ts +6 -0
  51. package/dist/testing/service-tests.js +26 -791
  52. package/dist/types/core.types.d.ts +11 -1
  53. package/dist/types/state.types.d.ts +1 -1
  54. package/dist/utils/node-host-resolver.d.ts +12 -0
  55. package/dist/utils/node-host-resolver.js +16 -0
  56. package/dist/utils/safe-fetch.d.ts +18 -0
  57. package/dist/utils/safe-fetch.js +167 -29
  58. package/dist/wirings/actor-flow/run-conversation.js +1 -6
  59. package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
  60. package/dist/wirings/ai-agent/agent-rpc.js +53 -0
  61. package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
  62. package/dist/wirings/ai-agent/ai-agent-memory.js +3 -1
  63. package/dist/wirings/ai-agent/ai-agent-prepare.js +3 -9
  64. package/dist/wirings/ai-agent/ai-agent-runner.js +28 -107
  65. package/dist/wirings/ai-agent/ai-agent-stream.js +20 -61
  66. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +56 -0
  67. package/dist/wirings/ai-agent/ai-agent-turn.js +81 -0
  68. package/dist/wirings/ai-agent/ai-agent.types.d.ts +1 -1
  69. package/dist/wirings/ai-agent/voice-input.js +1 -6
  70. package/dist/wirings/ai-agent/voice-output.js +2 -12
  71. package/dist/wirings/channel/channel-common.js +1 -0
  72. package/dist/wirings/channel/channel-handler.js +3 -5
  73. package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
  74. package/dist/wirings/channel/channel-rpc-service.js +0 -8
  75. package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
  76. package/dist/wirings/channel/channel-rpc.types.js +8 -0
  77. package/dist/wirings/channel/channel-runner.d.ts +1 -3
  78. package/dist/wirings/channel/channel-runner.js +16 -8
  79. package/dist/wirings/channel/channel.types.d.ts +2 -0
  80. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  81. package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
  82. package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
  83. package/dist/wirings/cli/cli-runner.js +4 -2
  84. package/dist/wirings/cli/cli.types.d.ts +0 -8
  85. package/dist/wirings/cli/command-parser.js +13 -0
  86. package/dist/wirings/gateway/gateway-runner.js +41 -5
  87. package/dist/wirings/http/http-routes.js +2 -0
  88. package/dist/wirings/http/http-runner.d.ts +0 -10
  89. package/dist/wirings/http/http-runner.js +6 -13
  90. package/dist/wirings/http/http.types.d.ts +0 -10
  91. package/dist/wirings/http/index.d.ts +1 -1
  92. package/dist/wirings/http/index.js +1 -1
  93. package/dist/wirings/mcp/mcp-runner.d.ts +0 -7
  94. package/dist/wirings/mcp/mcp-runner.js +0 -6
  95. package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
  96. package/dist/wirings/rpc/rpc-runner.js +53 -86
  97. package/dist/wirings/rpc/rpc-types.d.ts +3 -0
  98. package/dist/wirings/secret/validate-secret-definitions.js +2 -0
  99. package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
  100. package/dist/wirings/trigger/trigger-runner.js +1 -0
  101. package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
  102. package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
  103. package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
  104. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +29 -2
  105. package/dist/wirings/workflow/feature.js +7 -4
  106. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
  107. package/dist/wirings/workflow/graph/graph-runner.js +8 -7
  108. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
  109. package/dist/wirings/workflow/index.d.ts +7 -2
  110. package/dist/wirings/workflow/index.js +5 -1
  111. package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
  112. package/dist/wirings/workflow/pikku-scenario-service.js +38 -10
  113. package/dist/wirings/workflow/pikku-workflow-service.d.ts +44 -111
  114. package/dist/wirings/workflow/pikku-workflow-service.js +86 -404
  115. package/dist/wirings/workflow/workflow-approval.d.ts +39 -0
  116. package/dist/wirings/workflow/workflow-approval.js +114 -0
  117. package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
  118. package/dist/wirings/workflow/workflow-constants.js +35 -0
  119. package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
  120. package/dist/wirings/workflow/workflow-errors.js +112 -0
  121. package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
  122. package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
  123. package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
  124. package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
  125. package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
  126. package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
  127. package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
  128. package/dist/wirings/workflow/workflow-recovery.js +101 -0
  129. package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
  130. package/dist/wirings/workflow/workflow-run-engine.types.js +1 -0
  131. package/dist/wirings/workflow/workflow-run-ownership.d.ts +16 -0
  132. package/dist/wirings/workflow/workflow-run-ownership.js +29 -0
  133. package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
  134. package/dist/wirings/workflow/workflow-suspend.js +33 -0
  135. package/dist/wirings/workflow/workflow.types.d.ts +7 -0
  136. package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
  137. package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
  138. package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
  139. package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
  140. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
  141. package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
  142. package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
  143. package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
  144. package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
  145. package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
  146. package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
  147. package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
  148. package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
  149. package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
  150. package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
  151. package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
  152. package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
  153. package/knowledge/decisions/internals/index.md +38 -26
  154. package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
  155. package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
  156. package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
  157. package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
  158. package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
  159. package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
  160. package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
  161. package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
  162. package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
  163. package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
  164. package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
  165. package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
  166. package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
  167. package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
  168. package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
  169. package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
  170. package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
  171. package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
  172. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
  173. package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
  174. package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
  175. package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
  176. package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
  177. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +34 -0
  178. package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
  179. package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
  180. package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +13 -3
  181. package/knowledge/decisions/security/index.md +7 -0
  182. package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
  183. package/knowledge/questions/index.md +2 -1
  184. package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
  185. package/package.json +14 -3
  186. package/scripts/generate-api-report.d.mts +1 -0
  187. package/scripts/generate-api-report.mjs +89 -0
  188. package/scripts/generate-api-report.mts +218 -0
  189. package/src/api-report.test.ts +69 -0
  190. package/src/column-form.ts +1 -4
  191. package/src/crypto-utils.test.ts +15 -2
  192. package/src/dev/hot-reload.ts +2 -7
  193. package/src/ecosystem.ts +40 -0
  194. package/src/errors/error-handler.ts +5 -3
  195. package/src/errors/error.test.ts +4 -1
  196. package/src/function/function-runner.test.ts +1 -1
  197. package/src/function/function-runner.ts +28 -39
  198. package/src/function/functions.types.ts +15 -9
  199. package/src/handle-error.ts +1 -1
  200. package/src/index.ts +3 -22
  201. package/src/middleware/auth-cookie.test.ts +50 -0
  202. package/src/middleware/auth-cookie.ts +38 -2
  203. package/src/middleware/cors.ts +2 -1
  204. package/src/middleware/index.ts +1 -1
  205. package/src/middleware/remote-auth.test.ts +43 -0
  206. package/src/middleware/remote-auth.ts +17 -3
  207. package/src/middleware-runner.ts +4 -2
  208. package/src/no-any-casts.test.ts +57 -0
  209. package/src/permissions.test.ts +3 -1
  210. package/src/permissions.ts +22 -24
  211. package/src/pikku-state.ts +17 -6
  212. package/src/public-surface.json +578 -0
  213. package/src/public-surface.json.README +25 -0
  214. package/src/public-surface.test.ts +105 -0
  215. package/src/removed-legacy-exports.test.ts +62 -0
  216. package/src/schema.test.ts +78 -0
  217. package/src/schema.ts +36 -1
  218. package/src/services/ai-run-state-service.ts +7 -1
  219. package/src/services/audit-service.ts +2 -2
  220. package/src/services/in-memory-ai-run-state-service.ts +3 -2
  221. package/src/services/in-memory-workflow-service.ts +25 -0
  222. package/src/services/index.ts +1 -4
  223. package/src/services/local-content-request-handler.test.ts +43 -5
  224. package/src/services/local-content-request-handler.ts +103 -74
  225. package/src/services/local-content.ts +15 -1
  226. package/src/services/local-email-service.ts +5 -1
  227. package/src/services/system-role-guard.test.ts +4 -1
  228. package/src/services/workflow-service.ts +9 -2
  229. package/src/side-effects-are-declared.test.ts +84 -0
  230. package/src/source-files-stay-composable.test.ts +41 -0
  231. package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
  232. package/src/testing/service-tests/ai-storage-service-tests.ts +286 -0
  233. package/src/testing/service-tests/channel-store-tests.ts +98 -0
  234. package/src/testing/service-tests/credential-service-tests.ts +143 -0
  235. package/src/testing/service-tests/deployment-service-tests.ts +33 -0
  236. package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
  237. package/src/testing/service-tests/secret-service-tests.ts +105 -0
  238. package/src/testing/service-tests/session-store-tests.ts +62 -0
  239. package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
  240. package/src/testing/service-tests/workflow-service-tests.ts +308 -0
  241. package/src/testing/service-tests.ts +31 -1111
  242. package/src/types/core.types.ts +15 -1
  243. package/src/types/state.types.ts +1 -1
  244. package/src/utils/node-host-resolver.ts +20 -0
  245. package/src/utils/safe-fetch.test.ts +143 -1
  246. package/src/utils/safe-fetch.ts +200 -25
  247. package/src/wirings/actor-flow/run-conversation.ts +1 -6
  248. package/src/wirings/ai-agent/agent-rpc.ts +120 -0
  249. package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
  250. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
  251. package/src/wirings/ai-agent/ai-agent-memory.ts +8 -1
  252. package/src/wirings/ai-agent/ai-agent-prepare.ts +10 -12
  253. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
  254. package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
  255. package/src/wirings/ai-agent/ai-agent-runner.ts +49 -120
  256. package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
  257. package/src/wirings/ai-agent/ai-agent-stream.ts +36 -63
  258. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
  259. package/src/wirings/ai-agent/ai-agent-turn.ts +121 -0
  260. package/src/wirings/ai-agent/ai-agent.types.ts +1 -1
  261. package/src/wirings/ai-agent/voice-input.ts +1 -6
  262. package/src/wirings/ai-agent/voice-output.ts +2 -12
  263. package/src/wirings/channel/channel-common.ts +5 -2
  264. package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
  265. package/src/wirings/channel/channel-handler.ts +7 -7
  266. package/src/wirings/channel/channel-rpc-service.ts +0 -14
  267. package/src/wirings/channel/channel-rpc.test.ts +25 -4
  268. package/src/wirings/channel/channel-rpc.types.ts +14 -0
  269. package/src/wirings/channel/channel-runner.ts +33 -20
  270. package/src/wirings/channel/channel.types.ts +2 -0
  271. package/src/wirings/channel/local/local-channel-runner.ts +1 -1
  272. package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
  273. package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
  274. package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
  275. package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
  276. package/src/wirings/cli/cli-runner.ts +7 -4
  277. package/src/wirings/cli/cli.types.ts +0 -39
  278. package/src/wirings/cli/command-parser.test.ts +19 -0
  279. package/src/wirings/cli/command-parser.ts +17 -1
  280. package/src/wirings/gateway/gateway-authorization.test.ts +131 -0
  281. package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
  282. package/src/wirings/gateway/gateway-runner.ts +62 -22
  283. package/src/wirings/http/http-routes.ts +4 -1
  284. package/src/wirings/http/http-runner.ts +11 -26
  285. package/src/wirings/http/http.types.ts +0 -15
  286. package/src/wirings/http/index.ts +1 -7
  287. package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
  288. package/src/wirings/http/web-request.ts +1 -1
  289. package/src/wirings/mcp/mcp-runner.ts +2 -16
  290. package/src/wirings/persona/persona-environments.test.ts +14 -3
  291. package/src/wirings/persona/persona.test.ts +13 -3
  292. package/src/wirings/persona/validate-personas.ts +5 -1
  293. package/src/wirings/queue/queue-runner.ts +1 -1
  294. package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
  295. package/src/wirings/rpc/rpc-runner.ts +58 -136
  296. package/src/wirings/rpc/rpc-types.ts +7 -0
  297. package/src/wirings/rpc/wire-addon.ts +3 -1
  298. package/src/wirings/secret/validate-secret-definitions.test.ts +69 -0
  299. package/src/wirings/secret/validate-secret-definitions.ts +2 -0
  300. package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
  301. package/src/wirings/trigger/trigger-runner.ts +7 -5
  302. package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
  303. package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
  304. package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
  305. package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
  306. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
  307. package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
  308. package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
  309. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +35 -2
  310. package/src/wirings/workflow/feature.ts +7 -4
  311. package/src/wirings/workflow/graph/graph-node.ts +1 -1
  312. package/src/wirings/workflow/graph/graph-runner.test.ts +4 -2
  313. package/src/wirings/workflow/graph/graph-runner.ts +13 -16
  314. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
  315. package/src/wirings/workflow/index.ts +12 -4
  316. package/src/wirings/workflow/pikku-scenario-service.ts +54 -25
  317. package/src/wirings/workflow/pikku-workflow-service.test.ts +2 -2
  318. package/src/wirings/workflow/pikku-workflow-service.ts +208 -630
  319. package/src/wirings/workflow/scenario-hooks.test.ts +51 -0
  320. package/src/wirings/workflow/workflow-approval.ts +185 -0
  321. package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
  322. package/src/wirings/workflow/workflow-constants.ts +47 -0
  323. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
  324. package/src/wirings/workflow/workflow-dispatch-relay.test.ts +128 -0
  325. package/src/wirings/workflow/workflow-errors.ts +128 -0
  326. package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
  327. package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
  328. package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
  329. package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
  330. package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
  331. package/src/wirings/workflow/workflow-recovery.ts +162 -0
  332. package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
  333. package/src/wirings/workflow/workflow-run-authority.test.ts +215 -0
  334. package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
  335. package/src/wirings/workflow/workflow-run-ownership.ts +36 -0
  336. package/src/wirings/workflow/workflow-suspend.ts +61 -0
  337. package/src/wirings/workflow/workflow.types.ts +7 -0
  338. package/src/wirings-stay-decoupled.test.ts +123 -0
  339. package/tsconfig.json +1 -1
  340. package/tsconfig.tsbuildinfo +1 -1
  341. package/src/internal.ts +0 -10
@@ -11,5 +11,6 @@ currently makes a choice that nobody has defended — the choice stays, the doub
11
11
  gets recorded.
12
12
 
13
13
  <!-- pikku:knowledge-index -->
14
- _Nothing here yet._
14
+ - [A channel's middleware list accepts bare factories that nothing ever resolves](channel-middleware-accepts-bare-factories-that-nothing-resolves.md) — CoreChannel.channelMiddleware admits CorePikkuChannelMiddlewareFactory, but no runner calls one, so a bare factory would stall the chain
15
+ - [An unauthorized channel reply is sent outside the channel's declared Out type](unauthorized-channel-replies-escape-the-declared-out-type.md) — processMessageHandlers sends a bare string on auth failure that no generated client type admits, and the alternatives were never worked through
15
16
  <!-- /pikku:knowledge-index -->
@@ -0,0 +1,44 @@
1
+ ---
2
+ type: question
3
+ title: An unauthorized channel reply is sent outside the channel's declared Out type
4
+ description: processMessageHandlers sends a bare string on auth failure, which no generated client type admits — nobody has decided what the typed shape should be
5
+ tags: core, channel
6
+ ---
7
+
8
+ # An unauthorized channel reply is sent outside the channel's declared Out type
9
+
10
+ When a message arrives on a channel route that requires a session and none is
11
+ attached, `processMessageHandlers` in
12
+ `packages/core/src/wirings/channel/channel-handler.ts` logs the failure and then
13
+ sends the client a bare string:
14
+
15
+ ```ts
16
+ channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
17
+ ```
18
+
19
+ That compiles only because `processMessageHandlers` takes
20
+ `PikkuChannelHandler`, whose generics default to `<unknown, unknown>`, so `send`
21
+ accepts anything at this call site. The channel the application actually
22
+ declared has a concrete `Out`, and the generated client is typed from it. A
23
+ client can therefore receive a value its own types say is impossible, and no
24
+ `@pikku/client-websocket` consumer has a branch for it.
25
+
26
+ The alternatives were never worked through:
27
+
28
+ - **Widen every channel's `Out`** to `Out | ChannelError`, so the error is part
29
+ of the contract. Honest, but it forces a discriminated union on every consumer
30
+ including channels that can never fail auth.
31
+ - **Send on a reserved envelope** the way `channel-rpc` does, and let the client
32
+ library surface it out-of-band rather than as a message. Keeps `Out` clean,
33
+ but adds a second framing that every runtime adapter has to honour.
34
+ - **Send nothing** and let the close code carry it. Simplest, and loses the
35
+ route name that makes the failure debuggable.
36
+
37
+ Until one is chosen the string stays, because dropping it silently is worse: a
38
+ client that is quietly ignored has no way to tell "unauthorized" from "the
39
+ server is slow".
40
+
41
+ **What would settle it:** deciding whether channel-level errors belong in `Out`
42
+ at all, which is the same question `channel-rpc` already answered for RPC frames
43
+ by giving them their own envelope. If the answer is "same as RPC", this becomes
44
+ a decision note and the string becomes an envelope.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.78",
3
+ "version": "0.12.80",
4
4
  "description": "The Pikku runtime — functions, wirings, services, middleware and types",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -15,8 +15,17 @@
15
15
  "test": "bash run-tests.sh",
16
16
  "test:watch": "bash run-tests.sh --watch",
17
17
  "test:coverage": "bash run-tests.sh --coverage",
18
- "prepublishOnly": "yarn build"
18
+ "prepublishOnly": "yarn build",
19
+ "api-report": "tsx scripts/generate-api-report.mts"
19
20
  },
21
+ "sideEffects": [
22
+ "./dist/errors/errors.js",
23
+ "./dist/wirings/rpc/rpc-runner.js",
24
+ "./dist/wirings/rpc/remote-addon-auth.js",
25
+ "./dist/wirings/workflow/workflow-errors.js",
26
+ "./dist/wirings/workflow/workflow-run-ownership.js",
27
+ "./dist/wirings/workflow/pikku-scenario-service.js"
28
+ ],
20
29
  "exports": {
21
30
  ".": "./dist/index.js",
22
31
  "./middleware": "./dist/middleware/index.js",
@@ -37,6 +46,7 @@
37
46
  "./trigger": "./dist/wirings/trigger/index.js",
38
47
  "./rpc": "./dist/wirings/rpc/index.js",
39
48
  "./safe-fetch": "./dist/utils/safe-fetch.js",
49
+ "./node-host-resolver": "./dist/utils/node-host-resolver.js",
40
50
  "./mcp": "./dist/wirings/mcp/index.js",
41
51
  "./ai-agent": "./dist/wirings/ai-agent/index.js",
42
52
  "./gateway": "./dist/wirings/gateway/index.js",
@@ -61,7 +71,8 @@
61
71
  "./services/temporary-file-service": "./dist/services/temporary-file-service.js",
62
72
  "./crypto-utils": "./dist/crypto-utils.js",
63
73
  "./hmac": "./dist/utils/hmac.js",
64
- "./internal": "./dist/internal.js",
74
+ "./ecosystem": "./dist/ecosystem.js",
75
+ "./internal": "./dist/ecosystem.js",
65
76
  "./schema": "./dist/schema.js",
66
77
  "./testing": "./dist/testing/index.js",
67
78
  "./dev": "./dist/dev/hot-reload.js"
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Emits `api-report.md`: every entry point's exported symbols with their full
3
+ * type signatures.
4
+ *
5
+ * `public-surface.json` pins the *names* a consumer can reach. That catches an
6
+ * export appearing or disappearing and nothing else — adding a method to
7
+ * `MetaService`, or making a field on `ChannelMeta` required, sails straight
8
+ * past it. Those are the changes that break a consumer's build, and there are
9
+ * far more of them: 130 exported classes carry 543 members between them, and
10
+ * 268 exported interfaces carry 1,385.
11
+ *
12
+ * So this walks the type checker instead and writes what each symbol actually
13
+ * *is*. The report is committed, and `api-report.test.ts` fails when the two
14
+ * disagree — so a member-level change is a reviewable diff rather than a
15
+ * surprise in someone else's CI.
16
+ *
17
+ * Run: yarn api-report
18
+ */
19
+ import { readFileSync, writeFileSync } from 'node:fs';
20
+ import { resolve, dirname } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ import ts from 'typescript';
23
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
24
+ const pkg = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf-8'));
25
+ const entryPoints = Object.entries(pkg.exports).map(([subpath, dist]) => ({
26
+ subpath,
27
+ file: resolve(packageRoot, dist.replace('./dist/', './src/').replace(/\.js$/, '.ts')),
28
+ }));
29
+ const program = ts.createProgram(entryPoints.map((e) => e.file), {
30
+ target: ts.ScriptTarget.ESNext,
31
+ module: ts.ModuleKind.Node16,
32
+ moduleResolution: ts.ModuleResolutionKind.Node16,
33
+ strict: true,
34
+ skipLibCheck: true,
35
+ });
36
+ const checker = program.getTypeChecker();
37
+ /** One line per symbol: how it is declared, flattened and stripped of noise. */
38
+ const signature = (exported) => {
39
+ // A re-export is an alias; the declaration that matters is the target's, so
40
+ // an interface re-exported through a barrel still reports its members.
41
+ const symbol = exported.flags & ts.SymbolFlags.Alias
42
+ ? checker.getAliasedSymbol(exported)
43
+ : exported;
44
+ const declaration = symbol.declarations?.[0];
45
+ if (!declaration)
46
+ return exported.getName();
47
+ if (ts.isInterfaceDeclaration(declaration) ||
48
+ ts.isClassDeclaration(declaration) ||
49
+ ts.isTypeAliasDeclaration(declaration) ||
50
+ ts.isEnumDeclaration(declaration)) {
51
+ // The declaration itself, so member changes show up in the diff.
52
+ return declaration
53
+ .getText()
54
+ .replace(/\/\*\*[\s\S]*?\*\//g, '')
55
+ .replace(/\/\/[^\n]*/g, '')
56
+ .split('\n')
57
+ .map((l) => l.trim())
58
+ .filter(Boolean)
59
+ .join(' ');
60
+ }
61
+ const type = checker.getTypeOfSymbolAtLocation(symbol, declaration);
62
+ return `${exported.getName()}: ${checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)}`;
63
+ };
64
+ const sections = [
65
+ '# @pikku/core API report',
66
+ '',
67
+ 'Generated by `yarn api-report`. Every exported symbol with its full',
68
+ 'signature, so a member-level change is a reviewable diff. Do not edit.',
69
+ '',
70
+ ];
71
+ for (const { subpath, file } of entryPoints) {
72
+ const source = program.getSourceFile(file);
73
+ if (!source)
74
+ continue;
75
+ const moduleSymbol = checker.getSymbolAtLocation(source);
76
+ if (!moduleSymbol)
77
+ continue;
78
+ const exports = checker
79
+ .getExportsOfModule(moduleSymbol)
80
+ .map((s) => [s.getName(), signature(s)])
81
+ .sort(([a], [b]) => a.localeCompare(b));
82
+ sections.push(`## ${subpath}`, '');
83
+ sections.push('```ts');
84
+ for (const [, sig] of exports)
85
+ sections.push(sig);
86
+ sections.push('```', '');
87
+ }
88
+ writeFileSync(resolve(packageRoot, 'api-report.md'), sections.join('\n'));
89
+ console.log(`api-report.md written for ${entryPoints.length} entry points`);
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Emits `api-report.md`: every entry point's exported symbols with their full
3
+ * type signatures.
4
+ *
5
+ * `public-surface.json` pins the *names* a consumer can reach, by reading
6
+ * `Object.keys` off each entry point. That catches an export appearing or
7
+ * disappearing and nothing else — adding a method to `MetaService`, or making
8
+ * a field on `ChannelMeta` required, sails straight past it, and so does every
9
+ * type-only export, which erases before there is an object to enumerate.
10
+ *
11
+ * Those are the changes that break a consumer's build, and the report's own
12
+ * summary shows how many more of them there are than there are names.
13
+ *
14
+ * So this walks the type checker instead and writes what each symbol actually
15
+ * *is*. The report is committed, and `api-report.test.ts` fails when the two
16
+ * disagree — so a member-level change is a reviewable diff rather than a
17
+ * surprise in someone else's CI.
18
+ *
19
+ * Run: yarn api-report
20
+ */
21
+ import { readFileSync, writeFileSync } from 'node:fs'
22
+ import { resolve, dirname } from 'node:path'
23
+ import { fileURLToPath } from 'node:url'
24
+ import ts from 'typescript'
25
+
26
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
27
+ const pkg = JSON.parse(
28
+ readFileSync(resolve(packageRoot, 'package.json'), 'utf-8')
29
+ )
30
+
31
+ const entryPoints = Object.entries(pkg.exports as Record<string, string>).map(
32
+ ([subpath, dist]) => ({
33
+ subpath,
34
+ file: resolve(
35
+ packageRoot,
36
+ dist.replace('./dist/', './src/').replace(/\.js$/, '.ts')
37
+ ),
38
+ })
39
+ )
40
+
41
+ const program = ts.createProgram(
42
+ entryPoints.map((e) => e.file),
43
+ {
44
+ target: ts.ScriptTarget.ESNext,
45
+ module: ts.ModuleKind.Node16,
46
+ moduleResolution: ts.ModuleResolutionKind.Node16,
47
+ strict: true,
48
+ skipLibCheck: true,
49
+ }
50
+ )
51
+ const checker = program.getTypeChecker()
52
+
53
+ /** One line per symbol: how it is declared, flattened and stripped of noise. */
54
+ const signature = (exported: ts.Symbol): string => {
55
+ // A re-export is an alias; the declaration that matters is the target's, so
56
+ // an interface re-exported through a barrel still reports its members.
57
+ const symbol =
58
+ exported.flags & ts.SymbolFlags.Alias
59
+ ? checker.getAliasedSymbol(exported)
60
+ : exported
61
+ const declaration = symbol.declarations?.[0]
62
+ if (!declaration) return exported.getName()
63
+
64
+ if (
65
+ ts.isInterfaceDeclaration(declaration) ||
66
+ ts.isClassDeclaration(declaration) ||
67
+ ts.isTypeAliasDeclaration(declaration) ||
68
+ ts.isEnumDeclaration(declaration)
69
+ ) {
70
+ // The declaration itself, so member changes show up in the diff.
71
+ return declaration
72
+ .getText()
73
+ .replace(/\/\*\*[\s\S]*?\*\//g, '')
74
+ .replace(/\/\/[^\n]*/g, '')
75
+ .split('\n')
76
+ .map((l) => l.trim())
77
+ .filter(Boolean)
78
+ .join(' ')
79
+ }
80
+
81
+ const type = checker.getTypeOfSymbolAtLocation(symbol, declaration)
82
+ return `${exported.getName()}: ${checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)}`
83
+ }
84
+
85
+ /**
86
+ * Entry points whose signatures move with the CLI's codegen. Everything else
87
+ * is what an application imports, and is what a compatibility promise covers.
88
+ *
89
+ * knowledge: decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md
90
+ */
91
+ const ECOSYSTEM_SUBPATHS = new Set(['./ecosystem', './internal'])
92
+
93
+ type Exported = { name: string; members: number; signature: string }
94
+
95
+ /** Members a consumer can reach, so a private field is not a promise. */
96
+ const memberCount = (declaration: ts.Declaration): number => {
97
+ if (ts.isInterfaceDeclaration(declaration)) return declaration.members.length
98
+ if (ts.isEnumDeclaration(declaration)) return declaration.members.length
99
+ if (!ts.isClassDeclaration(declaration)) return 0
100
+ return declaration.members.filter(
101
+ (member) =>
102
+ !(ts.getModifiers(member) ?? []).some(
103
+ (modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword
104
+ ) && !(member.name && ts.isPrivateIdentifier(member.name))
105
+ ).length
106
+ }
107
+
108
+ const modules = new Map<string, Exported[]>()
109
+
110
+ for (const { subpath, file } of entryPoints) {
111
+ const source = program.getSourceFile(file)
112
+ if (!source) continue
113
+ const moduleSymbol = checker.getSymbolAtLocation(source)
114
+ if (!moduleSymbol) continue
115
+
116
+ modules.set(
117
+ subpath,
118
+ checker
119
+ .getExportsOfModule(moduleSymbol)
120
+ .map((exported) => {
121
+ const symbol =
122
+ exported.flags & ts.SymbolFlags.Alias
123
+ ? checker.getAliasedSymbol(exported)
124
+ : exported
125
+ const declaration = symbol.declarations?.[0]
126
+ return {
127
+ name: exported.getName(),
128
+ members: declaration ? memberCount(declaration) : 0,
129
+ signature: signature(exported),
130
+ }
131
+ })
132
+ .sort((a, b) => a.name.localeCompare(b.name))
133
+ )
134
+ }
135
+
136
+ // A name reachable from more than one entry point is counted once, against the
137
+ // weaker promise — reaching it through a stable subpath is what a consumer will
138
+ // have done, whatever else also exports it.
139
+ const tierOf = new Map<string, string>()
140
+ const membersOf = new Map<string, number>()
141
+ for (const [subpath, exports] of modules) {
142
+ const tier = ECOSYSTEM_SUBPATHS.has(subpath) ? 'ecosystem' : 'stable'
143
+ for (const { name, members } of exports) {
144
+ if (tier === 'stable' || !tierOf.has(name)) tierOf.set(name, tier)
145
+ membersOf.set(name, Math.max(membersOf.get(name) ?? 0, members))
146
+ }
147
+ }
148
+
149
+ const tally = (tier: string) => {
150
+ const names = [...tierOf].filter(([, t]) => t === tier).map(([n]) => n)
151
+ return {
152
+ entryPoints: [...modules.keys()].filter(
153
+ (s) => (ECOSYSTEM_SUBPATHS.has(s) ? 'ecosystem' : 'stable') === tier
154
+ ).length,
155
+ names: names.length,
156
+ members: names.reduce((total, n) => total + (membersOf.get(n) ?? 0), 0),
157
+ }
158
+ }
159
+ const stable = tally('stable')
160
+ const ecosystem = tally('ecosystem')
161
+ const observable =
162
+ stable.names + stable.members + ecosystem.names + ecosystem.members
163
+
164
+ const reachableFrom = new Map<string, number>()
165
+ for (const exports of modules.values())
166
+ for (const { name } of exports)
167
+ reachableFrom.set(name, (reachableFrom.get(name) ?? 0) + 1)
168
+
169
+ const perEntryPoint = [...modules]
170
+ .map(([subpath, exports]) => {
171
+ const exclusive = exports.filter(
172
+ ({ name }) => reachableFrom.get(name) === 1
173
+ )
174
+ return {
175
+ subpath,
176
+ exports: exports.length,
177
+ exclusive: exclusive.length,
178
+ members: exclusive.reduce((total, e) => total + e.members, 0),
179
+ }
180
+ })
181
+ .sort((a, b) => b.exclusive + b.members - (a.exclusive + a.members))
182
+
183
+ const sections: string[] = [
184
+ '# @pikku/core API report',
185
+ '',
186
+ 'Generated by `yarn api-report`. Every exported symbol with its full',
187
+ 'signature, so a member-level change is a reviewable diff. Do not edit.',
188
+ '',
189
+ '## What a compatibility promise covers',
190
+ '',
191
+ `**${observable} observable things**: ${stable.names + ecosystem.names} exported names, plus`,
192
+ `${stable.members + ecosystem.members} members on the classes and interfaces among them.`,
193
+ '',
194
+ '| tier | entry points | names | members |',
195
+ '| --- | ---: | ---: | ---: |',
196
+ `| stable | ${stable.entryPoints} | ${stable.names} | ${stable.members} |`,
197
+ `| ecosystem | ${ecosystem.entryPoints} | ${ecosystem.names} | ${ecosystem.members} |`,
198
+ '',
199
+ 'An entry point whose exports are mostly *exclusive* is a self-contained',
200
+ 'subsystem rather than shared machinery — which tends to mean a newer one.',
201
+ '',
202
+ '| entry point | exports | exclusive | members on those |',
203
+ '| --- | ---: | ---: | ---: |',
204
+ ...perEntryPoint.map(
205
+ (e) => `| \`${e.subpath}\` | ${e.exports} | ${e.exclusive} | ${e.members} |`
206
+ ),
207
+ '',
208
+ ]
209
+
210
+ for (const [subpath, exports] of modules) {
211
+ sections.push(`## ${subpath}`, '')
212
+ sections.push('```ts')
213
+ for (const { signature } of exports) sections.push(signature)
214
+ sections.push('```', '')
215
+ }
216
+
217
+ writeFileSync(resolve(packageRoot, 'api-report.md'), sections.join('\n'))
218
+ console.log(`api-report.md written for ${entryPoints.length} entry points`)
@@ -0,0 +1,69 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { execFileSync } from 'node:child_process'
4
+ import { readFileSync } from 'node:fs'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { join, dirname } from 'node:path'
7
+
8
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
9
+ const reportPath = join(packageRoot, 'api-report.md')
10
+
11
+ /**
12
+ * `public-surface.json` pins the *names* a consumer can reach. That catches an
13
+ * export appearing or vanishing and nothing else — adding a method to
14
+ * `MetaService`, or making a field on `ChannelMeta` required, sails past it,
15
+ * as does every type-only export. Those are the changes that break a
16
+ * consumer's build, and the report's summary counts how many more of them
17
+ * there are than there are names.
18
+ *
19
+ * So `api-report.md` records what each symbol *is*, and this fails when the
20
+ * committed report and the code disagree.
21
+ *
22
+ * knowledge: decisions/internals/the-api-report-pins-members-not-just-names.md
23
+ */
24
+ describe('the API report matches the code', () => {
25
+ test('regenerating produces no diff', () => {
26
+ const committed = readFileSync(reportPath, 'utf-8')
27
+
28
+ execFileSync(
29
+ 'npx',
30
+ ['tsx', join(packageRoot, 'scripts', 'generate-api-report.mts')],
31
+ { cwd: packageRoot, stdio: 'pipe' }
32
+ )
33
+ const regenerated = readFileSync(reportPath, 'utf-8')
34
+
35
+ if (committed !== regenerated) {
36
+ // Restore, so a failing run does not leave the tree dirty.
37
+ const committedLines = committed.split('\n')
38
+ const regeneratedLines = regenerated.split('\n')
39
+ const firstDiff = regeneratedLines.findIndex(
40
+ (line, i) => line !== committedLines[i]
41
+ )
42
+ require('node:fs').writeFileSync(reportPath, committed)
43
+
44
+ assert.fail(
45
+ 'the public API changed but api-report.md was not regenerated.\n' +
46
+ `First difference at line ${firstDiff + 1}:\n` +
47
+ ` committed: ${committedLines[firstDiff] ?? '(end of file)'}\n` +
48
+ ` regenerated: ${regeneratedLines[firstDiff] ?? '(end of file)'}\n` +
49
+ 'Run `yarn api-report` and review the diff — a member added to an ' +
50
+ 'exported interface or class is a change consumers compile against.'
51
+ )
52
+ }
53
+ })
54
+
55
+ test('the report actually contains member-level detail', () => {
56
+ // Guards the test above: a report of bare names would match itself forever
57
+ // while pinning nothing that public-surface.json does not already pin.
58
+ const report = readFileSync(reportPath, 'utf-8')
59
+
60
+ assert.ok(
61
+ /export interface MetaService \{[^}]*getHttpMeta/.test(report),
62
+ 'expected MetaService to be reported with its members'
63
+ )
64
+ assert.ok(
65
+ !/^\w+: any$/m.test(report),
66
+ 'symbols reported as `any` mean re-export aliases are not being followed'
67
+ )
68
+ })
69
+ })
@@ -23,10 +23,7 @@ export const hashToken = async (raw: string): Promise<HashedValue> => {
23
23
  if (!subtle) {
24
24
  throw new Error('WebCrypto not available')
25
25
  }
26
- const digest = await subtle.digest(
27
- 'SHA-256',
28
- new TextEncoder().encode(raw)
29
- )
26
+ const digest = await subtle.digest('SHA-256', new TextEncoder().encode(raw))
30
27
  let hex = ''
31
28
  for (const byte of new Uint8Array(digest)) {
32
29
  hex += byte.toString(16).padStart(2, '0')
@@ -627,8 +627,21 @@ describe('envelope encryption', () => {
627
627
  )
628
628
  })
629
629
 
630
+ /**
631
+ * Timed against a derivation measured here rather than a fixed millisecond
632
+ * budget. A constant threshold is a load test in disguise: this runs
633
+ * alongside two thousand other tests, and 50ms left barely 4x headroom over
634
+ * a 10ms window, so the suite went red roughly one run in five. Timing the
635
+ * derivation the test already needs costs nothing and makes both sides scale
636
+ * together, so the margin survives whatever else the machine is doing.
637
+ *
638
+ * knowledge: decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md
639
+ */
630
640
  test('N secrets cost one KEK derivation, not N', async () => {
641
+ const derivationStart = performance.now()
631
642
  const kek = await deriveKEK(passphrase, salt)
643
+ const oneDerivation = performance.now() - derivationStart
644
+
632
645
  const count = 50
633
646
  const stored = []
634
647
  for (let i = 0; i < count; i++) {
@@ -648,8 +661,8 @@ describe('envelope encryption', () => {
648
661
  const elapsed = performance.now() - start
649
662
 
650
663
  assert.ok(
651
- elapsed < 50,
652
- `unwrapping ${count} DEKs took ${elapsed.toFixed(1)}ms; a re-derivation per secret would cost seconds`
664
+ elapsed < oneDerivation,
665
+ `unwrapping ${count} DEKs took ${elapsed.toFixed(1)}ms, longer than the ${oneDerivation.toFixed(1)}ms a single KEK derivation costs — re-deriving per secret would cost about ${(oneDerivation * count).toFixed(0)}ms`
653
666
  )
654
667
  })
655
668
  })
@@ -26,7 +26,7 @@ const isFunctionConfig = (
26
26
  typeof value === 'object' &&
27
27
  value !== null &&
28
28
  'func' in value &&
29
- typeof (value as any).func === 'function'
29
+ typeof (value as { func?: unknown }).func === 'function'
30
30
  )
31
31
  }
32
32
 
@@ -104,12 +104,7 @@ export async function pikkuDevReloader(
104
104
  return
105
105
  }
106
106
 
107
- // Register every function-config export, replacing known functions and
108
- // adding new ones. Write into the map captured at startup, NOT pikkuState's
109
- // current one: a dev-server watcher may have temporarily swapped in a
110
- // codegen-scoped map, whose writes are discarded when it restores.
111
- // Schemas are NOT touched here: `input`/`output` hold raw zod schemas while
112
- // the schema map carries codegen JSON schemas; mixing them crashed reloads.
107
+ // knowledge: decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md
113
108
  for (const [exportName, exportValue] of Object.entries(mod)) {
114
109
  if (!isFunctionConfig(exportValue)) continue
115
110
  const isNew = !functionsMap.has(exportName)
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The surface a package in the Pikku ecosystem reaches for — a runtime adapter,
3
+ * a service, the code generator's output, the CLI — and application code does
4
+ * not.
5
+ *
6
+ * Split out from the package root so 1.0's compatibility promise covers what
7
+ * applications actually import. These signatures move when the runtime needs
8
+ * them to — `runPikkuFunc` was reshaped over the last year while every field on
9
+ * `PikkuWire` survived — and promising stability on both would mean promising
10
+ * the weaker of the two.
11
+ *
12
+ * Not `/internal`: generated bootstrap files import from here, so the specifier
13
+ * lands in the user's own `.pikku` directory, and telling someone they are
14
+ * touching internals when the code generator put it there is both wrong and
15
+ * self-defeating — it could never be broken anyway. Not `/runtime` either: that
16
+ * reads as runtime-versus-compile-time, i.e. the real API, and `packages/runtimes/*`
17
+ * already claims the word while the CLI is the largest consumer here.
18
+ *
19
+ * `/internal` stays as an alias because the pinned bootstrap CLI still emits it.
20
+ *
21
+ * knowledge: decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md
22
+ */
23
+ export {
24
+ pikkuState,
25
+ resetPikkuState,
26
+ getAllPackageStates,
27
+ getSingletonServices,
28
+ getCreateWireServices,
29
+ setSingletonServices,
30
+ } from './pikku-state.js'
31
+ export { runPikkuFunc, addFunction } from './function/function-runner.js'
32
+ export {
33
+ addGlobalMiddleware,
34
+ addTagMiddleware as addMiddleware,
35
+ } from './middleware-runner.js'
36
+ export { httpRouter } from './wirings/http/routers/http-router.js'
37
+ export type {
38
+ CreateSingletonServices,
39
+ CreateWireServices,
40
+ } from './types/core.types.js'
@@ -15,7 +15,8 @@ export class PikkuError extends Error {
15
15
  * everything else.
16
16
  */
17
17
  export const isExpectedError = (error: unknown): boolean =>
18
- error instanceof PikkuError || (error as any)?.expected === true
18
+ error instanceof PikkuError ||
19
+ (error as { expected?: unknown } | null)?.expected === true
19
20
 
20
21
  export interface ErrorDetails {
21
22
  status: number
@@ -48,7 +49,7 @@ export const getErrorResponse = (
48
49
  ): { status: number; message: string; mcpCode?: number } | undefined => {
49
50
  const errors = pikkuState(null, 'misc', 'errors')
50
51
 
51
- let ctor: unknown = (error as any)?.constructor
52
+ let ctor: unknown = (error as { constructor?: unknown } | null)?.constructor
52
53
  while (typeof ctor === 'function' && ctor !== Object) {
53
54
  const details = errors.get(ctor as PikkuErrorConstructor)
54
55
  if (details) {
@@ -57,7 +58,8 @@ export const getErrorResponse = (
57
58
  ctor = Object.getPrototypeOf(ctor)
58
59
  }
59
60
 
60
- const name = (error as any)?.constructor?.name
61
+ const name = (error as { constructor?: { name?: string } } | null)
62
+ ?.constructor?.name
61
63
  return name
62
64
  ? Array.from(errors.entries()).find(([e]) => e.name === name)?.[1]
63
65
  : undefined
@@ -199,7 +199,10 @@ describe('getErrorResponse resolution order', () => {
199
199
  test('a subclass of a registered error inherits its registered status', () => {
200
200
  class TenantNotFoundError extends NotFoundError {}
201
201
  const expected = getErrorResponse(new NotFoundError())
202
- assert.deepStrictEqual(getErrorResponse(new TenantNotFoundError()), expected)
202
+ assert.deepStrictEqual(
203
+ getErrorResponse(new TenantNotFoundError()),
204
+ expected
205
+ )
203
206
  })
204
207
 
205
208
  test('an unregistered, non-colliding error class resolves to undefined', () => {
@@ -637,7 +637,7 @@ describe('runPikkuFunc - Integration Tests', () => {
637
637
  singletonServices: mockSingletonServices,
638
638
  data: () => ({}),
639
639
  auth: false,
640
- wire: { session: { readonly: true } as any },
640
+ wire: { session: { readonly: true } },
641
641
  }),
642
642
  ReadonlySessionError
643
643
  )