@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
@@ -4,7 +4,7 @@ import {
4
4
  combineChannelMiddleware,
5
5
  wrapChannelWithMiddleware,
6
6
  } from '../wirings/channel/channel-middleware-runner.js'
7
- import { runPermissions } from '../permissions.js'
7
+ import { runPermissions, type PermissionWire } from '../permissions.js'
8
8
  import { withoutSecrets } from '../services/secretless.js'
9
9
  import { pikkuState } from '../pikku-state.js'
10
10
  import {
@@ -248,6 +248,28 @@ export const runPikkuFunc = async <In = any, Out = any>(
248
248
  invocationWire,
249
249
  'rpc'
250
250
  )
251
+
252
+ // knowledge: decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md
253
+ const restoreInvocationWire = () => {
254
+ if (previousRpcDescriptor) {
255
+ Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor)
256
+ }
257
+ restoreField('functionId', previousFunctionId)
258
+ restoreField('audit', previousAudit)
259
+ restoreField('addonNamespace', previousAddonNamespace)
260
+ }
261
+
262
+ function restoreField<K extends 'functionId' | 'audit' | 'addonNamespace'>(
263
+ key: K,
264
+ previous: PikkuWire[K]
265
+ ) {
266
+ if (previous === undefined) {
267
+ delete invocationWire[key]
268
+ } else {
269
+ invocationWire[key] = previous
270
+ }
271
+ }
272
+
251
273
  invocationWire.functionId = resolvedFunctionId
252
274
  invocationWire.audit = resolvedAuditConfig
253
275
  // Track which addon instance is executing so intra-addon sibling calls
@@ -294,7 +316,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
294
316
  }
295
317
  }
296
318
 
297
- if ((session as any)?.readonly && !funcMeta.readonly) {
319
+ if (session?.readonly && !funcMeta.readonly) {
298
320
  throw new ReadonlySessionError()
299
321
  }
300
322
 
@@ -347,7 +369,8 @@ export const runPikkuFunc = async <In = any, Out = any>(
347
369
  resolvedSingletonServices,
348
370
  'a permission'
349
371
  ) as CoreSingletonServices),
350
- wire: invocationWire as any,
372
+ // knowledge: decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md
373
+ wire: invocationWire as PermissionWire,
351
374
  data: actualData,
352
375
  packageName,
353
376
  })
@@ -435,47 +458,13 @@ export const runPikkuFunc = async <In = any, Out = any>(
435
458
  executeFunction
436
459
  )) as Out
437
460
  } finally {
438
- if (previousRpcDescriptor) {
439
- Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor)
440
- }
441
- if (previousFunctionId === undefined) {
442
- delete (invocationWire as any).functionId
443
- } else {
444
- invocationWire.functionId = previousFunctionId
445
- }
446
- if (previousAudit === undefined) {
447
- delete (invocationWire as any).audit
448
- } else {
449
- invocationWire.audit = previousAudit
450
- }
451
- if (previousAddonNamespace === undefined) {
452
- delete (invocationWire as any).addonNamespace
453
- } else {
454
- invocationWire.addonNamespace = previousAddonNamespace
455
- }
461
+ restoreInvocationWire()
456
462
  }
457
463
  }
458
464
 
459
465
  try {
460
466
  return (await executeFunction()) as Out
461
467
  } finally {
462
- if (previousRpcDescriptor) {
463
- Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor)
464
- }
465
- if (previousFunctionId === undefined) {
466
- delete (invocationWire as any).functionId
467
- } else {
468
- invocationWire.functionId = previousFunctionId
469
- }
470
- if (previousAudit === undefined) {
471
- delete (invocationWire as any).audit
472
- } else {
473
- invocationWire.audit = previousAudit
474
- }
475
- if (previousAddonNamespace === undefined) {
476
- delete (invocationWire as any).addonNamespace
477
- } else {
478
- invocationWire.addonNamespace = previousAddonNamespace
479
- }
468
+ restoreInvocationWire()
480
469
  }
481
470
  }
