@pikku/core 0.11.1 → 0.12.0

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 (363) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/crypto-utils.d.ts +2 -0
  3. package/dist/crypto-utils.js +78 -0
  4. package/dist/errors/error-handler.js +3 -3
  5. package/dist/errors/errors.d.ts +8 -0
  6. package/dist/errors/errors.js +16 -0
  7. package/dist/errors/index.d.ts +0 -1
  8. package/dist/errors/index.js +0 -1
  9. package/dist/function/function-runner.d.ts +10 -3
  10. package/dist/function/function-runner.js +136 -31
  11. package/dist/function/functions.types.d.ts +35 -12
  12. package/dist/function/functions.types.js +16 -6
  13. package/dist/function/index.d.ts +2 -2
  14. package/dist/function/index.js +1 -2
  15. package/dist/handle-error.js +1 -10
  16. package/dist/index.d.ts +30 -24
  17. package/dist/index.js +19 -27
  18. package/dist/middleware/auth-apikey.d.ts +1 -1
  19. package/dist/middleware/auth-apikey.js +3 -3
  20. package/dist/middleware/auth-bearer.d.ts +1 -1
  21. package/dist/middleware/auth-bearer.js +3 -6
  22. package/dist/middleware/auth-cookie.d.ts +1 -1
  23. package/dist/middleware/auth-cookie.js +6 -8
  24. package/dist/middleware/cors.d.ts +46 -0
  25. package/dist/middleware/cors.js +73 -0
  26. package/dist/middleware/index.d.ts +5 -3
  27. package/dist/middleware/index.js +5 -3
  28. package/dist/middleware/remote-auth.d.ts +5 -0
  29. package/dist/middleware/remote-auth.js +55 -0
  30. package/dist/middleware/timeout.d.ts +1 -1
  31. package/dist/middleware-runner.d.ts +3 -14
  32. package/dist/middleware-runner.js +14 -16
  33. package/dist/permissions.d.ts +3 -86
  34. package/dist/permissions.js +20 -80
  35. package/dist/pikku-state.d.ts +33 -104
  36. package/dist/pikku-state.js +150 -66
  37. package/dist/schema.d.ts +8 -6
  38. package/dist/schema.js +31 -18
  39. package/dist/services/ai-agent-runner-service.d.ts +24 -0
  40. package/dist/services/ai-agent-runner-service.js +1 -0
  41. package/dist/services/ai-run-state-service.d.ts +13 -0
  42. package/dist/services/ai-run-state-service.js +1 -0
  43. package/dist/services/ai-storage-service.d.ts +18 -0
  44. package/dist/services/ai-storage-service.js +1 -0
  45. package/dist/services/deployment-service.d.ts +19 -0
  46. package/dist/services/deployment-service.js +1 -0
  47. package/dist/services/gopass-secrets.d.ts +47 -0
  48. package/dist/services/gopass-secrets.js +94 -0
  49. package/dist/services/in-memory-trigger-service.d.ts +23 -0
  50. package/dist/services/in-memory-trigger-service.js +50 -0
  51. package/dist/services/in-memory-workflow-service.d.ts +59 -0
  52. package/dist/services/in-memory-workflow-service.js +287 -0
  53. package/dist/services/index.d.ts +25 -11
  54. package/dist/services/index.js +10 -13
  55. package/dist/services/local-secrets.d.ts +23 -0
  56. package/dist/services/local-secrets.js +45 -0
  57. package/dist/services/local-variables.d.ts +5 -0
  58. package/dist/services/local-variables.js +18 -0
  59. package/dist/services/scheduler-service.d.ts +21 -1
  60. package/dist/services/scheduler-service.js +14 -0
  61. package/dist/services/schema-service.d.ts +6 -0
  62. package/dist/services/scoped-secret-service.d.ts +16 -0
  63. package/dist/services/scoped-secret-service.js +35 -0
  64. package/dist/services/secret-service.d.ts +25 -4
  65. package/dist/services/trigger-service.d.ts +23 -0
  66. package/dist/services/trigger-service.js +1 -0
  67. package/dist/services/typed-secret-service.d.ts +30 -0
  68. package/dist/services/typed-secret-service.js +40 -0
  69. package/dist/services/typed-variables-service.d.ts +27 -0
  70. package/dist/services/typed-variables-service.js +46 -0
  71. package/dist/services/user-session-service.d.ts +13 -0
  72. package/dist/services/user-session-service.js +17 -0
  73. package/dist/services/variables-service.d.ts +5 -0
  74. package/dist/services/workflow-service.d.ts +43 -0
  75. package/dist/services/workflow-service.js +1 -0
  76. package/dist/types/core.types.d.ts +54 -10
  77. package/dist/types/core.types.js +7 -0
  78. package/dist/types/state.types.d.ts +127 -0
  79. package/dist/types/state.types.js +1 -0
  80. package/dist/utils.d.ts +10 -1
  81. package/dist/utils.js +60 -3
  82. package/dist/version.d.ts +6 -0
  83. package/dist/version.js +19 -0
  84. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +29 -0
  85. package/dist/wirings/ai-agent/ai-agent-memory.js +199 -0
  86. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +9 -0
  87. package/dist/wirings/ai-agent/ai-agent-model-config.js +36 -0
  88. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +60 -0
  89. package/dist/wirings/ai-agent/ai-agent-prepare.js +312 -0
  90. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +15 -0
  91. package/dist/wirings/ai-agent/ai-agent-registry.js +47 -0
  92. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +3 -0
  93. package/dist/wirings/ai-agent/ai-agent-runner.js +96 -0
  94. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +11 -0
  95. package/dist/wirings/ai-agent/ai-agent-stream.js +503 -0
  96. package/dist/wirings/ai-agent/ai-agent.types.d.ts +254 -0
  97. package/dist/wirings/ai-agent/ai-agent.types.js +1 -0
  98. package/dist/wirings/ai-agent/index.d.ts +5 -0
  99. package/dist/wirings/ai-agent/index.js +4 -0
  100. package/dist/wirings/channel/channel-common.d.ts +7 -4
  101. package/dist/wirings/channel/channel-common.js +15 -8
  102. package/dist/wirings/channel/channel-handler.js +13 -4
  103. package/dist/wirings/channel/channel-middleware-runner.d.ts +9 -0
  104. package/dist/wirings/channel/channel-middleware-runner.js +73 -0
  105. package/dist/wirings/channel/channel-runner.js +12 -12
  106. package/dist/wirings/channel/channel.types.d.ts +5 -1
  107. package/dist/wirings/channel/define-channel-routes.d.ts +21 -0
  108. package/dist/wirings/channel/define-channel-routes.js +23 -0
  109. package/dist/wirings/channel/index.d.ts +10 -8
  110. package/dist/wirings/channel/index.js +7 -8
  111. package/dist/wirings/channel/local/index.d.ts +3 -3
  112. package/dist/wirings/channel/local/index.js +3 -3
  113. package/dist/wirings/channel/local/local-channel-runner.js +18 -6
  114. package/dist/wirings/channel/log-channels.js +1 -1
  115. package/dist/wirings/channel/serverless/index.d.ts +1 -1
  116. package/dist/wirings/channel/serverless/index.js +1 -1
  117. package/dist/wirings/channel/serverless/serverless-channel-runner.js +17 -6
  118. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  119. package/dist/wirings/cli/channel/index.d.ts +1 -1
  120. package/dist/wirings/cli/channel/index.js +1 -1
  121. package/dist/wirings/cli/cli-runner.d.ts +1 -1
  122. package/dist/wirings/cli/cli-runner.js +22 -18
  123. package/dist/wirings/cli/cli.types.d.ts +7 -6
  124. package/dist/wirings/cli/define-cli-commands.d.ts +19 -0
  125. package/dist/wirings/cli/define-cli-commands.js +20 -0
  126. package/dist/wirings/cli/index.d.ts +2 -3
  127. package/dist/wirings/cli/index.js +1 -3
  128. package/dist/wirings/http/http-routes.d.ts +69 -0
  129. package/dist/wirings/http/http-routes.js +139 -0
  130. package/dist/wirings/http/http-runner.d.ts +2 -2
  131. package/dist/wirings/http/http-runner.js +62 -27
  132. package/dist/wirings/http/http.types.d.ts +77 -24
  133. package/dist/wirings/http/index.d.ts +5 -4
  134. package/dist/wirings/http/index.js +4 -3
  135. package/dist/wirings/http/log-http-routes.js +1 -1
  136. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  137. package/dist/wirings/mcp/index.d.ts +4 -3
  138. package/dist/wirings/mcp/index.js +3 -3
  139. package/dist/wirings/mcp/mcp-runner.d.ts +1 -3
  140. package/dist/wirings/mcp/mcp-runner.js +42 -56
  141. package/dist/wirings/mcp/mcp.types.d.ts +3 -3
  142. package/dist/wirings/node/index.d.ts +2 -0
  143. package/dist/wirings/node/index.js +1 -0
  144. package/dist/wirings/node/node.types.d.ts +20 -0
  145. package/dist/wirings/node/node.types.js +1 -0
  146. package/dist/wirings/oauth2/index.d.ts +3 -0
  147. package/dist/wirings/oauth2/index.js +2 -0
  148. package/dist/wirings/oauth2/oauth2-client.d.ts +47 -0
  149. package/dist/wirings/oauth2/oauth2-client.js +267 -0
  150. package/dist/wirings/oauth2/oauth2.types.d.ts +65 -0
  151. package/dist/wirings/oauth2/oauth2.types.js +4 -0
  152. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +20 -0
  153. package/dist/wirings/oauth2/wire-oauth2-credential.js +21 -0
  154. package/dist/wirings/queue/index.d.ts +2 -2
  155. package/dist/wirings/queue/index.js +1 -1
  156. package/dist/wirings/queue/queue-runner.d.ts +7 -0
  157. package/dist/wirings/queue/queue-runner.js +23 -12
  158. package/dist/wirings/queue/queue.types.d.ts +8 -2
  159. package/dist/wirings/rpc/index.d.ts +1 -1
  160. package/dist/wirings/rpc/index.js +1 -1
  161. package/dist/wirings/rpc/rpc-runner.d.ts +40 -13
  162. package/dist/wirings/rpc/rpc-runner.js +191 -25
  163. package/dist/wirings/rpc/rpc-types.d.ts +15 -4
  164. package/dist/wirings/scheduler/index.d.ts +3 -3
  165. package/dist/wirings/scheduler/index.js +2 -3
  166. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  167. package/dist/wirings/scheduler/scheduler-runner.d.ts +5 -0
  168. package/dist/wirings/scheduler/scheduler-runner.js +28 -10
  169. package/dist/wirings/secret/index.d.ts +3 -0
  170. package/dist/wirings/secret/index.js +2 -0
  171. package/dist/wirings/secret/secret.types.d.ts +27 -0
  172. package/dist/wirings/secret/secret.types.js +1 -0
  173. package/dist/wirings/secret/validate-secret-definitions.d.ts +6 -0
  174. package/dist/wirings/secret/validate-secret-definitions.js +55 -0
  175. package/dist/wirings/trigger/index.d.ts +3 -0
  176. package/dist/wirings/trigger/index.js +2 -0
  177. package/dist/wirings/trigger/pikku-trigger-service.d.ts +26 -0
  178. package/dist/wirings/trigger/pikku-trigger-service.js +75 -0
  179. package/dist/wirings/trigger/trigger-runner.d.ts +42 -0
  180. package/dist/wirings/trigger/trigger-runner.js +85 -0
  181. package/dist/wirings/trigger/trigger.types.d.ts +130 -0
  182. package/dist/wirings/trigger/trigger.types.js +40 -0
  183. package/dist/wirings/variable/index.d.ts +3 -0
  184. package/dist/wirings/variable/index.js +2 -0
  185. package/dist/wirings/variable/validate-variable-definitions.d.ts +6 -0
  186. package/dist/wirings/variable/validate-variable-definitions.js +45 -0
  187. package/dist/wirings/variable/variable.types.d.ts +18 -0
  188. package/dist/wirings/variable/variable.types.js +1 -0
  189. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  190. package/dist/wirings/workflow/dsl/index.js +4 -0
  191. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +292 -0
  192. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  193. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  194. package/dist/wirings/workflow/dsl/workflow-runner.js +23 -0
  195. package/dist/wirings/workflow/graph/graph-node.d.ts +117 -0
  196. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  197. package/dist/wirings/workflow/graph/graph-runner.d.ts +9 -0
  198. package/dist/wirings/workflow/graph/graph-runner.js +540 -0
  199. package/dist/wirings/workflow/graph/index.d.ts +4 -0
  200. package/dist/wirings/workflow/graph/index.js +3 -0
  201. package/dist/wirings/workflow/graph/template.d.ts +15 -0
  202. package/dist/wirings/workflow/graph/template.js +27 -0
  203. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +15 -0
  204. package/dist/wirings/workflow/graph/wire-workflow-graph.js +8 -0
  205. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +82 -0
  206. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  207. package/dist/wirings/workflow/index.d.ts +7 -12
  208. package/dist/wirings/workflow/index.js +8 -11
  209. package/dist/wirings/workflow/pikku-workflow-service.d.ts +114 -10
  210. package/dist/wirings/workflow/pikku-workflow-service.js +386 -115
  211. package/dist/wirings/workflow/workflow-helpers.d.ts +36 -0
  212. package/dist/wirings/workflow/workflow-helpers.js +38 -0
  213. package/dist/wirings/workflow/workflow-utils.d.ts +1 -0
  214. package/dist/wirings/workflow/workflow-utils.js +1 -0
  215. package/dist/wirings/workflow/workflow.types.d.ts +83 -214
  216. package/package.json +13 -6
  217. package/run-tests.sh +4 -1
  218. package/src/crypto-utils.ts +99 -0
  219. package/src/errors/error-handler.ts +3 -3
  220. package/src/errors/errors.ts +17 -0
  221. package/src/errors/index.ts +0 -1
  222. package/src/factory-functions.test.ts +42 -2
  223. package/src/function/function-runner.test.ts +5 -12
  224. package/src/function/function-runner.ts +195 -40
  225. package/src/function/functions.types.ts +66 -14
  226. package/src/function/index.ts +5 -2
  227. package/src/handle-error.ts +2 -10
  228. package/src/index.ts +106 -27
  229. package/src/middleware/auth-apikey.test.ts +15 -12
  230. package/src/middleware/auth-apikey.ts +3 -3
  231. package/src/middleware/auth-bearer.test.ts +17 -14
  232. package/src/middleware/auth-bearer.ts +25 -27
  233. package/src/middleware/auth-cookie.test.ts +16 -13
  234. package/src/middleware/auth-cookie.ts +6 -8
  235. package/src/middleware/cors.ts +96 -0
  236. package/src/middleware/index.ts +5 -3
  237. package/src/middleware/remote-auth.ts +68 -0
  238. package/src/middleware-runner.test.ts +122 -0
  239. package/src/middleware-runner.ts +24 -17
  240. package/src/permissions.test.ts +102 -86
  241. package/src/permissions.ts +32 -93
  242. package/src/pikku-state.ts +168 -201
  243. package/src/run-tests-script.test.ts +49 -0
  244. package/src/schema.ts +50 -18
  245. package/src/services/ai-agent-runner-service.ts +29 -0
  246. package/src/services/ai-run-state-service.ts +20 -0
  247. package/src/services/ai-storage-service.ts +39 -0
  248. package/src/services/deployment-service.ts +22 -0
  249. package/src/services/gopass-secrets.ts +95 -0
  250. package/src/services/in-memory-trigger-service.ts +68 -0
  251. package/src/services/in-memory-workflow-service.ts +395 -0
  252. package/src/services/index.ts +49 -14
  253. package/src/services/local-secrets.ts +51 -0
  254. package/src/services/local-variables.ts +22 -0
  255. package/src/services/scheduler-service.ts +29 -1
  256. package/src/services/schema-service.ts +7 -0
  257. package/src/services/scoped-secret-service.ts +41 -0
  258. package/src/services/secret-service.ts +25 -4
  259. package/src/services/trigger-service.ts +38 -0
  260. package/src/services/typed-secret-service.ts +70 -0
  261. package/src/services/typed-variables-service.ts +81 -0
  262. package/src/services/user-session-service.ts +23 -0
  263. package/src/services/variables-service.ts +5 -0
  264. package/src/services/workflow-service.ts +94 -0
  265. package/src/types/core.types.ts +82 -8
  266. package/src/types/state.types.ts +169 -0
  267. package/src/utils.ts +74 -5
  268. package/src/version.test.ts +80 -0
  269. package/src/version.ts +25 -0
  270. package/src/wirings/ai-agent/ai-agent-memory.ts +300 -0
  271. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  272. package/src/wirings/ai-agent/ai-agent-prepare.ts +470 -0
  273. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  274. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  275. package/src/wirings/ai-agent/ai-agent-runner.test.ts +75 -0
  276. package/src/wirings/ai-agent/ai-agent-runner.ts +137 -0
  277. package/src/wirings/ai-agent/ai-agent-stream.test.ts +246 -0
  278. package/src/wirings/ai-agent/ai-agent-stream.ts +774 -0
  279. package/src/wirings/ai-agent/ai-agent.types.ts +273 -0
  280. package/src/wirings/ai-agent/index.ts +28 -0
  281. package/src/wirings/channel/channel-common.ts +32 -19
  282. package/src/wirings/channel/channel-handler.ts +18 -5
  283. package/src/wirings/channel/channel-middleware-runner.ts +119 -0
  284. package/src/wirings/channel/channel-runner.ts +14 -15
  285. package/src/wirings/channel/channel.types.ts +17 -1
  286. package/src/wirings/channel/define-channel-routes.ts +25 -0
  287. package/src/wirings/channel/index.ts +23 -8
  288. package/src/wirings/channel/local/index.ts +3 -3
  289. package/src/wirings/channel/local/local-channel-runner.test.ts +47 -2
  290. package/src/wirings/channel/local/local-channel-runner.ts +22 -6
  291. package/src/wirings/channel/log-channels.ts +1 -1
  292. package/src/wirings/channel/serverless/index.ts +5 -1
  293. package/src/wirings/channel/serverless/serverless-channel-runner.ts +22 -6
  294. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  295. package/src/wirings/cli/channel/index.ts +1 -1
  296. package/src/wirings/cli/cli-runner.test.ts +28 -28
  297. package/src/wirings/cli/cli-runner.ts +27 -19
  298. package/src/wirings/cli/cli.types.ts +12 -11
  299. package/src/wirings/cli/command-parser.test.ts +8 -8
  300. package/src/wirings/cli/define-cli-commands.ts +24 -0
  301. package/src/wirings/cli/index.ts +10 -4
  302. package/src/wirings/http/http-routes.test.ts +322 -0
  303. package/src/wirings/http/http-routes.ts +198 -0
  304. package/src/wirings/http/http-runner.test.ts +5 -5
  305. package/src/wirings/http/http-runner.ts +80 -29
  306. package/src/wirings/http/http.types.ts +121 -23
  307. package/src/wirings/http/index.ts +16 -4
  308. package/src/wirings/http/log-http-routes.ts +1 -1
  309. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  310. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  311. package/src/wirings/mcp/index.ts +27 -3
  312. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +3 -3
  313. package/src/wirings/mcp/mcp-runner.ts +56 -67
  314. package/src/wirings/mcp/mcp.types.ts +3 -3
  315. package/src/wirings/node/index.ts +2 -0
  316. package/src/wirings/node/node.types.ts +23 -0
  317. package/src/wirings/oauth2/index.ts +3 -0
  318. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  319. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  320. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  321. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  322. package/src/wirings/queue/index.ts +14 -2
  323. package/src/wirings/queue/queue-runner.test.ts +115 -73
  324. package/src/wirings/queue/queue-runner.ts +39 -13
  325. package/src/wirings/queue/queue.types.ts +12 -2
  326. package/src/wirings/rpc/index.ts +1 -1
  327. package/src/wirings/rpc/rpc-runner.ts +270 -34
  328. package/src/wirings/rpc/rpc-types.ts +21 -4
  329. package/src/wirings/scheduler/index.ts +7 -4
  330. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  331. package/src/wirings/scheduler/scheduler-runner.test.ts +97 -53
  332. package/src/wirings/scheduler/scheduler-runner.ts +50 -10
  333. package/src/wirings/secret/index.ts +9 -0
  334. package/src/wirings/secret/secret.types.ts +32 -0
  335. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  336. package/src/wirings/trigger/index.ts +10 -0
  337. package/src/wirings/trigger/pikku-trigger-service.ts +134 -0
  338. package/src/wirings/trigger/trigger-runner.ts +138 -0
  339. package/src/wirings/trigger/trigger.types.ts +178 -0
  340. package/src/wirings/variable/index.ts +8 -0
  341. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  342. package/src/wirings/variable/variable.types.ts +22 -0
  343. package/src/wirings/workflow/dsl/index.ts +31 -0
  344. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  345. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  346. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  347. package/src/wirings/workflow/graph/graph-runner.test.ts +495 -0
  348. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  349. package/src/wirings/workflow/graph/index.ts +14 -0
  350. package/src/wirings/workflow/graph/template.ts +42 -0
  351. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  352. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  353. package/src/wirings/workflow/index.ts +64 -26
  354. package/src/wirings/workflow/pikku-workflow-service.test.ts +212 -0
  355. package/src/wirings/workflow/pikku-workflow-service.ts +571 -136
  356. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  357. package/src/wirings/workflow/workflow-utils.ts +0 -0
  358. package/src/wirings/workflow/workflow.types.ts +123 -279
  359. package/tsconfig.tsbuildinfo +1 -1
  360. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  361. package/dist/wirings/workflow/workflow-runner.js +0 -65
  362. package/src/function/function-runner.ts.bak +0 -188
  363. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -0,0 +1,96 @@
