@pikku/core 0.12.84 → 0.12.86

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 (564) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/dist/bootstrap-compat/root.d.ts +20 -0
  3. package/dist/classification/column-form.d.ts +32 -0
  4. package/dist/classification/column-form.js +42 -0
  5. package/dist/classification/data-classification.d.ts +64 -0
  6. package/dist/classification/index.d.ts +13 -0
  7. package/dist/classification/index.js +2 -0
  8. package/dist/classification/secret-value.d.ts +56 -0
  9. package/dist/classification/secret-value.js +46 -0
  10. package/dist/crypto-utils.d.ts +1 -1
  11. package/dist/ecosystem/agent-scorer.d.ts +1 -1
  12. package/dist/ecosystem/agent.d.ts +4 -1
  13. package/dist/ecosystem/channel/local.d.ts +2 -0
  14. package/dist/ecosystem/channel/local.js +2 -0
  15. package/dist/ecosystem/channel/serverless.d.ts +9 -0
  16. package/dist/ecosystem/channel/serverless.js +1 -0
  17. package/dist/ecosystem/channel.d.ts +3 -0
  18. package/dist/ecosystem/channel.js +1 -0
  19. package/dist/ecosystem/cli.d.ts +1 -1
  20. package/dist/ecosystem/cli.js +1 -1
  21. package/dist/ecosystem/credential.d.ts +1 -0
  22. package/dist/ecosystem/crypto-utils.d.ts +8 -0
  23. package/dist/ecosystem/crypto-utils.js +1 -0
  24. package/dist/ecosystem/dev.d.ts +1 -0
  25. package/dist/ecosystem/dev.js +1 -0
  26. package/{src/ecosystem/testing.ts → dist/ecosystem/errors.d.ts} +3 -3
  27. package/dist/ecosystem/errors.js +2 -0
  28. package/dist/ecosystem/function.d.ts +1 -1
  29. package/dist/ecosystem/function.js +1 -1
  30. package/dist/ecosystem/http.d.ts +3 -1
  31. package/dist/ecosystem/http.js +3 -1
  32. package/dist/ecosystem/mcp.d.ts +1 -1
  33. package/dist/ecosystem/mcp.js +1 -1
  34. package/dist/ecosystem/middleware.d.ts +2 -1
  35. package/dist/ecosystem/middleware.js +2 -1
  36. package/dist/ecosystem/persona.d.ts +4 -2
  37. package/dist/ecosystem/persona.js +2 -1
  38. package/dist/ecosystem/queue.d.ts +4 -2
  39. package/dist/ecosystem/queue.js +3 -1
  40. package/dist/ecosystem/role.d.ts +1 -0
  41. package/dist/ecosystem/rpc.d.ts +2 -0
  42. package/dist/ecosystem/rpc.js +1 -0
  43. package/dist/ecosystem/scenario.d.ts +8 -2
  44. package/dist/ecosystem/scenario.js +5 -1
  45. package/dist/ecosystem/scheduler.d.ts +2 -1
  46. package/dist/ecosystem/scheduler.js +2 -1
  47. package/dist/ecosystem/schema.d.ts +2 -1
  48. package/dist/ecosystem/schema.js +1 -1
  49. package/dist/ecosystem/scope.d.ts +2 -0
  50. package/dist/ecosystem/scope.js +1 -0
  51. package/dist/ecosystem/secret.d.ts +2 -0
  52. package/dist/ecosystem/secret.js +1 -0
  53. package/dist/ecosystem/services/local-content.d.ts +2 -1
  54. package/dist/ecosystem/services/local-content.js +1 -1
  55. package/dist/ecosystem/services/local-meta.d.ts +1 -0
  56. package/dist/ecosystem/services/local-meta.js +1 -0
  57. package/dist/ecosystem/services.d.ts +11 -3
  58. package/dist/ecosystem/services.js +7 -1
  59. package/dist/ecosystem/trigger.d.ts +1 -0
  60. package/dist/ecosystem/trigger.js +1 -1
  61. package/dist/ecosystem/types.d.ts +6 -3
  62. package/dist/ecosystem/types.js +3 -2
  63. package/dist/ecosystem/variable.d.ts +1 -0
  64. package/dist/ecosystem/virtual-user.d.ts +3 -0
  65. package/dist/ecosystem/virtual-user.js +3 -0
  66. package/dist/ecosystem/workflow.d.ts +2 -0
  67. package/dist/ecosystem/workflow.js +2 -0
  68. package/dist/errors/index.d.ts +8 -0
  69. package/dist/errors/index.js +1 -0
  70. package/dist/errors/serialized-error.d.ts +7 -0
  71. package/dist/function/function-meta.types.d.ts +139 -0
  72. package/dist/function/function-runner.d.ts +3 -2
  73. package/dist/function/function-runner.js +31 -11
  74. package/dist/function/functions.types.d.ts +10 -2
  75. package/dist/function/index.d.ts +8 -2
  76. package/dist/function/index.js +4 -1
  77. package/dist/function/pikku-request.d.ts +8 -0
  78. package/dist/function/pikku-request.js +15 -0
  79. package/dist/middleware/auth-apikey.d.ts +2 -10
  80. package/dist/middleware/auth-apikey.js +1 -1
  81. package/dist/middleware/auth-bearer.d.ts +2 -10
  82. package/dist/middleware/auth-bearer.js +1 -1
  83. package/dist/middleware/auth-cookie.d.ts +2 -10
  84. package/dist/middleware/auth-cookie.js +1 -1
  85. package/dist/middleware/cors.d.ts +5 -11
  86. package/dist/middleware/cors.js +13 -6
  87. package/dist/middleware/index.d.ts +3 -0
  88. package/dist/middleware/index.js +1 -0
  89. package/dist/middleware/middleware-factories.d.ts +9 -0
  90. package/dist/middleware/middleware-factories.js +19 -0
  91. package/dist/middleware/middleware.types.d.ts +27 -0
  92. package/dist/middleware/middleware.types.js +1 -0
  93. package/dist/middleware/remote-auth.d.ts +2 -2
  94. package/dist/middleware/remote-auth.js +1 -1
  95. package/dist/middleware/telemetry.d.ts +4 -20
  96. package/dist/middleware/telemetry.js +1 -1
  97. package/dist/middleware-runner.d.ts +2 -1
  98. package/dist/pikku-state.d.ts +2 -1
  99. package/dist/services/audit-service.d.ts +1 -1
  100. package/dist/services/credential-wire-service.js +1 -1
  101. package/dist/services/email-service.d.ts +1 -1
  102. package/dist/services/file-scenario-run-store.d.ts +58 -0
  103. package/dist/services/file-scenario-run-store.js +214 -0
  104. package/dist/services/in-memory-workflow-service.d.ts +1 -1
  105. package/dist/services/index.d.ts +5 -1
  106. package/dist/services/index.js +3 -0
  107. package/dist/services/local-secrets.d.ts +1 -1
  108. package/dist/services/local-secrets.js +1 -1
  109. package/dist/services/logger.d.ts +7 -7
  110. package/dist/services/meta-service.d.ts +2 -1
  111. package/dist/services/scoped-secret-service.d.ts +1 -1
  112. package/dist/services/secret-service.d.ts +1 -1
  113. package/dist/services/typed-secret-service.d.ts +16 -2
  114. package/dist/services/typed-secret-service.js +5 -0
  115. package/dist/services/webhook-service.d.ts +1 -1
  116. package/dist/services/workflow-service.d.ts +2 -1
  117. package/dist/testing/index.d.ts +1 -0
  118. package/dist/testing/index.js +1 -0
  119. package/dist/types/core.types.d.ts +30 -180
  120. package/dist/types/core.types.js +1 -19
  121. package/dist/types/index.d.ts +2 -0
  122. package/dist/types/index.js +1 -0
  123. package/dist/types/state.types.d.ts +3 -1
  124. package/dist/utils.d.ts +13 -0
  125. package/dist/utils.js +2 -0
  126. package/dist/wirings/addon/addon-runner.d.ts +17 -0
  127. package/dist/wirings/addon/addon-runner.js +150 -0
  128. package/dist/wirings/addon/index.d.ts +6 -0
  129. package/dist/wirings/addon/index.js +3 -0
  130. package/dist/wirings/addon/remote-addon-auth.d.ts +16 -0
  131. package/dist/wirings/addon/remote-addon-auth.js +39 -0
  132. package/dist/wirings/addon/wire-addon.d.ts +80 -0
  133. package/dist/wirings/addon/wire-addon.js +120 -0
  134. package/dist/wirings/addon/wire-remote-addon.d.ts +22 -0
  135. package/dist/wirings/addon/wire-remote-addon.js +11 -0
  136. package/dist/wirings/agent/agent-prepare.d.ts +3 -3
  137. package/dist/wirings/agent/agent-prepare.js +1 -1
  138. package/dist/wirings/agent/agent-runner.js +2 -3
  139. package/dist/wirings/agent/agent-stream.js +2 -2
  140. package/dist/wirings/agent/agent-utils.d.ts +13 -0
  141. package/dist/wirings/agent/agent-utils.js +16 -0
  142. package/dist/wirings/agent/agent.types.d.ts +3 -2
  143. package/dist/wirings/agent/index.d.ts +1 -1
  144. package/dist/wirings/agent/voice-input.js +1 -1
  145. package/dist/wirings/agent/voice-output.js +1 -1
  146. package/dist/wirings/agent-scorer/agent-scorer-judge.d.ts +13 -0
  147. package/dist/wirings/agent-scorer/agent-scorer-judge.js +52 -0
  148. package/dist/wirings/agent-scorer/agent-scorer.d.ts +7 -1
  149. package/dist/wirings/agent-scorer/agent-scorer.js +1 -0
  150. package/dist/wirings/agent-scorer/agent-scorer.types.d.ts +20 -0
  151. package/dist/wirings/agent-scorer/index.d.ts +1 -1
  152. package/dist/wirings/channel/channel-common.d.ts +2 -1
  153. package/dist/wirings/channel/channel-middleware-runner.d.ts +2 -1
  154. package/dist/wirings/channel/channel.types.d.ts +1 -1
  155. package/dist/wirings/channel/serverless/index.d.ts +1 -0
  156. package/dist/wirings/cli/channel/index.d.ts +1 -1
  157. package/dist/wirings/cli/cli-runner.d.ts +2 -1
  158. package/dist/wirings/cli/cli-runner.js +4 -1
  159. package/dist/wirings/cli/cli.types.d.ts +21 -2
  160. package/dist/wirings/cli/command-parser.js +157 -23
  161. package/dist/wirings/gateway/gateway.types.d.ts +2 -1
  162. package/dist/wirings/http/http-runner.d.ts +1 -1
  163. package/dist/wirings/http/http-runner.js +1 -1
  164. package/dist/wirings/http/http.types.d.ts +2 -2
  165. package/dist/wirings/http/index.d.ts +1 -0
  166. package/dist/wirings/http/index.js +1 -0
  167. package/dist/wirings/http/pikku-fetch-http-request.js +1 -1
  168. package/dist/wirings/http/pikku-fetch-http-response.js +2 -2
  169. package/dist/wirings/mcp/mcp.types.d.ts +1 -1
  170. package/dist/wirings/persona/index.d.ts +1 -1
  171. package/dist/wirings/persona/index.js +1 -1
  172. package/dist/wirings/persona/persona.types.d.ts +14 -0
  173. package/dist/wirings/queue/queue.types.d.ts +1 -1
  174. package/dist/wirings/queue/signed-queue-service.d.ts +1 -1
  175. package/dist/wirings/rpc/index.d.ts +0 -6
  176. package/dist/wirings/rpc/index.js +0 -3
  177. package/dist/wirings/rpc/remote-addon-auth.d.ts +1 -1
  178. package/dist/wirings/rpc/remote-addon-auth.js +1 -1
  179. package/dist/wirings/rpc/rpc-runner.js +2 -2
  180. package/dist/wirings/rpc/wire-addon.d.ts +1 -1
  181. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -1
  182. package/dist/wirings/scope/index.d.ts +1 -0
  183. package/dist/wirings/scope/index.js +1 -0
  184. package/dist/wirings/secret/secret.types.d.ts +10 -0
  185. package/dist/wirings/secret/validate-secret-definitions.js +2 -0
  186. package/dist/wirings/variable/validate-variable-definitions.js +2 -0
  187. package/dist/wirings/variable/variable.types.d.ts +9 -0
  188. package/dist/wirings/virtual-user/index.d.ts +1 -1
  189. package/dist/wirings/virtual-user/index.js +1 -1
  190. package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +1 -1
  191. package/dist/wirings/virtual-user/virtual-user-derive.d.ts +1 -1
  192. package/dist/wirings/workflow/graph/graph-node.d.ts +9 -2
  193. package/dist/wirings/workflow/graph/graph-node.js +2 -0
  194. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -0
  195. package/dist/wirings/workflow/graph/graph-runner.js +283 -31
  196. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +16 -1
  197. package/dist/wirings/workflow/index.d.ts +2 -0
  198. package/dist/wirings/workflow/index.js +1 -0
  199. package/dist/wirings/workflow/pikku-scenario-service.d.ts +14 -4
  200. package/dist/wirings/workflow/pikku-scenario-service.js +44 -16
  201. package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -1
  202. package/dist/wirings/workflow/pikku-workflow-service.js +3 -4
  203. package/dist/wirings/workflow/run-timeline.d.ts +1 -1
  204. package/dist/wirings/workflow/scenario-run.types.d.ts +171 -0
  205. package/dist/wirings/workflow/scenario-run.types.js +1 -0
  206. package/dist/wirings/workflow/scenario-step-guards.d.ts +1 -2
  207. package/dist/wirings/workflow/scenario-step-guards.js +0 -8
  208. package/dist/wirings/workflow/scenario-step.types.d.ts +27 -12
  209. package/dist/wirings/workflow/workflow-approval.js +2 -1
  210. package/dist/wirings/workflow/workflow-run-engine.types.d.ts +2 -1
  211. package/dist/wirings/workflow/workflow.types.d.ts +2 -1
  212. package/knowledge/decisions/index.md +2 -0
  213. package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +1 -1
  214. package/knowledge/decisions/internals/a-secret-that-fails-to-decrypt-fails-the-whole-read.md +1 -1
  215. package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-and-not-a-queued-job.md +2 -2
  216. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +6 -6
  217. package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +1 -1
  218. package/knowledge/decisions/internals/addon-singleton-services-are-cached-per-namespace-not-per-package.md +1 -1
  219. package/knowledge/decisions/internals/addon-workflow-names-are-prefixed-with-the-consumer-namespace.md +1 -1
  220. package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +1 -1
  221. package/knowledge/decisions/internals/channel-message-handlers-accept-three-config-shapes.md +1 -1
  222. package/knowledge/decisions/internals/channel-middleware-caches-only-statically-resolved-middleware.md +1 -1
  223. package/knowledge/decisions/internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md +1 -1
  224. package/knowledge/decisions/internals/cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md +1 -1
  225. package/knowledge/decisions/internals/cli-parse-errors-are-routed-by-message-prefix.md +1 -1
  226. package/knowledge/decisions/internals/cli-unknown-long-options-warn-instead-of-failing.md +1 -1
  227. package/knowledge/decisions/internals/core-column-form-is-an-axis-of-its-own.md +4 -4
  228. package/knowledge/decisions/internals/core-data-classification-brand-is-an-optional-property.md +1 -1
  229. package/knowledge/decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md +3 -3
  230. package/knowledge/decisions/internals/core-hot-reload-merges-generated-meta-never-replaces-it.md +2 -2
  231. package/knowledge/decisions/internals/core-hot-reload-owns-its-module-registry.md +2 -2
  232. package/knowledge/decisions/internals/core-middleware-order-is-scope-then-priority.md +1 -1
  233. package/knowledge/decisions/internals/core-schema-defaults-apply-on-every-transport.md +1 -1
  234. package/knowledge/decisions/internals/core-scopes-are-an-and-gate-separate-from-permissions.md +3 -3
  235. package/knowledge/decisions/internals/gateway-adapters-resolve-lazily-and-are-promise-cached.md +1 -1
  236. package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +4 -4
  237. package/knowledge/decisions/internals/gateway-wiring-is-a-meta-wiring-over-http-and-channels.md +1 -1
  238. package/knowledge/decisions/internals/http-router-matches-normalized-paths-but-returns-registered-ones.md +1 -1
  239. package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +1 -1
  240. package/knowledge/decisions/internals/in-memory-workflow-history-aliases-the-live-step-object.md +1 -1
  241. package/knowledge/decisions/internals/index.md +4 -2
  242. package/knowledge/decisions/internals/istanbul-statement-counts-attach-to-the-start-line-only.md +1 -1
  243. package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +1 -1
  244. package/knowledge/decisions/internals/queue-jobs-always-carry-an-explicit-attempts-count.md +1 -1
  245. package/knowledge/decisions/internals/remote-addons-dispatch-over-http-instead-of-local-meta.md +1 -1
  246. package/knowledge/decisions/internals/rpc-names-resolve-through-package-scope-before-root.md +1 -1
  247. package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +2 -2
  248. package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +5 -5
  249. package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +1 -1
  250. package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +1 -1
  251. package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +43 -19
  252. package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +1 -1
  253. package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +1 -1
  254. package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +3 -3
  255. package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +1 -1
  256. package/knowledge/decisions/internals/webhook-delivery-history-records-every-attempt-best-effort.md +1 -1
  257. package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +1 -1
  258. package/knowledge/decisions/internals/workflow-approval-expiry-is-decided-from-a-recorded-deadline.md +1 -1
  259. package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +1 -1
  260. package/knowledge/decisions/internals/workflow-inline-runs-report-their-run-id-before-they-can-fail.md +1 -1
  261. package/knowledge/decisions/internals/workflow-invocation-id-is-the-dedupe-key-not-step-id.md +1 -1
  262. package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +1 -1
  263. package/knowledge/decisions/internals/workflow-run-timeline-is-a-pure-fold-over-durable-history.md +1 -1
  264. package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +9 -5
  265. package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
  266. package/knowledge/decisions/internals/workflow-step-rpc-name-is-provenance-only.md +2 -2
  267. package/knowledge/decisions/internals/workflow-suspend-and-approval-reasons-are-durable-step-identities.md +1 -1
  268. package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +1 -1
  269. package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +1 -1
  270. package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +2 -3
  271. package/knowledge/decisions/security/actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md +1 -1
  272. package/knowledge/decisions/security/addon-auth-and-tags-only-tighten.md +1 -1
  273. package/knowledge/decisions/security/agent-gate-requires-a-session-only-when-auth-is-true.md +1 -1
  274. package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +3 -3
  275. package/knowledge/decisions/security/console-addon-privileged-functions-gate-themselves.md +4 -3
  276. package/knowledge/decisions/security/core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md +1 -1
  277. package/knowledge/decisions/security/gateway-middleware-sessions-must-be-bridged-onto-the-wire.md +2 -2
  278. package/knowledge/decisions/security/global-permissions-and-function-permissions-are-independent-gates.md +1 -1
  279. package/knowledge/decisions/security/http-request-bodies-are-bounded-before-they-are-buffered.md +2 -2
  280. package/knowledge/decisions/security/index.md +2 -0
  281. package/knowledge/decisions/security/mcp-internal-error-details-are-double-gated-on-production.md +1 -1
  282. package/knowledge/decisions/security/queue-jobs-carry-the-producers-pikku-user-id.md +3 -3
  283. package/knowledge/decisions/security/remote-addon-tokens-are-client-credentials-not-mesh-trust.md +1 -1
  284. package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +2 -2
  285. package/knowledge/decisions/security/self-authentication-is-declared-not-detected.md +1 -1
  286. package/knowledge/decisions/security/signed-content-urls-bind-the-request-path.md +2 -2
  287. package/knowledge/index.md +3 -1
  288. package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +1 -1
  289. package/knowledge/questions/index.md +2 -0
  290. package/package.json +14 -13
  291. package/run-tests.sh +0 -0
  292. package/scripts/generate-api-report.d.mts +1 -1
  293. package/scripts/generate-api-report.mjs +67 -60
  294. package/scripts/generate-api-report.mts +11 -45
  295. package/src/api-report.test.ts +70 -1
  296. package/src/app-alias-surface.test.ts +179 -0
  297. package/src/app-leaf-surface.test.ts +217 -0
  298. package/src/bootstrap-compat/root.ts +20 -0
  299. package/src/{column-form.test.ts → classification/column-form.test.ts} +2 -2
  300. package/src/{data-classification.ts → classification/data-classification.ts} +1 -5
  301. package/src/classification/index.ts +38 -0
  302. package/src/{secret-value.test.ts → classification/secret-value.test.ts} +2 -2
  303. package/src/{secret-value.ts → classification/secret-value.ts} +1 -6
  304. package/src/crypto-utils.ts +1 -1
  305. package/src/dev/reload-meta.test.ts +1 -1
  306. package/src/errors/index.ts +9 -0
  307. package/src/errors/serialized-error.ts +9 -0
  308. package/src/factory-functions.test.ts +4 -2
  309. package/src/function/function-meta.types.ts +151 -0
  310. package/src/function/function-runner.test.ts +4 -2
  311. package/src/function/function-runner.ts +47 -14
  312. package/src/function/functions.types.ts +11 -7
  313. package/src/function/index.ts +24 -5
  314. package/src/function/list.types.ts +1 -3
  315. package/src/middleware/auth-apikey.ts +4 -2
  316. package/src/middleware/auth-bearer.test.ts +1 -1
  317. package/src/middleware/auth-bearer.ts +4 -2
  318. package/src/middleware/auth-cookie.ts +4 -1
  319. package/src/middleware/cors.test.ts +126 -5
  320. package/src/middleware/cors.ts +19 -8
  321. package/src/middleware/index.ts +16 -0
  322. package/src/middleware/middleware-factories.ts +62 -0
  323. package/src/middleware/middleware.types.ts +61 -0
  324. package/src/middleware/remote-auth.test.ts +1 -1
  325. package/src/middleware/remote-auth.ts +1 -1
  326. package/src/middleware/telemetry.ts +4 -2
  327. package/src/middleware-runner.test.ts +2 -3
  328. package/src/middleware-runner.ts +2 -2
  329. package/src/no-root-barrel.test.ts +114 -0
  330. package/src/permissions.ts +2 -4
  331. package/src/pikku-state.ts +3 -1
  332. package/src/public-surface.json +56 -241
  333. package/src/public-surface.json.README +3 -3
  334. package/src/public-surface.test.ts +5 -5
  335. package/src/remote.test.ts +1 -1
  336. package/src/removed-legacy-exports.test.ts +5 -1
  337. package/src/services/audit-service.ts +1 -1
  338. package/src/services/credential-wire-service.ts +1 -1
  339. package/src/services/email-service.ts +2 -4
  340. package/src/services/file-scenario-run-store.test.ts +349 -0
  341. package/src/services/file-scenario-run-store.ts +282 -0
  342. package/src/services/in-memory-workflow-service.ts +1 -1
  343. package/src/services/index.ts +13 -1
  344. package/src/services/local-content-request-handler.ts +1 -2
  345. package/src/services/local-secrets.ts +1 -1
  346. package/src/services/logger.ts +7 -7
  347. package/src/services/meta-service.ts +2 -2
  348. package/src/services/queue-webhook-service.test.ts +1 -1
  349. package/src/services/scoped-secret-service.ts +1 -1
  350. package/src/services/secret-service.ts +1 -1
  351. package/src/services/typed-secret-service.test.ts +52 -0
  352. package/src/services/typed-secret-service.ts +26 -2
  353. package/src/services/webhook-service.ts +1 -1
  354. package/src/services/workflow-service.ts +2 -1
  355. package/src/template-alias-surface.test.ts +240 -0
  356. package/src/testing/index.ts +1 -0
  357. package/src/types/core.types.ts +33 -285
  358. package/src/types/index.ts +2 -0
  359. package/src/types/state.types.ts +6 -5
  360. package/src/types/typed-credentials.test.ts +72 -0
  361. package/src/utils.ts +34 -0
  362. package/src/wirings/{rpc → addon}/addon-scopes.test.ts +1 -1
  363. package/src/wirings/addon/index.ts +12 -0
  364. package/src/wirings/{rpc → addon}/remote-addon-auth.ts +4 -1
  365. package/src/wirings/{rpc → addon}/wire-addon.ts +37 -2
  366. package/src/wirings/agent/agent-prepare.ts +8 -5
  367. package/src/wirings/agent/agent-runner.ts +2 -3
  368. package/src/wirings/agent/agent-stream.ts +2 -3
  369. package/src/wirings/agent/agent-utils.ts +19 -0
  370. package/src/wirings/agent/agent.types.ts +3 -3
  371. package/src/wirings/agent/index.ts +1 -0
  372. package/src/wirings/agent/voice-input.ts +1 -1
  373. package/src/wirings/agent/voice-output.test.ts +2 -4
  374. package/src/wirings/agent/voice-output.ts +1 -1
  375. package/src/wirings/agent-scorer/agent-scorer-judge.test.ts +145 -0
  376. package/src/wirings/agent-scorer/agent-scorer-judge.ts +60 -0
  377. package/src/wirings/agent-scorer/agent-scorer.ts +8 -0
  378. package/src/wirings/agent-scorer/agent-scorer.types.ts +21 -0
  379. package/src/wirings/agent-scorer/index.ts +1 -0
  380. package/src/wirings/channel/channel-common.ts +4 -2
  381. package/src/wirings/channel/channel-handler.ts +2 -5
  382. package/src/wirings/channel/channel-middleware-runner.ts +1 -1
  383. package/src/wirings/channel/channel-rpc.types.ts +1 -5
  384. package/src/wirings/channel/channel.types.ts +1 -2
  385. package/src/wirings/channel/local/local-channel-runner.test.ts +1 -2
  386. package/src/wirings/channel/serverless/index.ts +1 -0
  387. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +2 -2
  388. package/src/wirings/cli/channel/index.ts +1 -3
  389. package/src/wirings/cli/cli-runner.test.ts +1 -1
  390. package/src/wirings/cli/cli-runner.ts +11 -7
  391. package/src/wirings/cli/cli.types.ts +24 -3
  392. package/src/wirings/cli/command-parser.test.ts +388 -1
  393. package/src/wirings/cli/command-parser.ts +189 -29
  394. package/src/wirings/gateway/gateway-runner.test.ts +1 -2
  395. package/src/wirings/gateway/gateway-runner.ts +3 -6
  396. package/src/wirings/gateway/gateway.types.ts +5 -4
  397. package/src/wirings/http/http-routes.test.ts +1 -2
  398. package/src/wirings/http/http-runner-addon-ref.test.ts +238 -0
  399. package/src/wirings/http/http-runner.test.ts +3 -8
  400. package/src/wirings/http/http-runner.ts +9 -3
  401. package/src/wirings/http/http.types.ts +3 -12
  402. package/src/wirings/http/index.ts +1 -0
  403. package/src/wirings/http/pikku-fetch-http-request.ts +1 -1
  404. package/src/wirings/http/pikku-fetch-http-response.ts +3 -2
  405. package/src/wirings/mcp/mcp-runner.test.ts +1 -1
  406. package/src/wirings/mcp/mcp.types.ts +1 -2
  407. package/src/wirings/persona/index.ts +1 -4
  408. package/src/wirings/persona/persona.types.ts +14 -0
  409. package/src/wirings/queue/queue-identity.test.ts +1 -1
  410. package/src/wirings/queue/queue.types.ts +2 -6
  411. package/src/wirings/queue/signed-queue-service.ts +1 -1
  412. package/src/wirings/rpc/index.ts +0 -12
  413. package/src/wirings/rpc/rpc-runner.test.ts +4 -4
  414. package/src/wirings/rpc/rpc-runner.ts +3 -3
  415. package/src/wirings/scheduler/scheduler.types.ts +2 -5
  416. package/src/wirings/scope/index.ts +1 -0
  417. package/src/wirings/secret/secret.types.ts +10 -0
  418. package/src/wirings/secret/validate-secret-definitions.test.ts +23 -0
  419. package/src/wirings/secret/validate-secret-definitions.ts +2 -0
  420. package/src/wirings/variable/validate-variable-definitions.test.ts +23 -0
  421. package/src/wirings/variable/validate-variable-definitions.ts +2 -0
  422. package/src/wirings/variable/variable.types.ts +9 -0
  423. package/src/wirings/virtual-user/index.ts +1 -4
  424. package/src/wirings/virtual-user/prepare-virtual-user-run.ts +1 -1
  425. package/src/wirings/virtual-user/virtual-user-derive.test.ts +1 -1
  426. package/src/wirings/virtual-user/virtual-user-derive.ts +1 -1
  427. package/src/wirings/virtual-user/virtual-user.types.ts +1 -5
  428. package/src/wirings/workflow/graph/graph-foreach-queued.test.ts +569 -0
  429. package/src/wirings/workflow/graph/graph-foreach.test.ts +626 -0
  430. package/src/wirings/workflow/graph/graph-node.ts +16 -1
  431. package/src/wirings/workflow/graph/graph-runner.ts +387 -61
  432. package/src/wirings/workflow/graph/workflow-graph.types.ts +28 -3
  433. package/src/wirings/workflow/index.ts +8 -0
  434. package/src/wirings/workflow/pikku-scenario-service.ts +59 -19
  435. package/src/wirings/workflow/pikku-workflow-service.ts +6 -8
  436. package/src/wirings/workflow/run-timeline.ts +1 -1
  437. package/src/wirings/workflow/scenario-expectations.test.ts +28 -13
  438. package/src/wirings/workflow/scenario-run.types.ts +186 -0
  439. package/src/wirings/workflow/scenario-step-guards.ts +1 -14
  440. package/src/wirings/workflow/scenario-step.test.ts +32 -7
  441. package/src/wirings/workflow/scenario-step.types.ts +27 -12
  442. package/src/wirings/workflow/workflow-approval-policy.test.ts +117 -47
  443. package/src/wirings/workflow/workflow-approval.ts +2 -1
  444. package/src/wirings/workflow/workflow-run-engine.types.ts +2 -1
  445. package/src/wirings/workflow/workflow.types.ts +4 -12
  446. package/src/wirings-stay-decoupled.test.ts +6 -3
  447. package/tsconfig.tsbuildinfo +1 -1
  448. package/tsconfig.type-tests.json +3 -2
  449. package/dist/ecosystem/ai-agent.d.ts +0 -18
  450. package/dist/ecosystem/ai-agent.js +0 -6
  451. package/dist/ecosystem/ai-scorer.d.ts +0 -12
  452. package/dist/ecosystem/ai-scorer.js +0 -5
  453. package/dist/services/ai-agent-runner-service.d.ts +0 -220
  454. package/dist/services/ai-run-state-service.d.ts +0 -29
  455. package/dist/services/ai-storage-service.d.ts +0 -18
  456. package/dist/services/in-memory-ai-run-state-service.d.ts +0 -19
  457. package/dist/services/in-memory-ai-run-state-service.js +0 -54
  458. package/dist/testing/service-tests/ai-storage-service-tests.d.ts +0 -3
  459. package/dist/testing/service-tests/ai-storage-service-tests.js +0 -302
  460. package/dist/wirings/ai-agent/agent-rpc.d.ts +0 -15
  461. package/dist/wirings/ai-agent/agent-rpc.js +0 -53
  462. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +0 -79
  463. package/dist/wirings/ai-agent/ai-agent-agui.js +0 -327
  464. package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +0 -58
  465. package/dist/wirings/ai-agent/ai-agent-finalize.js +0 -138
  466. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +0 -35
  467. package/dist/wirings/ai-agent/ai-agent-helpers.js +0 -47
  468. package/dist/wirings/ai-agent/ai-agent-interrupt.d.ts +0 -153
  469. package/dist/wirings/ai-agent/ai-agent-interrupt.js +0 -257
  470. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +0 -42
  471. package/dist/wirings/ai-agent/ai-agent-memory.js +0 -333
  472. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +0 -16
  473. package/dist/wirings/ai-agent/ai-agent-model-config.js +0 -51
  474. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +0 -117
  475. package/dist/wirings/ai-agent/ai-agent-prepare.js +0 -728
  476. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +0 -16
  477. package/dist/wirings/ai-agent/ai-agent-registry.js +0 -51
  478. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +0 -7
  479. package/dist/wirings/ai-agent/ai-agent-runner.js +0 -580
  480. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +0 -41
  481. package/dist/wirings/ai-agent/ai-agent-stream.js +0 -1029
  482. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +0 -57
  483. package/dist/wirings/ai-agent/ai-agent-turn.js +0 -82
  484. package/dist/wirings/ai-agent/ai-agent-utils.d.ts +0 -1
  485. package/dist/wirings/ai-agent/ai-agent-utils.js +0 -1
  486. package/dist/wirings/ai-agent/ai-agent.types.d.ts +0 -625
  487. package/dist/wirings/ai-agent/index.d.ts +0 -12
  488. package/dist/wirings/ai-agent/index.js +0 -10
  489. package/dist/wirings/ai-agent/voice-input.d.ts +0 -69
  490. package/dist/wirings/ai-agent/voice-input.js +0 -150
  491. package/dist/wirings/ai-agent/voice-output.d.ts +0 -85
  492. package/dist/wirings/ai-agent/voice-output.js +0 -196
  493. package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +0 -26
  494. package/dist/wirings/ai-scorer/ai-scorer-grade.js +0 -33
  495. package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +0 -17
  496. package/dist/wirings/ai-scorer/ai-scorer-judge.js +0 -92
  497. package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +0 -15
  498. package/dist/wirings/ai-scorer/ai-scorer-live.js +0 -38
  499. package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +0 -18
  500. package/dist/wirings/ai-scorer/ai-scorer-registry.js +0 -46
  501. package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +0 -8
  502. package/dist/wirings/ai-scorer/ai-scorer-sampling.js +0 -31
  503. package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +0 -10
  504. package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +0 -40
  505. package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +0 -15
  506. package/dist/wirings/ai-scorer/ai-scorer-worker.js +0 -58
  507. package/dist/wirings/ai-scorer/ai-scorer.d.ts +0 -39
  508. package/dist/wirings/ai-scorer/ai-scorer.js +0 -40
  509. package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +0 -90
  510. package/dist/wirings/ai-scorer/ai-scorer.types.js +0 -4
  511. package/dist/wirings/ai-scorer/index.d.ts +0 -6
  512. package/dist/wirings/ai-scorer/index.js +0 -5
  513. package/src/ecosystem/agent-scorer.ts +0 -24
  514. package/src/ecosystem/agent.ts +0 -42
  515. package/src/ecosystem/channel/local.ts +0 -1
  516. package/src/ecosystem/channel.ts +0 -22
  517. package/src/ecosystem/cli/channel.ts +0 -25
  518. package/src/ecosystem/cli.ts +0 -21
  519. package/src/ecosystem/credential.ts +0 -10
  520. package/src/ecosystem/dev.ts +0 -12
  521. package/src/ecosystem/function.ts +0 -28
  522. package/src/ecosystem/gateway.ts +0 -4
  523. package/src/ecosystem/hmac.ts +0 -1
  524. package/src/ecosystem/http.ts +0 -22
  525. package/src/ecosystem/mcp.ts +0 -17
  526. package/src/ecosystem/middleware.ts +0 -27
  527. package/src/ecosystem/node-host-resolver.ts +0 -1
  528. package/src/ecosystem/node.ts +0 -5
  529. package/src/ecosystem/oauth2.ts +0 -4
  530. package/src/ecosystem/persona.ts +0 -30
  531. package/src/ecosystem/queue.ts +0 -23
  532. package/src/ecosystem/remote.ts +0 -9
  533. package/src/ecosystem/role.ts +0 -13
  534. package/src/ecosystem/rpc.ts +0 -7
  535. package/src/ecosystem/safe-fetch.ts +0 -8
  536. package/src/ecosystem/scenario.ts +0 -32
  537. package/src/ecosystem/scheduler.ts +0 -5
  538. package/src/ecosystem/schema.ts +0 -1
  539. package/src/ecosystem/scope.ts +0 -14
  540. package/src/ecosystem/secret.ts +0 -15
  541. package/src/ecosystem/services/istanbul-coverage.ts +0 -1
  542. package/src/ecosystem/services/local-content-request-handler.ts +0 -16
  543. package/src/ecosystem/services/local-content.ts +0 -1
  544. package/src/ecosystem/services/v8-coverage.ts +0 -1
  545. package/src/ecosystem/services.ts +0 -82
  546. package/src/ecosystem/trigger.ts +0 -7
  547. package/src/ecosystem/types.ts +0 -34
  548. package/src/ecosystem/variable.ts +0 -14
  549. package/src/ecosystem/virtual-user.ts +0 -48
  550. package/src/ecosystem/workflow.ts +0 -68
  551. package/src/ecosystem.ts +0 -40
  552. package/src/index.ts +0 -244
  553. /package/dist/{services/ai-agent-runner-service.js → bootstrap-compat/root.js} +0 -0
  554. /package/dist/{services/ai-run-state-service.js → classification/data-classification.js} +0 -0
  555. /package/dist/{services/ai-storage-service.js → errors/serialized-error.js} +0 -0
  556. /package/dist/{wirings/ai-agent/ai-agent.types.js → function/function-meta.types.js} +0 -0
  557. /package/src/{column-form.ts → classification/column-form.ts} +0 -0
  558. /package/src/{pikku-request.test.ts → function/pikku-request.test.ts} +0 -0
  559. /package/src/{pikku-request.ts → function/pikku-request.ts} +0 -0
  560. /package/src/wirings/{rpc → addon}/addon-auth-tags.test.ts +0 -0
  561. /package/src/wirings/{rpc → addon}/addon-runner.ts +0 -0
  562. /package/src/wirings/{rpc → addon}/addon-secrets.test.ts +0 -0
  563. /package/src/wirings/{rpc → addon}/wire-addon.test.ts +0 -0
  564. /package/src/wirings/{rpc → addon}/wire-remote-addon.ts +0 -0