@@ -14,12 +14,6 @@ import type { CoreNodeConfig } from '../wirings/node/node.types.js'
14
14
  import type { ScenarioSurface } from '../wirings/workflow/scenario-step.types.js'
15
15
  import type { Safe } from '../secret-value.js'
16
16
 
17
- /**
18
- * @deprecated Use StandardSchemaV1 from @standard-schema/spec instead.
19
- * This alias exists only for backward compatibility with generated code.
20
- */
21
- export type ZodLike<T = any> = StandardSchemaV1<T, T>
22
-
23
17
  export type CorePikkuFunction<
24
18
  In,
25
19
  Out,
@@ -141,6 +135,14 @@ export type CorePikkuAuthConfig<
141
135
  description?: string
142
136
  }
143
137
 
138
+ /**
139
+ * Marks a permission produced by `pikkuAuth`, so agent tool filtering can tell a
140
+ * session check apart from an ordinary permission.
141
+ *
142
+ * knowledge: decisions/security/permission-auth-filtering-requires-live-permission-functions.md
143
+ */
144
+ export type AuthBranded = { __pikkuAuth?: true }
145
+
144
146
  export const pikkuAuth = <
145
147
  Services extends CoreSecretlessSingletonServices =
146
148
  SecretlessServices<CoreServices>,
@@ -151,13 +153,17 @@ export const pikkuAuth = <
151
153
  | CorePikkuAuthConfig<Services, Session>
152
154
  ): CorePikkuPermission<any, Services, any> => {
153
155
  const fn = typeof auth === 'function' ? auth : auth.func
154
- const wrapper = async (services: Services, _data: any, wire: any) => {
156
+ const wrapper: CorePikkuPermission<any, Services, any> & AuthBranded = async (
157
+ services: Services,
158
+ _data: any,
159
+ wire: any
160
+ ) => {
155
161
  const session = wire.session
156
162
  if (!session) return false
157
163
  return fn(services, session as Session)
158
164
  }
159
- ;(wrapper as any).__pikkuAuth = true
160
- return wrapper as any
165
+ wrapper.__pikkuAuth = true
166
+ return wrapper
161
167
  }
162
168
 
163
169
  export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> =
@@ -33,7 +33,7 @@ export const handleHTTPError = (
33
33
  e.message !== 'An error occurred'
34
34
  ? e.message
35
35
  : errorResponse.message,
36
- payload: clientFacing ? (e as any).payload : undefined,
36
+ payload: clientFacing ? (e as { payload?: unknown }).payload : undefined,
37
37
  errorId: traceId,
38
38
  })
39
39
 
package/src/index.ts CHANGED
@@ -63,7 +63,6 @@ export type {
63
63
  CorePikkuPermissionFactory,
64
64
  CorePikkuApprovalDescription,
65
65
  CorePermissionGroup,
66
- ZodLike,
67
66
  } from './function/functions.types.js'