1
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
2
+
3
+ /**
4
+ * CORS middleware that handles cross-origin requests including OPTIONS preflight.
5
+ *
6
+ * Sets appropriate CORS headers on all responses and short-circuits OPTIONS
7
+ * preflight requests with a 204 No Content response.
8
+ *
9
+ * @param options.origin - Allowed origin(s). Use `'*'` for any origin, a string for a single origin, or an array for multiple origins. Defaults to `'*'`.
10
+ * @param options.methods - Allowed HTTP methods. Defaults to common methods.
11
+ * @param options.headers - Allowed request headers. Defaults to common headers.
12
+ * @param options.credentials - Whether to allow credentials. Defaults to `false`.
13
+ * @param options.maxAge - Preflight cache duration in seconds. Defaults to `86400` (24 hours).
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { cors } from '@pikku/core/middleware'
18
+ *
19
+ * // Allow all origins
20
+ * addHTTPMiddleware('*', [cors()])
21
+ *
22
+ * // Specific origin with credentials
23
+ * addHTTPMiddleware('*', [
24
+ * cors({
25
+ * origin: 'https://app.example.com',
26
+ * credentials: true,
27
+ * })
28
+ * ])
29
+ *
30
+ * // Multiple origins
31
+ * addHTTPMiddleware('*', [
32
+ * cors({
33
+ * origin: ['https://app.example.com', 'https://admin.example.com'],
34
+ * })
35
+ * ])
36
+ * ```
37
+ */
38
+ export const cors = pikkuMiddlewareFactory<{
39
+ origin?: string | string[]
40
+ methods?: string[]
41
+ headers?: string[]
42
+ credentials?: boolean
43
+ maxAge?: number
44
+ }>(
45
+ ({
46
+ origin = '*',
47
+ methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
48
+ headers = ['Content-Type', 'Authorization', 'x-api-key'],
49
+ credentials = false,
50
+ maxAge = 86400,
51
+ } = {}) =>
52
+ pikkuMiddleware({
53
+ name: 'CORS',
54
+ description: 'Handles cross-origin requests including OPTIONS preflight',
55
+ func: async (_services, wires, next) => {
56
+ const request = wires.http?.request
57
+ const response = wires.http?.response
58
+
59
+ if (!request || !response) {
60
+ return next()
61
+ }
62
+
63
+ const requestOrigin = request.header('origin')
64
+
65
+ let allowedOrigin: string
66
+ if (Array.isArray(origin)) {
67
+ allowedOrigin =
68
+ requestOrigin && origin.includes(requestOrigin)
69
+ ? requestOrigin
70
+ : origin[0]
71
+ } else {
72
+ allowedOrigin = origin
73
+ }
74
+
75
+ response.header('Access-Control-Allow-Origin', allowedOrigin)
76
+ response.header('Access-Control-Allow-Methods', methods.join(', '))
77
+ response.header('Access-Control-Allow-Headers', headers.join(', '))
78
+
79
+ if (credentials) {
80
+ response.header('Access-Control-Allow-Credentials', 'true')
81
+ }
82
+
83
+ if (Array.isArray(origin)) {
84
+ response.header('Vary', 'Origin')
85
+ }
86
+
87
+ if (request.method() === 'options') {
88
+ response.header('Access-Control-Max-Age', String(maxAge))
89
+ response.status(204).json(undefined as any)
90
+ return
91
+ }
92
+
93
+ return next()
94
+ },
95
+ })
96
+ )
@@ -1,3 +1,5 @@
1
- export * from './auth-apikey.js'
2
- export * from './auth-cookie.js'
3
- export * from './auth-bearer.js'
1
+ export { authAPIKey } from './auth-apikey.js'
2
+ export { authCookie } from './auth-cookie.js'
3
+ export { authBearer } from './auth-bearer.js'
4
+ export { pikkuRemoteAuthMiddleware } from './remote-auth.js'
5
+ export { cors } from './cors.js'
@@ -0,0 +1,68 @@
1
+ import { UnauthorizedError } from '../errors/errors.js'
2
+ import { pikkuMiddleware } from '../types/core.types.js'
3
+ import { decryptJSON } from '../crypto-utils.js'
4
+
5
+ export const pikkuRemoteAuthMiddleware = pikkuMiddleware(
6
+ async ({ secrets, jwt }, { http, setSession }, next) => {
7
+ if (!http?.request || !secrets) {
8
+ return next()
9
+ }
10
+
11
+ let secret: string
12
+ try {
13
+ secret = await secrets.getSecret('PIKKU_REMOTE_SECRET')
14
+ } catch {
15
+ return next()
16
+ }
17
+ if (!jwt) {
18
+ throw new Error('PIKKU_REMOTE_SECRET set but JWT service missing')
19
+ }
20
+
21
+ const authHeader =
22
+ http.request.header('authorization') ||
23
+ http.request.header('Authorization')
24
+
25
+ if (!authHeader) {
26
+ throw new UnauthorizedError()
27
+ }
28
+
29
+ const [scheme, token] = authHeader.split(' ')
30
+ if (scheme !== 'Bearer' || !token) {
31
+ throw new UnauthorizedError()
32
+ }
33
+
34
+ let payload: any
35
+ try {
36
+ payload = await jwt.decode(token)
37
+ } catch {
38
+ throw new UnauthorizedError()
39
+ }
40
+
41
+ if (payload?.aud !== 'pikku-remote') {
42
+ throw new UnauthorizedError()
43
+ }
44
+
45
+ if (payload?.fn && http.request.path().startsWith('/rpc/')) {
46
+ const fn = decodeURIComponent(http.request.path().slice('/rpc/'.length))
47
+ if (fn && payload.fn !== fn) {
48
+ throw new UnauthorizedError()
49
+ }
50
+ }
51
+
52
+ if (payload?.session) {
53
+ try {
54
+ const decrypted = await decryptJSON<{ session?: unknown }>(
55
+ secret,
56
+ payload.session
57
+ )
58
+ if (decrypted?.session && setSession) {
59
+ await setSession(decrypted.session)
60
+ }
61
+ } catch {
62
+ throw new UnauthorizedError()
63
+ }
64
+ }
65
+
66
+ return next()
67
+ }
68
+ )
@@ -189,6 +189,128 @@ describe('combineMiddleware', () => {
189
189
  assert.equal(result[1], middleware2)
190
190
  })