@@ -0,0 +1,349 @@
1
+ import { describe, test, beforeEach, afterEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import {
4
+ mkdtempSync,
5
+ mkdirSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ existsSync,
9
+ } from 'node:fs'
10
+ import { tmpdir } from 'node:os'
11
+ import { join } from 'node:path'
12
+
13
+ import {
14
+ FileScenarioRunStore,
15
+ scenarioRunSummary,
16
+ } from './file-scenario-run-store.js'
17
+ import type {
18
+ ScenarioResult,
19
+ ScenarioRunRecord,
20
+ } from '../wirings/workflow/scenario-run.types.js'
21
+
22
+ let dir: string
23
+
24
+ beforeEach(() => {
25
+ dir = mkdtempSync(join(tmpdir(), 'pikku-scenario-runs-'))
26
+ })
27
+
28
+ afterEach(() => {
29
+ rmSync(dir, { recursive: true, force: true })
30
+ })
31
+
32
+ const record = (
33
+ overrides: Partial<ScenarioRunRecord> = {}
34
+ ): ScenarioRunRecord => ({
35
+ runId: 'run-1',
36
+ environment: 'local',
37
+ surface: 'browser',
38
+ status: 'running',
39
+ startedAt: '2026-08-15T10:00:00.000Z',
40
+ results: [],
41
+ skipped: [],
42
+ hookFailures: [],
43
+ ...overrides,
44
+ })
45
+
46
+ const result = (overrides: Partial<ScenarioResult> = {}): ScenarioResult => ({
47
+ name: 'todos › a member adds one',
48
+ status: 'passed',
49
+ durationMs: 120,
50
+ ...overrides,
51
+ })
52
+
53
+ describe('FileScenarioRunStore', () => {
54
+ test('a run is readable from the moment it opens, before any scenario finishes', async () => {
55
+ const store = new FileScenarioRunStore({ dir })
56
+
57
+ await store.start(record())
58
+
59
+ const open = await store.get('run-1')
60
+ assert.equal(open?.status, 'running')
61
+ assert.deepEqual(open?.results, [])
62
+ })
63
+
64
+ test('scenarios accumulate as they finish, so a suite killed halfway keeps what ran', async () => {
65
+ const store = new FileScenarioRunStore({ dir })
66
+ await store.start(record())
67
+
68
+ await store.recordScenario('run-1', result({ name: 'one' }))
69
+ await store.recordScenario(
70
+ 'run-1',
71
+ result({ name: 'two', status: 'failed' })
72
+ )
73
+
74
+ const partial = await store.get('run-1')
75
+ assert.deepEqual(
76
+ partial?.results.map((r) => r.name),
77
+ ['one', 'two']
78
+ )
79
+ assert.equal(
80
+ partial?.status,
81
+ 'running',
82
+ 'a run nobody finished is still running, not passed'
83
+ )
84
+ })
85
+
86
+ test('finishing settles the outcome and everything only known at the end', async () => {
87
+ const store = new FileScenarioRunStore({ dir })
88
+ await store.start(record())
89
+ await store.recordScenario('run-1', result())
90
+
91
+ await store.finish('run-1', {
92
+ status: 'failed',
93
+ finishedAt: '2026-08-15T10:00:30.000Z',
94
+ skipped: [{ name: 'quarantined', reason: 'flaky' }],
95
+ hookFailures: ['feature after hook failed: boom'],
96
+ })
97
+
98
+ const done = await store.get('run-1')
99
+ assert.equal(done?.status, 'failed')
100
+ assert.equal(done?.finishedAt, '2026-08-15T10:00:30.000Z')
101
+ assert.deepEqual(done?.skipped, [{ name: 'quarantined', reason: 'flaky' }])
102
+ assert.equal(done?.results.length, 1, 'finishing does not lose the results')
103
+ })
104
+
105
+ test('concurrent scenario writes never lose one to a read-modify-write race', async () => {
106
+ const store = new FileScenarioRunStore({ dir })
107
+ await store.start(record())
108
+
109
+ await Promise.all(
110
+ Array.from({ length: 20 }, (_, i) =>
111
+ store.recordScenario('run-1', result({ name: `scenario-${i}` }))
112
+ )
113
+ )
114
+
115
+ const all = await store.get('run-1')
116
+ assert.equal(all?.results.length, 20)
117
+ })
118
+
119
+ test('runs list newest first, summarised without loading their stacks', async () => {
120
+ const store = new FileScenarioRunStore({ dir })
121
+ await store.start(
122
+ record({ runId: 'older', startedAt: '2026-08-14T10:00:00.000Z' })
123
+ )
124
+ await store.start(
125
+ record({ runId: 'newer', startedAt: '2026-08-15T10:00:00.000Z' })
126
+ )
127
+ await store.recordScenario(
128
+ 'newer',
129
+ result({
130
+ artifacts: [{ scenario: 'x', kind: 'video', path: 'x/admin.mp4' }],
131
+ })
132
+ )
133
+ await store.recordScenario('newer', result({ status: 'failed' }))
134
+ await store.finish('newer', {
135
+ status: 'failed',
136
+ finishedAt: '2026-08-15T10:00:05.000Z',
137
+ skipped: [{ name: 'held', reason: 'no browser' }],
138
+ hookFailures: [],
139
+ })
140
+
141
+ const runs = await store.list()
142
+
143
+ assert.deepEqual(
144
+ runs.map((r) => r.runId),
145
+ ['newer', 'older']
146
+ )
147
+ assert.deepEqual(
148
+ { ...runs[0] },
149
+ {
150
+ runId: 'newer',
151
+ environment: 'local',
152
+ surface: 'browser',
153
+ status: 'failed',
154
+ startedAt: '2026-08-15T10:00:00.000Z',
155
+ finishedAt: '2026-08-15T10:00:05.000Z',
156
+ durationMs: 5000,
157
+ passed: 1,
158
+ failed: 1,
159
+ skipped: 1,
160
+ artifacts: 1,
161
+ }
162
+ )
163
+ })
164
+
165
+ test('a limit takes the newest runs, not whichever the filesystem returned first', async () => {
166
+ const store = new FileScenarioRunStore({ dir })
167
+ for (const day of ['11', '12', '13']) {
168
+ await store.start(
169
+ record({
170
+ runId: `run-${day}`,
171
+ startedAt: `2026-08-${day}T10:00:00.000Z`,
172
+ })
173
+ )
174
+ }
175
+
176
+ const runs = await store.list({ limit: 2 })
177
+
178
+ assert.deepEqual(
179
+ runs.map((r) => r.runId),
180
+ ['run-13', 'run-12']
181
+ )
182
+ })
183
+
184
+ test('opening a run drops the oldest past the limit, with everything they filed', async () => {
185
+ const store = new FileScenarioRunStore({ dir, keep: 2 })
186
+ for (const day of ['11', '12', '13']) {
187
+ await store.start(
188
+ record({
189
+ runId: `run-${day}`,
190
+ startedAt: `2026-08-${day}T10:00:00.000Z`,
191
+ })
192
+ )
193
+ writeFileSync(join(dir, `run-${day}`, 'video.mp4'), 'x')
194
+ }
195
+
196
+ assert.deepEqual(
197
+ (await store.list()).map((r) => r.runId),
198
+ ['run-13', 'run-12']
199
+ )
200
+ assert.equal(
201
+ existsSync(join(dir, 'run-11')),
202
+ false,
203
+ 'the pruned run takes its artifacts with it'
204
+ )
205
+ })
206
+
207
+ test('a keep of one still leaves the run being opened', async () => {
208
+ const store = new FileScenarioRunStore({ dir, keep: 1 })
209
+ await store.start(record({ runId: 'first' }))
210
+ await store.start(record({ runId: 'second' }))
211
+
212
+ assert.deepEqual(
213
+ (await store.list()).map((r) => r.runId),
214
+ ['second']
215
+ )
216
+ })
217
+
218
+ test('artifacts are filed against the scenario the run knows them by', async () => {
219
+ const store = new FileScenarioRunStore({ dir })
220
+ await store.start(record())
221
+ await store.recordScenario('run-1', result({ name: 'checkout › breaks' }))
222
+ await store.recordScenario('run-1', result({ name: 'checkout › works' }))
223
+
224
+ await store.attachArtifacts('run-1', [
225
+ { scenario: 'checkout › breaks', kind: 'video', path: 'a/admin.mp4' },
226
+ {
227
+ scenario: 'checkout › breaks',
228
+ kind: 'failure',
229
+ path: 'a/failure-admin.png',
230
+ },
231
+ { scenario: 'a scenario that never ran', kind: 'video', path: 'b/x.mp4' },
232
+ ])
233
+
234
+ const [broke, worked] = (await store.get('run-1'))!.results
235
+ assert.deepEqual(
236
+ broke?.artifacts?.map((a) => a.path),
237
+ ['a/admin.mp4', 'a/failure-admin.png']
238
+ )
239
+ assert.equal(
240
+ worked?.artifacts,
241
+ undefined,
242
+ 'a scenario that produced nothing gets no empty list to render'
243
+ )
244
+ })
245
+
246
+ test('a run id from outside cannot read its way out of the store', async () => {
247
+ const store = new FileScenarioRunStore({ dir })
248
+ writeFileSync(join(dir, 'run.json'), JSON.stringify(record()))
249
+
250
+ assert.equal(await store.get('..'), undefined)
251
+ assert.equal(await store.get('../..'), undefined)
252
+ assert.equal(existsSync(join(dir, 'run.json')), true)
253
+
254
+ await store.remove('../..')
255
+ assert.equal(
256
+ existsSync(dir),
257
+ true,
258
+ 'a traversing id must not delete the store itself'
259
+ )
260
+ })
261
+
262
+ test('an artifact is served with the type its extension implies', async () => {
263
+ const store = new FileScenarioRunStore({ dir })
264
+ await store.start(record())
265
+ mkdirSync(join(dir, 'run-1', 'checkout'))
266
+ writeFileSync(join(dir, 'run-1', 'checkout', 'admin.mp4'), 'footage')
267
+ writeFileSync(join(dir, 'run-1', 'checkout', '01-shot.png'), 'image')
268
+
269
+ const video = await store.readArtifact('run-1', 'checkout/admin.mp4')
270
+ const shot = await store.readArtifact('run-1', 'checkout/01-shot.png')
271
+
272
+ assert.equal(video?.contentType, 'video/mp4')
273
+ assert.equal(Buffer.from(video!.body).toString(), 'footage')
274
+ assert.equal(shot?.contentType, 'image/png')
275
+ })
276
+
277
+ test('an artifact path cannot climb out of its run', async () => {
278
+ const store = new FileScenarioRunStore({ dir })
279
+ await store.start(record())
280
+ writeFileSync(join(dir, 'secret.txt'), 'not yours')
281
+
282
+ assert.equal(await store.readArtifact('run-1', '../secret.txt'), undefined)
283
+ assert.equal(
284
+ await store.readArtifact('run-1', 'checkout/../../secret.txt'),
285
+ undefined
286
+ )
287
+ assert.equal(await store.readArtifact('run-1', '/etc/hosts'), undefined)
288
+ assert.equal(
289
+ await store.readArtifact('run-1', 'checkout/never-taken.png'),
290
+ undefined,
291
+ 'and an artifact that was never filed is simply absent'
292
+ )
293
+ })
294
+
295
+ test('an artifact of a run that never existed is absent, not an error', async () => {
296
+ const store = new FileScenarioRunStore({ dir })
297
+
298
+ assert.equal(await store.readArtifact('run-1', 'a/b.png'), undefined)
299
+ })
300
+
301
+ test('a half-written run is skipped rather than failing the whole listing', async () => {
302
+ const store = new FileScenarioRunStore({ dir })
303
+ await store.start(record())
304
+ mkdirSync(join(dir, 'killed-mid-write'))
305
+ writeFileSync(join(dir, 'killed-mid-write', 'run.json'), '{"runId":')
306
+
307
+ const runs = await store.list()
308
+
309
+ assert.deepEqual(
310
+ runs.map((r) => r.runId),
311
+ ['run-1']
312
+ )
313
+ })
314
+
315
+ test('a store with nothing in it lists nothing rather than throwing', async () => {
316
+ const store = new FileScenarioRunStore({ dir: join(dir, 'never-created') })
317
+
318
+ assert.deepEqual(await store.list(), [])
319
+ assert.equal(await store.get('run-1'), undefined)
320
+ })
321
+
322
+ test('recording against a run that does not exist is ignored, not invented', async () => {
323
+ const store = new FileScenarioRunStore({ dir })
324
+
325
+ await store.recordScenario('never-started', result())
326
+
327
+ assert.deepEqual(await store.list(), [])
328
+ })
329
+
330
+ test('removing a run takes its whole folder', async () => {
331
+ const store = new FileScenarioRunStore({ dir })
332
+ await store.start(record())
333
+ writeFileSync(join(dir, 'run-1', 'video.mp4'), 'x')
334
+
335
+ await store.remove('run-1')
336
+
337
+ assert.equal(existsSync(join(dir, 'run-1')), false)
338
+ })
339
+ })
340
+
341
+ describe('scenarioRunSummary', () => {
342
+ test('a run still going has no duration to report', () => {
343
+ const summary = scenarioRunSummary(record({ results: [result()] }))
344
+
345
+ assert.equal(summary.durationMs, undefined)
346
+ assert.equal(summary.status, 'running')
347
+ assert.equal(summary.passed, 1)
348
+ })
349
+ })
@@ -0,0 +1,282 @@
1
+ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import type {
4
+ ScenarioArtifact,
5
+ ScenarioResult,
6
+ ScenarioRunRecord,
7
+ ScenarioRunStore,
8
+ ScenarioRunSummary,
9
+ } from '../wirings/workflow/scenario-run.types.js'
10
+
11
+ /** The record, beside the images and footage the same run produced. */
12
+ const RECORD_FILE = 'run.json'
13
+
14
+ /**
15
+ * A run id is a path segment, and `get('../../etc')` is a file read.
16
+ *
17
+ * Ids are generated here, so anything failing this arrived from outside and is
18
+ * refused rather than sanitised — a request for a run that cannot exist is a
19
+ * request for a run that does not exist.
20
+ */
21
+ const SAFE_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/
22
+
23
+ /** Counts and duration, derived rather than stored so they cannot disagree. */
24
+ export const scenarioRunSummary = (
25
+ record: ScenarioRunRecord
26
+ ): ScenarioRunSummary => ({
27
+ runId: record.runId,
28
+ environment: record.environment,
29
+ surface: record.surface,
30
+ status: record.status,
31
+ startedAt: record.startedAt,
32
+ finishedAt: record.finishedAt,
33
+ durationMs: record.finishedAt
34
+ ? new Date(record.finishedAt).getTime() -
35
+ new Date(record.startedAt).getTime()
36
+ : undefined,
37
+ passed: record.results.filter((r) => r.status === 'passed').length,
38
+ failed: record.results.filter((r) => r.status === 'failed').length,
39
+ skipped: record.skipped.length,
40
+ artifacts: record.results.reduce(
41
+ (total, result) => total + (result.artifacts?.length ?? 0),
42
+ 0
43
+ ),
44
+ })
45
+
46
+ /**
47
+ * What a scenario artifact is served as.
48
+ *
49
+ * A short, closed list rather than a MIME database: a run produces images and
50
+ * recordings and nothing else, and anything unrecognised is served as bytes the
51
+ * browser is told not to interpret.
52
+ */
53
+ const CONTENT_TYPES: Record<string, string> = {
54
+ png: 'image/png',
55
+ jpg: 'image/jpeg',
56
+ jpeg: 'image/jpeg',
57
+ webp: 'image/webp',
58
+ mp4: 'video/mp4',
59
+ webm: 'video/webm',
60
+ }
61
+
62
+ export const scenarioArtifactContentType = (path: string): string =>
63
+ CONTENT_TYPES[path.split('.').pop()?.toLowerCase() ?? ''] ??
64
+ 'application/octet-stream'
65
+
66
+ /**
67
+ * An artifact path is a content key from the record, but it arrives from a
68
+ * client and is joined onto a directory. Anything that could climb out of the
69
+ * run, or name something other than a file inside it, is refused.
70
+ */
71
+ const SAFE_ARTIFACT_PATH =
72
+ /^[A-Za-z0-9][A-Za-z0-9._-]*(\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/
73
+
74
+ export interface FileScenarioRunStoreOptions {
75
+ /** The directory runs live under — one folder per run, named by its id. */
76
+ dir: string
77
+ /**
78
+ * How many runs to keep. Older ones are deleted, with their artifacts, as a
79
+ * new run opens.
80
+ *
81
+ * Bounded on purpose: a run with video is megabytes, and an unbounded history
82
+ * beside a project is a directory nobody notices until it is enormous.
83
+ */
84
+ keep?: number
85
+ }
86
+
87
+ const DEFAULT_KEEP = 20
88
+
89
+ /**
90
+ * Scenario run history as files on disk — the OSS store.
91
+ *
92
+ * One folder per run holding its record and everything it filed, which makes a
93
+ * run a thing you can open, copy, attach to an issue or delete with `rm -rf`.
94
+ * A hosted project swaps this for a table and an object store; the console
95
+ * reads the same shapes from either.
96
+ *
97
+ * Writes are serialised through one chain. Scenarios finish one at a time, but
98
+ * `recordScenario` is read-modify-write on a single file, and a store that
99
+ * corrupts its own history the first time two things overlap is worse than no
100
+ * history.
101
+ */
102
+ export class FileScenarioRunStore implements ScenarioRunStore {
103
+ private writes: Promise<unknown> = Promise.resolve()
104
+
105
+ constructor(private readonly options: FileScenarioRunStoreOptions) {}
106
+
107
+ async start(record: ScenarioRunRecord): Promise<void> {
108
+ await this.serialise(async () => {
109
+ await this.write(record)
110
+ await this.prune(record.runId)
111
+ })
112
+ }
113
+
114
+ async recordScenario(runId: string, result: ScenarioResult): Promise<void> {
115
+ await this.serialise(async () => {
116
+ const record = await this.read(runId)
117
+ if (!record) {
118
+ return
119
+ }
120
+ record.results.push(result)
121
+ await this.write(record)
122
+ })
123
+ }
124
+
125
+ async attachArtifacts(
126
+ runId: string,
127
+ artifacts: ScenarioArtifact[]
128
+ ): Promise<void> {
129
+ if (artifacts.length === 0) {
130
+ return
131
+ }
132
+ await this.serialise(async () => {
133
+ const record = await this.read(runId)
134
+ if (!record) {
135
+ return
136
+ }
137
+ for (const artifact of artifacts) {
138
+ const result = record.results.find(
139
+ (candidate) => candidate.name === artifact.scenario
140
+ )
141
+ // An artifact whose scenario is not in the results belongs to a
142
+ // scenario that never produced one — it is dropped rather than filed
143
+ // under a run-level bucket nothing renders.
144
+ if (result) {
145
+ result.artifacts = [...(result.artifacts ?? []), artifact]
146
+ }
147
+ }
148
+ await this.write(record)
149
+ })
150
+ }
151
+
152
+ async finish(
153
+ runId: string,
154
+ outcome: Pick<
155
+ ScenarioRunRecord,
156
+ 'status' | 'finishedAt' | 'skipped' | 'hookFailures'
157
+ >
158
+ ): Promise<void> {
159
+ await this.serialise(async () => {
160
+ const record = await this.read(runId)
161
+ if (!record) {
162
+ return
163
+ }
164
+ await this.write({ ...record, ...outcome })
165
+ })
166
+ }
167
+
168
+ async readArtifact(
169
+ runId: string,
170
+ path: string
171
+ ): Promise<
172
+ { body: Uint8Array<ArrayBuffer>; contentType: string } | undefined
173
+ > {
174
+ if (!SAFE_RUN_ID.test(runId) || !SAFE_ARTIFACT_PATH.test(path)) {
175
+ return undefined
176
+ }
177
+ try {
178
+ const file = await readFile(
179
+ join(this.options.dir, runId, ...path.split('/'))
180
+ )
181
+ return {
182
+ body: new Uint8Array(file),
183
+ contentType: scenarioArtifactContentType(path),
184
+ }
185
+ } catch {
186
+ return undefined
187
+ }
188
+ }
189
+
190
+ async list(options: { limit?: number } = {}): Promise<ScenarioRunSummary[]> {
191
+ const summaries: ScenarioRunSummary[] = []
192
+ for (const runId of await this.runIds()) {
193
+ const record = await this.read(runId)
194
+ if (record) {
195
+ summaries.push(scenarioRunSummary(record))
196
+ }
197
+ }
198
+ summaries.sort((a, b) => b.startedAt.localeCompare(a.startedAt))
199
+ return options.limit ? summaries.slice(0, options.limit) : summaries
200
+ }
201
+
202
+ async get(runId: string): Promise<ScenarioRunRecord | undefined> {
203
+ return this.read(runId)
204
+ }
205
+
206
+ async remove(runId: string): Promise<void> {
207
+ if (!SAFE_RUN_ID.test(runId)) {
208
+ return
209
+ }
210
+ await rm(join(this.options.dir, runId), { recursive: true, force: true })
211
+ }
212
+
213
+ /** Where this run's artifacts live, for a driver writing into it. */
214
+ runDir(runId: string): string {
215
+ return join(this.options.dir, runId)
216
+ }
217
+
218
+ private serialise<T>(work: () => Promise<T>): Promise<T> {
219
+ const next = this.writes.then(work, work)
220
+ // Swallowed on the CHAIN only, so one failed write does not reject every
221
+ // later one; the caller still sees its own rejection through `next`.
222
+ this.writes = next.catch(() => {})
223
+ return next
224
+ }
225
+
226
+ private async read(runId: string): Promise<ScenarioRunRecord | undefined> {
227
+ if (!SAFE_RUN_ID.test(runId)) {
228
+ return undefined
229
+ }
230
+ try {
231
+ const raw = await readFile(
232
+ join(this.options.dir, runId, RECORD_FILE),
233
+ 'utf-8'
234
+ )
235
+ return JSON.parse(raw) as ScenarioRunRecord
236
+ } catch {
237
+ // A folder with artifacts but no readable record is a run that was killed
238
+ // mid-write. It is not listable, and it is not an error either.
239
+ return undefined
240
+ }
241
+ }
242
+
243
+ private async write(record: ScenarioRunRecord): Promise<void> {
244
+ const dir = join(this.options.dir, record.runId)
245
+ await mkdir(dir, { recursive: true })
246
+ await writeFile(
247
+ join(dir, RECORD_FILE),
248
+ JSON.stringify(record, null, 2) + '\n'
249
+ )
250
+ }
251
+
252
+ private async runIds(): Promise<string[]> {
253
+ try {
254
+ const entries = await readdir(this.options.dir, { withFileTypes: true })
255
+ return entries
256
+ .filter((entry) => entry.isDirectory() && SAFE_RUN_ID.test(entry.name))
257
+ .map((entry) => entry.name)
258
+ } catch {
259
+ return []
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Drop the oldest runs past the limit, never the one just opened — a `keep`
265
+ * of zero or one must still leave the run being started.
266
+ */
267
+ private async prune(keeping: string): Promise<void> {
268
+ const keep = this.options.keep ?? DEFAULT_KEEP
269
+ const dated: Array<{ runId: string; startedAt: string }> = []
270
+ for (const runId of await this.runIds()) {
271
+ if (runId === keeping) {
272
+ continue
273
+ }
274
+ const record = await this.read(runId)
275
+ dated.push({ runId, startedAt: record?.startedAt ?? '' })
276
+ }
277
+ dated.sort((a, b) => b.startedAt.localeCompare(a.startedAt))
278
+ for (const { runId } of dated.slice(Math.max(keep - 1, 0))) {
279
+ await rm(join(this.options.dir, runId), { recursive: true, force: true })
280
+ }
281
+ }
282
+ }
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from 'crypto'
2
2
  import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service.js'
3
3
  import { isExpectedError } from '../errors/error-handler.js'
4
- import type { SerializedError } from '../types/core.types.js'
4
+ import type { SerializedError } from '../errors/serialized-error.js'
5
5
  import type {
6
6
  WorkflowPlannedStep,
7
7
  WorkflowQueueOptions,
@@ -22,6 +22,12 @@ export { InMemoryQueueService } from './in-memory-queue-service.js'
22
22
  export { InMemoryTriggerService } from './in-memory-trigger-service.js'
23
23
  export { InMemoryAgentRunStateService } from './in-memory-agent-run-state-service.js'
24
24
  export { LocalGatewayService } from './local-gateway-service.js'
25
+ export {
26
+ FileScenarioRunStore,
27
+ scenarioArtifactContentType,
28
+ scenarioRunSummary,
29
+ type FileScenarioRunStoreOptions,
30
+ } from './file-scenario-run-store.js'
25
31
  export type {
26
32
  ContentService,
27
33
  SignContentKeyArgs,
@@ -67,8 +73,8 @@ export type { SessionService } from './user-session-service.js'
67
73
  export type {
68
74
  ScheduledTaskSummary,
69
75
  ScheduledTaskInfo,
70
- SchedulerService,
71
76
  } from './scheduler-service.js'
77
+ export { SchedulerService } from './scheduler-service.js'
72
78
  export type { TriggerService } from './trigger-service.js'
73
79
  export type { GatewayService } from './gateway-service.js'
74
80
  export type {
@@ -151,3 +157,9 @@ export {
151
157
  SecretHostNotAllowedError,
152
158
  assertSecretAllowedForHost,
153
159
  } from './secret-host-binding.js'
160
+ export type {
161
+ AuditFacets,
162
+ AuditQuery,
163
+ AuditQueryResult,
164
+ } from './audit-service.js'
165
+ export { SecretAccessDeniedError, withoutSecrets } from './secretless.js'
@@ -78,8 +78,7 @@ const text = (status: number, body: string) =>
78
78
  })
79
79
 
80
80
  export type SignedContentVerification =
81
- | { ok: true }
82
- | { ok: false; status: number; body: string }
81
+ { ok: true } | { ok: false; status: number; body: string }
83
82
 
84
83
  /**
85
84
  * Verifies a signed content URL — the one function that decides whether a
@@ -2,7 +2,7 @@ import {
2
2
  createSecretValue,
3
3
  isSecretValue,
4
4
  type SecretValue,
5
- } from '../secret-value.js'
5
+ } from '../classification/secret-value.js'
6
6
  import { LocalVariablesService } from './local-variables.js'
7
7
  import type { SecretService, SecretValues } from './secret-service.js'
8
8
  import type { VariablesService } from './variables-service.js'