68
67
  export {
69
68
  pikkuAuth,
@@ -71,7 +70,7 @@ export {
71
70
  pikkuPermissionFactory,
72
71
  pikkuApprovalDescription,
73
72
  } from './function/functions.types.js'
74
- export { addFunction, getAllFunctionNames } from './function/index.js'
73
+ export { getAllFunctionNames } from './function/index.js'
75
74
  export type {
76
75
  ListInput,
77
76
  ListOutput,
@@ -79,6 +78,7 @@ export type {
79
78
  LeafFilter,
80
79
  LeafValue,
81
80
  } from './function/list.types.js'
81
+ export { pikkuCLIRender } from './wirings/cli/cli-runner.js'
82
82
  export { PikkuRequest } from './pikku-request.js'
83
83
  export {
84
84
  getRelativeTimeOffsetFromNow,
@@ -90,7 +90,6 @@ export {
90
90
  isVersionedId,
91
91
  parseVersionedId,
92
92
  } from './version.js'
93
- export { runPikkuFunc } from './function/function-runner.js'
94
93
  export {
95
94
  AbandonedError,
96
95
  beginChanges,
@@ -98,20 +97,12 @@ export {
98
97
  runInAbortScope,
99
98
  type AbortScope,
100
99
  } from './function/abort-scope.js'
101
- export { runCLICommand, pikkuCLIRender } from './wirings/cli/cli-runner.js'
102
100
  export { fetch } from './wirings/http/http-runner.js'
103
- export {
104
- runMCPTool,
105
- runMCPResource,
106
- runMCPPrompt,
107
- } from './wirings/mcp/mcp-runner.js'
108
101
  export type {
109
102
  MCPToolResponse,
110
103
  MCPResourceResponse,
111
104
  MCPPromptResponse,
112
105
  } from './wirings/mcp/mcp.types.js'
113
- export { runQueueJob } from './wirings/queue/queue-runner.js'
114
- export { runScheduledTask } from './wirings/scheduler/scheduler-runner.js'
115
106
  export {
116
107
  AIProviderAuthError,
117
108
  AIProviderNotConfiguredError,
@@ -254,12 +245,7 @@ export type {
254
245
  RemoteAddonAuth,
255
246
  } from './wirings/rpc/wire-remote-addon.js'
256
247
  export type { PikkuPackageState } from './types/state.types.js'
257
- export {
258
- runMiddleware,
259
- addTagMiddleware,
260
- addTagMiddleware as addMiddleware,
261
- addGlobalMiddleware,
262
- } from './middleware-runner.js'
248
+ export { runMiddleware, addTagMiddleware } from './middleware-runner.js'
263
249
  export { addGlobalPermission, checkAuthPermissions } from './permissions.js'
264
250
  export { hasScopes, verifyScopes } from './scopes.js'
265
251
  export {
@@ -267,11 +253,6 @@ export {
267
253
  stopSingletonServices,
268
254
  pikkuServerLifecycle,
269
255
  } from './utils.js'
270
- export {
271
- getSingletonServices,
272
- getCreateWireServices,
273
- setSingletonServices,
274
- } from './pikku-state.js'
275
256
  export { clearPikkuRuntimeState } from './test-utils.js'
276
257
  export {
277
258
  type ScheduledTaskInfo,
@@ -131,6 +131,56 @@ describe('authCookie middleware', () => {
131
131
  assert(setCookies[0].options.expires instanceof Date)
132
132
  })
133
133
 
134
+ test('clears the cookie on logout instead of re-minting a credential', async () => {
135
+ const SessionService = new PikkuSessionService<CoreUserSession>()
136
+ let encodeCalled = false
137
+ const jwtService = {
138
+ // A JWT of an absent session still decodes to a truthy {iat,exp} the
139
+ // runner accepts — so encode must never be called on logout.
140
+ encode: async () => {
141
+ encodeCalled = true
142
+ return 'reminted-jwt'
143
+ },
144
+ decode: async () => ({ userId: 'user123' }),
145
+ }
146
+
147
+ const middleware = authCookie({
148
+ name: 'session',
149
+ expiresIn: { value: 30, unit: 'day' },
150
+ options: { httpOnly: true },
151
+ })
152
+
153
+ const mockResponse = createMockHTTPResponse()
154
+
155
+ await middleware(
156
+ { jwt: jwtService, logger: createMockLogger() } as any,
157
+ {
158
+ ...createMiddlewareSessionWireProps(SessionService),
159
+ http: {
160
+ request: createMockHTTPRequest({ session: 'existing-jwt' }),
161
+ response: mockResponse,
162
+ },
163
+ } as any,
164
+ async () => {
165
+ // The logout handler clears the session.
166
+ SessionService.clear()
167
+ }
168
+ )
169
+
170
+ const setCookies = mockResponse.getCookies()
171
+ assert.equal(setCookies.length, 1, 'a clearing cookie must be written')
172
+ const cleared = setCookies[0]!
173
+ assert.equal(cleared.name, 'session')
174
+ assert.equal(cleared.value, '', 'the cookie value must be empty')
175
+ assert.equal(cleared.options.maxAge, 0)
176
+ assert.ok(
177
+ cleared.options.expires instanceof Date &&
178
+ cleared.options.expires.getTime() === 0,
179
+ 'the cookie must expire at the epoch'
180
+ )
181
+ assert.equal(encodeCalled, false, 'must not sign an absent session')
182
+ })
183
+
134
184
  test('should not set cookie when session does not change', async () => {
135
185
  const mockUserSession: CoreUserSession = { userId: 'user789' }
136
186
  const SessionService = new PikkuSessionService<CoreUserSession>()
@@ -7,6 +7,27 @@ import { getRelativeTimeOffsetFromNow } from '../time-utils.js'
7
7
  * Reads a JWT session from a cookie, and re-issues the cookie after the
8
8
  * request whenever the session changed (e.g. after login).
9
9
  */
10
+ /** Standard JWT registered claims — present on a token even with no session. */
11
+ const JWT_REGISTERED_CLAIMS = new Set([
12
+ 'iat',
13
+ 'exp',
14
+ 'nbf',
15
+ 'iss',
16
+ 'aud',
17
+ 'sub',
18
+ 'jti',
19
+ ])
20
+
21
+ /**
22
+ * Whether a decoded payload carries an actual session, as opposed to being
23
+ * absent or a bare JWT envelope of `{ iat, exp }`. True only for a non-null
24
+ * object with at least one key that is not a registered JWT claim.
25
+ */
26
+ const hasSessionIdentity = (session: unknown): boolean => {
27
+ if (!session || typeof session !== 'object') return false
28
+ return Object.keys(session).some((key) => !JWT_REGISTERED_CLAIMS.has(key))
29
+ }
30
+
10
31
  export const authCookie = pikkuMiddlewareFactory<{
11
32
  name: string
12
33
  options: SerializeOptions
@@ -32,7 +53,12 @@ export const authCookie = pikkuMiddlewareFactory<{
32
53
  const cookieValue = http.request.cookie(name)
33
54
  if (cookieValue && jwtService) {
34
55
  const userSession = await jwtService.decode(cookieValue)
35
- if (userSession) {
56
+ // Not just truthy: a JWT of an absent session still decodes to a
57
+ // `{ iat, exp }` object, which is truthy and which the function runner
58
+ // would accept as a session. Require at least one claim that is not a
59
+ // standard JWT timestamp, so an identity-less token is treated as no
60
+ // session rather than as an authenticated one.
61
+ if (userSession && hasSessionIdentity(userSession)) {
36
62
  setSession?.(userSession)
37
63
  }
38
64
  }
@@ -45,7 +71,17 @@ export const authCookie = pikkuMiddlewareFactory<{
45
71
 
46
72
  if (hasSessionChanged?.()) {
47
73
  const currentSession = await getSession?.()
48
- if (jwtService) {
74
+ // Logout path: `clearSession()` marks the session changed while leaving
75
+ // it absent. Re-signing that into a fresh, unexpired cookie would hand
76
+ // the browser a credential on the way out — so clear the cookie instead
77
+ // of minting one, and never call `encode` with an absent session.
78
+ if (!hasSessionIdentity(currentSession)) {
79
+ http.response.cookie(name, '', {
80
+ ...mergedOptions,
81
+ expires: new Date(0),
82
+ maxAge: 0,
83
+ })
84
+ } else if (jwtService) {
49
85
  http.response.cookie(
50
86
  name,
51
87
  await jwtService.encode(expiresIn, currentSession),
@@ -72,7 +72,8 @@ export const cors = pikkuMiddlewareFactory<{
72
72
 
73
73
  if (request.method() === 'options') {
74
74
  response.header('Access-Control-Max-Age', String(maxAge))
75
- response.status(204).json(undefined as any)
75
+ // 204 carries no body, and `json` has no no-content overload.
76
+ response.status(204).json(undefined as never)
76
77
  return
77
78
  }
78
79
 
@@ -10,4 +10,4 @@ export {
10
10
  addGlobalMiddleware,
11
11
  runMiddleware,
12
12
  } from '../middleware-runner.js'
13
- export { addGlobalPermission, addTagPermission } from '../permissions.js'
13
+ export { addGlobalPermission } from '../permissions.js'
@@ -263,6 +263,49 @@ describe('pikkuRemoteAuthMiddleware', () => {
263
263
  )
264
264
  })
265
265
 
266
+ test('enforces the fn binding even when the path prefix varies in case', async () => {
267
+ // The router matches /remote/rpc/ case-insensitively, so /Remote/RPC/
268
+ // reaches the same handler. The fn-binding check must run for it too, or
269
+ // a token scoped to one function invokes any other by varying the case.
270
+ await assert.rejects(
271
+ () =>
272
+ pikkuRemoteAuthMiddleware(
273
+ {
274
+ secrets: createMockSecrets(TEST_SECRET),
275
+ jwt: createMockJWT({ aud: 'pikku-remote', fn: 'allowedFunc' }),
276
+ } as any,
277
+ {
278
+ http: {
279
+ request: createMockRequest(
280
+ { authorization: 'Bearer valid-token' },
281
+ '/Remote/RPC/otherFunc'
282
+ ),
283
+ },
284
+ } as any,
285
+ async () => {}
286
+ ),
287
+ (err: any) => err instanceof UnauthorizedError
288
+ )
289
+ })
290
+
291
+ test('requires authorization for a case-varied remote path when no secret is set', async () => {
292
+ // The no-secret branch must also treat /REMOTE/RPC/ as remote, or a
293
+ // misconfigured mesh silently serves it unauthenticated.
294
+ await assert.rejects(
295
+ () =>
296
+ pikkuRemoteAuthMiddleware(
297
+ { secrets: createMockSecrets(undefined) } as any,
298
+ {
299
+ http: {
300
+ request: createMockRequest({}, '/REMOTE/RPC/anyFunc'),
301
+ },
302
+ } as any,
303
+ async () => {}
304
+ ),
305
+ (err: any) => err instanceof UnauthorizedError
306
+ )
307
+ })
308
+
266
309
  test('should allow when token fn matches requested function', async () => {
267
310
  let nextCalled = false
268
311
  await pikkuRemoteAuthMiddleware(
@@ -6,6 +6,18 @@ import {
6
6
  REMOTE_SESSION_INFO,
7
7
  } from '../crypto-utils.js'
8
8
 
9
+ const REMOTE_RPC_PREFIX = '/remote/rpc/'
10
+
11
+ /**
12
+ * Whether this request targets the remote-RPC surface. Compared case-insensitively
13
+ * because the router matches routes that way (path-to-regexp defaults to
14
+ * `sensitive: false`), so `/Remote/RPC/fn` reaches the same handler as
15
+ * `/remote/rpc/fn`. A case-sensitive check here let a case-varied path slip
16
+ * past the trust gate and the token's `fn` binding while still being dispatched.
17
+ */
18
+ const isRemoteRpcPath = (path: string): boolean =>
19
+ path.toLowerCase().startsWith(REMOTE_RPC_PREFIX)
20
+
9
21
  export const pikkuRemoteAuthMiddleware = pikkuMiddleware(
10
22
  async ({ secrets, jwt }, { http, setSession }, next) => {
11
23
  if (!http?.request || !secrets) {
@@ -16,7 +28,7 @@ export const pikkuRemoteAuthMiddleware = pikkuMiddleware(
16
28
  try {
17
29
  secret = (await secrets.getSecret('PIKKU_REMOTE_SECRET')).reveal()
18
30
  } catch {
19
- if (http.request.path().startsWith('/remote/rpc/')) {
31
+ if (isRemoteRpcPath(http.request.path())) {
20
32
  throw new UnauthorizedError()
21
33
  }
22
34
  return next()
@@ -51,9 +63,11 @@ export const pikkuRemoteAuthMiddleware = pikkuMiddleware(
51
63
  throw new UnauthorizedError()
52
64
  }
53
65
 
54
- if (payload?.fn && http.request.path().startsWith('/remote/rpc/')) {
66
+ if (payload?.fn && isRemoteRpcPath(http.request.path())) {
67
+ // The prefix length is the same in any case, so slicing the raw path
68
+ // keeps the requested function name in its original case for the compare.
55
69
  const fn = decodeURIComponent(
56
- http.request.path().slice('/remote/rpc/'.length)
70
+ http.request.path().slice(REMOTE_RPC_PREFIX.length)
57
71
  )
58
72
  if (fn && payload.fn !== fn) {
59
73
  throw new UnauthorizedError()
@@ -47,8 +47,10 @@ export const runMiddleware = async <
47
47
  let result: any
48
48
  const dispatch = async (index: number): Promise<any> => {
49
49
  if (sorted && index < sorted.length) {
50
- return await sorted[index]!(services as any, wire, () =>
51
- dispatch(index + 1)
50
+ return await sorted[index]!(
51
+ services as Parameters<Middleware>[0],
52
+ wire,
53
+ () => dispatch(index + 1)
52
54
  )
53
55
  } else if (main) {
54
56
  result = await main()
@@ -0,0 +1,57 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { readFileSync, readdirSync } from 'node:fs'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { join, dirname, relative } from 'node:path'
6
+
7
+ const srcRoot = dirname(fileURLToPath(import.meta.url))
8
+ const thisFile = fileURLToPath(import.meta.url)
9
+
10
+ /**
11
+ * `as any` in a cast position. The word boundary and the following delimiter
12
+ * keep prose such as "as any URL a browser can load" out of the match.
13
+ */
14
+ const AS_ANY = /\bas any\b(?!\s+[A-Za-z])/
15
+
16
+ const collectSourceFiles = (directory: string): string[] =>
17
+ readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
18
+ const entryPath = join(directory, entry.name)
19
+ if (entry.isDirectory()) {
20
+ return entry.name === 'dist' ? [] : collectSourceFiles(entryPath)
21
+ }
22
+ return entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')
23
+ ? [entryPath]
24
+ : []
25
+ })
26
+
27
+ describe('core asserts to named types, never to any', () => {
28
+ test('no non-test module casts through `as any`', () => {
29
+ const offenders = collectSourceFiles(srcRoot)
30
+ .filter((file) => file !== thisFile)
31
+ .flatMap((file) =>
32
+ readFileSync(file, 'utf-8')
33
+ .split('\n')
34
+ .flatMap((line, index) =>
35
+ AS_ANY.test(line)
36
+ ? [`${relative(srcRoot, file)}:${index + 1} ${line.trim()}`]
37
+ : []
38
+ )
39
+ )
40
+
41
+ assert.deepEqual(
42
+ offenders,
43
+ [],
44
+ '`as any` discards the target type, so a wrong value passes as readily as ' +
45
+ 'a right one. Assert to the type actually wanted — and if the narrowing ' +
46
+ 'cannot be expressed, say why in a knowledge note and point at it:\n' +
47
+ offenders.join('\n')
48
+ )
49
+ })
50
+
51
+ test('the matcher finds a cast and ignores the prose that reads like one', () => {
52
+ // Guards the test above: a regex that matched nothing would pass forever.
53
+ assert.equal(AS_ANY.test('const x = y as any'), true)
54
+ assert.equal(AS_ANY.test('foo(bar as any)'), true)
55
+ assert.equal(AS_ANY.test('a picture, as any URL a browser can load'), false)
56
+ })
57
+ })
@@ -72,7 +72,9 @@ describe('runPermissions — function permissions (OR gate)', () => {
72
72
  run({ funcPermissions: { grp: [async () => true, async () => false] } }),
73
73
  { message: 'Permission denied' }
74
74
  )
75
- await run({ funcPermissions: { grp: [async () => true, async () => true] } })
75
+ await run({
76
+ funcPermissions: { grp: [async () => true, async () => true] },
77
+ })
76
78
  })
77
79
 
78
80
  test('accepts a bare permission array as a single AND branch', async () => {
@@ -3,7 +3,24 @@ import type {
3
3
  CoreUserSession,
4
4
  PikkuWire,
5
5
  } from './types/core.types.js'
6
+ import type { PikkuRPC } from './wirings/rpc/rpc-types.js'
7
+
8
+ /**
9
+ * The wire a permission function sees. `Out = never` denies it `channel.send`.
10
+ *
11
+ * knowledge: decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md
12
+ */
13
+ export type PermissionWire = PikkuWire<
14
+ any,
15
+ never,
16
+ false,
17
+ any,
18
+ PikkuRPC,
19
+ never,
20
+ never
21
+ >
6
22
  import type {
23
+ AuthBranded,
7
24
  CorePermissionGroup,
8
25
  CorePikkuPermission,
9
26
  } from './function/functions.types.js'
@@ -14,7 +31,7 @@ const verifyPermissions = async (
14
31
  permissions: CorePermissionGroup,
15
32
  services: CoreServices,
16
33
  data: any,
17
- wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>
34
+ wire: PermissionWire
18
35
  ): Promise<boolean> => {
19
36
  if (!permissions) {
20
37
  return true
@@ -34,7 +51,7 @@ const verifyPermissions = async (
34
51
  return true
35
52
  }
36
53
  } else {
37
- if (await funcs(services, data, wire as any)) {
54
+ if (await funcs(services, data, wire)) {
38
55
  return true
39
56
  }
40
57
  }
@@ -42,7 +59,6 @@ const verifyPermissions = async (
42
59
  return false
43
60
  }
44
61
 
45
-
46
62
  const globalPermissionsCache: Record<
47
63
  string,
48
64
  readonly (CorePermissionGroup | CorePikkuPermission)[]
@@ -71,24 +87,6 @@ export const addGlobalPermission = (
71
87
  return permissions
72
88
  }
73
89
 
74
- /**
75
- * @deprecated Tag-level permissions were removed in #972 — permissions are now
76
- * function-scoped only (declare them on the function via `pikkuFunc({ permissions })`).
77
- * This throwing stub exists solely so the pinned bootstrap CLI (which still
78
- * generates an `addTagPermission` wrapper) can resolve the import at build time;
79
- * it is never called. Delete once `PIKKU_CLI_VERSION` in the CLI build is bumped
80
- * past the release that removes tag permissions.
81
- */
82
- export const addTagPermission = (
83
- _tag: string,
84
- _permissions: CorePermissionGroup | CorePikkuPermission[],
85
- _packageName: string | null = null
86
- ): never => {
87
- throw new Error(
88
- 'addTagPermission was removed in #972 — tag-level permissions no longer exist. Declare permissions on the function definition instead: pikkuFunc({ permissions }).'
89
- )
90
- }
91
-
92
90
  const bucket = (
93
91
  packageName: string | null
94
92
  ): (CorePermissionGroup | CorePikkuPermission)[] =>
@@ -141,7 +139,7 @@ export const runPermissions = async ({
141
139
  }: {
142
140
  funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
143
141
  services: CoreServices
144
- wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>
142
+ wire: PermissionWire
145
143
  data: any
146
144
  packageName?: string | null
147
145
  /** What the non-global gate is called in debug logs, e.g. 'function', 'agent'. */
@@ -188,14 +186,14 @@ export const checkAuthPermissions = async (
188
186
 
189
187
  const collect = (perm: CorePermissionGroup | CorePikkuPermission) => {
190
188
  if (typeof perm === 'function') {
191
- if ((perm as any).__pikkuAuth) {
189
+ if ((perm as AuthBranded).__pikkuAuth) {
192
190
  authPerms.push(perm)
193
191
  }
194
192
  } else if (perm && typeof perm === 'object') {
195
193
  for (const funcs of Object.values(perm)) {
196
194
  const arr = Array.isArray(funcs) ? funcs : [funcs]
197
195
  for (const fn of arr) {
198
- if (typeof fn === 'function' && (fn as any).__pikkuAuth) {
196
+ if (typeof fn === 'function' && (fn as AuthBranded).__pikkuAuth) {
199
197
  authPerms.push(fn)
200
198
  }
201
199
  }