191
191
 
192
+ test('should apply parent tag middleware to namespaced tag', () => {
193
+ const billingMiddleware: CorePikkuMiddleware = async (
194
+ services,
195
+ wire,
196
+ next
197
+ ) => {
198
+ await next()
199
+ }
200
+
201
+ addMiddleware('billing', [billingMiddleware])
202
+
203
+ const result = combineMiddleware('http', Math.random().toString(), {
204
+ wireInheritedMiddleware: [{ type: 'tag', tag: 'billing:read' }],
205
+ })
206
+
207
+ assert.equal(result.length, 1)
208
+ assert.equal(result[0], billingMiddleware)
209
+ })
210
+
211
+ test('should apply exact tag middleware for non-namespaced tag', () => {
212
+ const billingMiddleware: CorePikkuMiddleware = async (
213
+ services,
214
+ wire,
215
+ next
216
+ ) => {
217
+ await next()
218
+ }
219
+
220
+ addMiddleware('billing', [billingMiddleware])
221
+
222
+ const result = combineMiddleware('http', Math.random().toString(), {
223
+ wireInheritedMiddleware: [{ type: 'tag', tag: 'billing' }],
224
+ })
225
+
226
+ assert.equal(result.length, 1)
227
+ assert.equal(result[0], billingMiddleware)
228
+ })
229
+
230
+ test('should apply both exact and parent tag middleware (specific first)', () => {
231
+ const billingMiddleware: CorePikkuMiddleware = async (
232
+ services,
233
+ wire,
234
+ next
235
+ ) => {
236
+ await next()
237
+ }
238
+ const billingReadMiddleware: CorePikkuMiddleware = async (
239
+ services,
240
+ wire,
241
+ next
242
+ ) => {
243
+ await next()
244
+ }
245
+
246
+ addMiddleware('billing', [billingMiddleware])
247
+ addMiddleware('billing:read', [billingReadMiddleware])
248
+
249
+ const result = combineMiddleware('http', Math.random().toString(), {
250
+ wireInheritedMiddleware: [{ type: 'tag', tag: 'billing:read' }],
251
+ })
252
+
253
+ assert.equal(result.length, 2)
254
+ assert.equal(result[0], billingReadMiddleware)
255
+ assert.equal(result[1], billingMiddleware)
256
+ })
257
+
258
+ test('should resolve multi-level namespace tags', () => {
259
+ const billingMiddleware: CorePikkuMiddleware = async (
260
+ services,
261
+ wire,
262
+ next
263
+ ) => {
264
+ await next()
265
+ }
266
+ const billingReadMiddleware: CorePikkuMiddleware = async (
267
+ services,
268
+ wire,
269
+ next
270
+ ) => {
271
+ await next()
272
+ }
273
+ const billingReadAdminMiddleware: CorePikkuMiddleware = async (
274
+ services,
275
+ wire,
276
+ next
277
+ ) => {
278
+ await next()
279
+ }
280
+
281
+ addMiddleware('billing', [billingMiddleware])
282
+ addMiddleware('billing:read', [billingReadMiddleware])
283
+ addMiddleware('billing:read:admin', [billingReadAdminMiddleware])
284
+
285
+ const result = combineMiddleware('http', Math.random().toString(), {
286
+ wireInheritedMiddleware: [{ type: 'tag', tag: 'billing:read:admin' }],
287
+ })
288
+
289
+ assert.equal(result.length, 3)
290
+ assert.equal(result[0], billingReadAdminMiddleware)
291
+ assert.equal(result[1], billingReadMiddleware)
292
+ assert.equal(result[2], billingMiddleware)
293
+ })
294
+
295
+ test('should apply parent tag middleware via funcInheritedMiddleware', () => {
296
+ const billingMiddleware: CorePikkuMiddleware = async (
297
+ services,
298
+ wire,
299
+ next
300
+ ) => {
301
+ await next()
302
+ }
303
+
304
+ addMiddleware('billing', [billingMiddleware])
305
+
306
+ const result = combineMiddleware('http', Math.random().toString(), {
307
+ funcInheritedMiddleware: [{ type: 'tag', tag: 'billing:write' }],
308
+ })
309
+
310
+ assert.equal(result.length, 1)
311
+ assert.equal(result[0], billingMiddleware)
312
+ })
313
+
192
314
  test('should ignore non-existent tags', () => {
193
315
  const middleware: CorePikkuMiddleware = async (services, wire, next) => {
194
316
  await next()
@@ -7,7 +7,7 @@ import {
7
7
  MiddlewareMetadata,
8
8
  } from './types/core.types.js'
9
9
  import { pikkuState } from './pikku-state.js'
10
- import { freezeDedupe } from './utils.js'
10
+ import { freezeDedupe, getTagGroups } from './utils.js'
11
11
 
12
12
  /**
13
13
  * Runs a chain of middleware functions in sequence before executing the main function.
@@ -82,9 +82,10 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
82
82
  */
83
83
  export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
84
84
  tag: string,
85
- middleware: CorePikkuMiddlewareGroup
85
+ middleware: CorePikkuMiddlewareGroup,
86
+ packageName: string | null = null
86
87
  ): CorePikkuMiddlewareGroup => {
87
- const tagGroups = pikkuState('middleware', 'tagGroup')
88
+ const tagGroups = pikkuState(packageName, 'middleware', 'tagGroup')
88
89
  tagGroups[tag] = middleware
89
90
  return middleware
90
91
  }
@@ -95,15 +96,13 @@ export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
95
96
  * This function looks up middleware that was registered with registerMiddleware.
96
97
  * It's used internally by the framework to resolve middleware references in metadata.
97
98
  *
98
- * @param {string} name - The unique name (pikkuFuncName) of the middleware function.
99
+ * @param {string} name - The unique name (pikkuFuncId) of the middleware function.
99
100
  * @returns {CorePikkuMiddleware | undefined} The middleware function, or undefined if not found.
100
101
  *
101
102
  * @internal
102
103
  */
103
- export const getMiddlewareByName = (
104
- name: string
105
- ): CorePikkuMiddleware | undefined => {
106
- const middlewareStore = pikkuState('misc', 'middleware')
104
+ const getMiddlewareByName = (name: string): CorePikkuMiddleware | undefined => {
105
+ const middlewareStore = pikkuState(null, 'misc', 'middleware')
107
106
  const middleware = middlewareStore[name]
108
107
  return middleware?.[0]
109
108
  }
@@ -117,7 +116,9 @@ const middlewareCache: Record<
117
116
  channel: {},
118
117
  queue: {},
119
118
  scheduler: {},
119
+ trigger: {},
120
120
  mcp: {},
121
+ agent: {},
121
122
  cli: {},
122
123
  workflow: {},
123
124
  }
@@ -152,11 +153,13 @@ export const combineMiddleware = (
152
153
  wireMiddleware,
153
154
  funcInheritedMiddleware,
154
155
  funcMiddleware,
156
+ packageName = null,
155
157
  }: {
156
158
  wireInheritedMiddleware?: MiddlewareMetadata[]
157
159
  wireMiddleware?: CorePikkuMiddleware[]
158
160
  funcInheritedMiddleware?: MiddlewareMetadata[]
159
161
  funcMiddleware?: CorePikkuMiddleware[]
162
+ packageName?: string | null
160
163
  } = {}
161
164
  ): readonly CorePikkuMiddleware[] => {
162
165
  if (middlewareCache[wireType][uid]) {
@@ -170,16 +173,19 @@ export const combineMiddleware = (
170
173
  for (const meta of wireInheritedMiddleware) {
171
174
  if (meta.type === 'http') {
172
175
  // Look up HTTP middleware group from pikkuState
173
- const group = pikkuState('middleware', 'httpGroup')[meta.route]
176
+ const group = pikkuState(packageName, 'middleware', 'httpGroup')[
177
+ meta.route
178
+ ]
174
179
  if (group) {
175
180
  // At runtime, all factories should be resolved to middleware
176
181
  resolved.push(...(group as CorePikkuMiddleware[]))
177
182
  }
178
183
  } else if (meta.type === 'tag') {
179
- // Look up tag middleware group from pikkuState
180
- const group = pikkuState('middleware', 'tagGroup')[meta.tag]
181
- if (group) {
182
- // At runtime, all factories should be resolved to middleware
184
+ const groups = getTagGroups(
185
+ pikkuState(packageName, 'middleware', 'tagGroup'),
186
+ meta.tag
187
+ )
188
+ for (const group of groups) {
183
189
  resolved.push(...(group as CorePikkuMiddleware[]))
184
190
  }
185
191
  } else if (meta.type === 'wire') {
@@ -201,10 +207,11 @@ export const combineMiddleware = (
201
207
  if (funcInheritedMiddleware) {
202
208
  for (const meta of funcInheritedMiddleware) {
203
209
  if (meta.type === 'tag') {
204
- // Look up tag middleware group from pikkuState
205
- const group = pikkuState('middleware', 'tagGroup')[meta.tag]
206
- if (group) {
207
- // At runtime, all factories should be resolved to middleware
210
+ const groups = getTagGroups(
211
+ pikkuState(packageName, 'middleware', 'tagGroup'),
212
+ meta.tag
213
+ )
214
+ for (const group of groups) {
208
215
  resolved.push(...(group as CorePikkuMiddleware[]))
209
216
  }
210
217
  }
@@ -1,18 +1,39 @@
1
1
  import { describe, test, beforeEach } from 'node:test'
2
2
  import * as assert from 'node:assert'
3
- import {
4
- addPermission,
5
- getPermissionsForTags,
6
- runPermissions,
7
- } from './permissions.js'
8
- import { resetPikkuState } from './pikku-state.js'
3
+ import { addPermission, runPermissions } from './permissions.js'
4
+ import { pikkuState, resetPikkuState } from './pikku-state.js'
9
5
  import { CoreServices, CoreUserSession } from './types/core.types.js'
10
- import { CorePermissionGroup } from './function/functions.types.js'
6
+ import {
7
+ CorePermissionGroup,
8
+ CorePikkuPermission,
9
+ } from './function/functions.types.js'
11
10
 
12
11
  beforeEach(() => {
13
12
  resetPikkuState()
14
13
  })
15
14
 
15
+ // Test helper to get permissions for tags (not exported from core to keep it lean)
16
+ const getPermissionsForTags = (
17
+ tags?: string[]
18
+ ): (CorePermissionGroup | CorePikkuPermission)[] => {
19
+ if (!tags || tags.length === 0) {
20
+ return []
21
+ }
22
+ const permissionsStore = pikkuState(null, 'permissions', 'tagGroup')
23
+ const result: (CorePermissionGroup | CorePikkuPermission)[] = []
24
+ for (const tag of new Set(tags)) {
25
+ const tagPermissions = permissionsStore[tag]
26
+ if (tagPermissions) {
27
+ if (Array.isArray(tagPermissions)) {
28
+ result.push(...tagPermissions)
29
+ } else {
30
+ result.push(tagPermissions)
31
+ }
32
+ }
33
+ }
34
+ return result
35
+ }
36
+
16
37
  const mockServices: CoreServices = {
17
38
  logger: {
18
39
  info: () => {},
@@ -61,99 +82,99 @@ describe('addPermission', () => {
61
82
  assert.equal(permissions[0], mockPermissionGroup)
62
83
  })
63
84
 
64
- test('should throw error when tag already exists', () => {
65
- const mockPermission = async () => true
85
+ test('should apply parent tag permission to namespaced tag via runPermissions', async () => {
86
+ const billingPermission = async () => true
66
87
 
67
- addPermission('duplicateTag', [mockPermission])
88
+ addPermission('billing', [billingPermission])
68
89
 
69
- assert.throws(
70
- () => {
71
- addPermission('duplicateTag', [mockPermission])
72
- },
73
- {
74
- message:
75
- "Permissions for tag 'duplicateTag' already exist. Use a different tag or remove the existing permissions first.",
76
- }
77
- )
90
+ await runPermissions('rpc', Math.random().toString(), {
91
+ wireInheritedPermissions: [{ type: 'tag', tag: 'billing:write' }],
92
+ services: mockServices,
93
+ wire: {},
94
+ data: {},
95
+ })
78
96
  })
79
- })
80
97
 
81
- describe('getPermissionsForTags', () => {
82
- test('should return empty array when no tags provided', () => {
83
- const permissions = getPermissionsForTags([])
84
- assert.deepEqual(permissions, [])
85
- })
98
+ test('should apply both parent and exact tag permissions', async () => {
99
+ const executionOrder: string[] = []
100
+ const billingPermission = async () => {
101
+ executionOrder.push('billing')
102
+ return true
103
+ }
104
+ const billingReadPermission = async () => {
105
+ executionOrder.push('billing:read')
106
+ return true
107
+ }
86
108
 
87
- test('should return empty array when tags is undefined', () => {
88
- const permissions = getPermissionsForTags(undefined)
89
- assert.deepEqual(permissions, [])
90
- })
109
+ addPermission('billing', [billingPermission])
110
+ addPermission('billing:read', [billingReadPermission])
91
111
 
92
- test('should return permissions for single tag', () => {
93
- const mockPermission = async () => true
94
- addPermission('singleTestTag', [mockPermission])
112
+ await runPermissions('rpc', Math.random().toString(), {
113
+ wireInheritedPermissions: [{ type: 'tag', tag: 'billing:read' }],
114
+ services: mockServices,
115
+ wire: {},
116
+ data: {},
117
+ })
95
118
 
96
- const permissions = getPermissionsForTags(['singleTestTag'])
97
- assert.equal(permissions.length, 1)
98
- assert.equal(permissions[0], mockPermission)
119
+ assert.deepEqual(executionOrder, ['billing:read'])
99
120
  })
100
121
 
101
- test('should return permissions for multiple tags', () => {
102
- const mockPermission1 = async () => true
103
- const mockPermission2 = async () => false
104
-
105
- addPermission('tag1', [mockPermission1])
106
- addPermission('tag2', [mockPermission2])
107
-
108
- const permissions = getPermissionsForTags(['tag1', 'tag2'])
109
- assert.equal(permissions.length, 2)
110
- assert.equal(permissions[0], mockPermission1)
111
- assert.equal(permissions[1], mockPermission2)
112
- })
122
+ test('should resolve multi-level namespace permissions', async () => {
123
+ const executionOrder: string[] = []
124
+ const billingPermission = async () => {
125
+ executionOrder.push('billing')
126
+ return false
127
+ }
128
+ const billingReadPermission = async () => {
129
+ executionOrder.push('billing:read')
130
+ return false
131
+ }
132
+ const billingReadAdminPermission = async () => {
133
+ executionOrder.push('billing:read:admin')
134
+ return true
135
+ }
113
136
 
114
- test('should handle array permissions correctly', () => {
115
- const mockPermission1 = async () => true
116
- const mockPermission2 = async () => false
137
+ addPermission('billing', [billingPermission])
138
+ addPermission('billing:read', [billingReadPermission])
139
+ addPermission('billing:read:admin', [billingReadAdminPermission])
117
140
 
118
- addPermission('arrayHandleTestTag', [mockPermission1, mockPermission2])
141
+ await runPermissions('rpc', Math.random().toString(), {
142
+ wireInheritedPermissions: [{ type: 'tag', tag: 'billing:read:admin' }],
143
+ services: mockServices,
144
+ wire: {},
145
+ data: {},
146
+ })
119
147
 
120
- const permissions = getPermissionsForTags(['arrayHandleTestTag'])
121
- assert.equal(permissions.length, 2)
122
- assert.equal(permissions[0], mockPermission1)
123
- assert.equal(permissions[1], mockPermission2)
148
+ assert.deepEqual(executionOrder, ['billing:read:admin'])
124
149
  })
125
150
 
126
- test('should handle non-array permissions correctly', () => {
127
- const mockPermissionGroup: CorePermissionGroup = {
128
- permissions: [async () => true],
129
- }
151
+ test('should apply parent tag permission via funcInheritedPermissions', async () => {
152
+ const billingPermission = async () => true
130
153
 
131
- addPermission('objectHandleTestTag', mockPermissionGroup)
154
+ addPermission('billing', [billingPermission])
132
155
 
133
- const permissions = getPermissionsForTags(['objectHandleTestTag'])
134
- assert.equal(permissions.length, 1)
135
- assert.equal(permissions[0], mockPermissionGroup)
156
+ await runPermissions('rpc', Math.random().toString(), {
157
+ funcInheritedPermissions: [{ type: 'tag', tag: 'billing:delete' }],
158
+ services: mockServices,
159
+ wire: {},
160
+ data: {},
161
+ })
136
162
  })
137
163
 
138
- test('should deduplicate tags', () => {
164
+ test('should throw error when tag already exists', () => {
139
165
  const mockPermission = async () => true
140
- addPermission('dedupeTestTag', [mockPermission])
141
-
142
- const permissions = getPermissionsForTags([
143
- 'dedupeTestTag',
144
- 'dedupeTestTag',
145
- ])
146
- assert.equal(permissions.length, 1)
147
- assert.equal(permissions[0], mockPermission)
148
- })
149
166
 
150
- test('should ignore non-existent tags', () => {
151
- const mockPermission = async () => true
152
- addPermission('existingTag', [mockPermission])
167
+ addPermission('duplicateTag', [mockPermission])
153
168
 
154
- const permissions = getPermissionsForTags(['existingTag', 'nonExistentTag'])
155
- assert.equal(permissions.length, 1)
156
- assert.equal(permissions[0], mockPermission)
169
+ assert.throws(
170
+ () => {
171
+ addPermission('duplicateTag', [mockPermission])
172
+ },
173
+ {
174
+ message:
175
+ "Permissions for tag 'duplicateTag' already exist. Use a different tag or remove the existing permissions first.",
176
+ }
177
+ )
157
178
  })
158
179
  })
159
180
 
@@ -229,13 +250,8 @@ describe('runPermissions', () => {
229
250
  data: {},
230
251
  })
231
252
 
232
- // Order: wireInheritedPermissions (wiringTag) wirePermissions funcInheritedPermissions (funcTag) → funcPermissions
233
- assert.deepEqual(executionOrder, [
234
- 'wiringTag',
235
- 'wiringPermission',
236
- 'funcTag',
237
- 'funcPermission',
238
- ])
253
+ // Order: wireInheritedPermissions (wiringTag) only (short-circuit on first passing group)
254
+ assert.deepEqual(executionOrder, ['wiringTag'])
239
255
  })
240
256
 
241
257
  test('should implement "at least one must pass" logic for tag permissions', async () => {