palaryn 0.1.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 (607) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +716 -0
  3. package/dist/sdk/typescript/src/client.d.ts +71 -0
  4. package/dist/sdk/typescript/src/client.d.ts.map +1 -0
  5. package/dist/sdk/typescript/src/client.js +176 -0
  6. package/dist/sdk/typescript/src/client.js.map +1 -0
  7. package/dist/sdk/typescript/src/errors.d.ts +50 -0
  8. package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
  9. package/dist/sdk/typescript/src/errors.js +103 -0
  10. package/dist/sdk/typescript/src/errors.js.map +1 -0
  11. package/dist/sdk/typescript/src/index.d.ts +4 -0
  12. package/dist/sdk/typescript/src/index.d.ts.map +1 -0
  13. package/dist/sdk/typescript/src/index.js +15 -0
  14. package/dist/sdk/typescript/src/index.js.map +1 -0
  15. package/dist/sdk/typescript/src/types.d.ts +101 -0
  16. package/dist/sdk/typescript/src/types.d.ts.map +1 -0
  17. package/dist/sdk/typescript/src/types.js +6 -0
  18. package/dist/sdk/typescript/src/types.js.map +1 -0
  19. package/dist/src/admin/index.d.ts +2 -0
  20. package/dist/src/admin/index.d.ts.map +1 -0
  21. package/dist/src/admin/index.js +6 -0
  22. package/dist/src/admin/index.js.map +1 -0
  23. package/dist/src/admin/routes.d.ts +5 -0
  24. package/dist/src/admin/routes.d.ts.map +1 -0
  25. package/dist/src/admin/routes.js +471 -0
  26. package/dist/src/admin/routes.js.map +1 -0
  27. package/dist/src/admin/templates.d.ts +51 -0
  28. package/dist/src/admin/templates.d.ts.map +1 -0
  29. package/dist/src/admin/templates.js +500 -0
  30. package/dist/src/admin/templates.js.map +1 -0
  31. package/dist/src/anomaly/detector.d.ts +141 -0
  32. package/dist/src/anomaly/detector.d.ts.map +1 -0
  33. package/dist/src/anomaly/detector.js +554 -0
  34. package/dist/src/anomaly/detector.js.map +1 -0
  35. package/dist/src/anomaly/index.d.ts +2 -0
  36. package/dist/src/anomaly/index.d.ts.map +1 -0
  37. package/dist/src/anomaly/index.js +7 -0
  38. package/dist/src/anomaly/index.js.map +1 -0
  39. package/dist/src/approval/manager.d.ts +147 -0
  40. package/dist/src/approval/manager.d.ts.map +1 -0
  41. package/dist/src/approval/manager.js +511 -0
  42. package/dist/src/approval/manager.js.map +1 -0
  43. package/dist/src/approval/webhook.d.ts +36 -0
  44. package/dist/src/approval/webhook.d.ts.map +1 -0
  45. package/dist/src/approval/webhook.js +135 -0
  46. package/dist/src/approval/webhook.js.map +1 -0
  47. package/dist/src/audit/logger.d.ts +70 -0
  48. package/dist/src/audit/logger.d.ts.map +1 -0
  49. package/dist/src/audit/logger.js +440 -0
  50. package/dist/src/audit/logger.js.map +1 -0
  51. package/dist/src/auth/index.d.ts +6 -0
  52. package/dist/src/auth/index.d.ts.map +1 -0
  53. package/dist/src/auth/index.js +22 -0
  54. package/dist/src/auth/index.js.map +1 -0
  55. package/dist/src/auth/password.d.ts +3 -0
  56. package/dist/src/auth/password.d.ts.map +1 -0
  57. package/dist/src/auth/password.js +25 -0
  58. package/dist/src/auth/password.js.map +1 -0
  59. package/dist/src/auth/pkce.d.ts +13 -0
  60. package/dist/src/auth/pkce.d.ts.map +1 -0
  61. package/dist/src/auth/pkce.js +58 -0
  62. package/dist/src/auth/pkce.js.map +1 -0
  63. package/dist/src/auth/providers.d.ts +28 -0
  64. package/dist/src/auth/providers.d.ts.map +1 -0
  65. package/dist/src/auth/providers.js +198 -0
  66. package/dist/src/auth/providers.js.map +1 -0
  67. package/dist/src/auth/routes.d.ts +14 -0
  68. package/dist/src/auth/routes.d.ts.map +1 -0
  69. package/dist/src/auth/routes.js +431 -0
  70. package/dist/src/auth/routes.js.map +1 -0
  71. package/dist/src/auth/session.d.ts +24 -0
  72. package/dist/src/auth/session.d.ts.map +1 -0
  73. package/dist/src/auth/session.js +105 -0
  74. package/dist/src/auth/session.js.map +1 -0
  75. package/dist/src/billing/index.d.ts +7 -0
  76. package/dist/src/billing/index.d.ts.map +1 -0
  77. package/dist/src/billing/index.js +14 -0
  78. package/dist/src/billing/index.js.map +1 -0
  79. package/dist/src/billing/plan-enforcer.d.ts +44 -0
  80. package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
  81. package/dist/src/billing/plan-enforcer.js +110 -0
  82. package/dist/src/billing/plan-enforcer.js.map +1 -0
  83. package/dist/src/billing/routes.d.ts +15 -0
  84. package/dist/src/billing/routes.d.ts.map +1 -0
  85. package/dist/src/billing/routes.js +193 -0
  86. package/dist/src/billing/routes.js.map +1 -0
  87. package/dist/src/billing/stripe-client.d.ts +14 -0
  88. package/dist/src/billing/stripe-client.d.ts.map +1 -0
  89. package/dist/src/billing/stripe-client.js +51 -0
  90. package/dist/src/billing/stripe-client.js.map +1 -0
  91. package/dist/src/billing/webhook-handler.d.ts +19 -0
  92. package/dist/src/billing/webhook-handler.d.ts.map +1 -0
  93. package/dist/src/billing/webhook-handler.js +169 -0
  94. package/dist/src/billing/webhook-handler.js.map +1 -0
  95. package/dist/src/billing/webhook-routes.d.ts +5 -0
  96. package/dist/src/billing/webhook-routes.d.ts.map +1 -0
  97. package/dist/src/billing/webhook-routes.js +30 -0
  98. package/dist/src/billing/webhook-routes.js.map +1 -0
  99. package/dist/src/budget/manager.d.ts +95 -0
  100. package/dist/src/budget/manager.d.ts.map +1 -0
  101. package/dist/src/budget/manager.js +547 -0
  102. package/dist/src/budget/manager.js.map +1 -0
  103. package/dist/src/budget/usage-extractor.d.ts +38 -0
  104. package/dist/src/budget/usage-extractor.d.ts.map +1 -0
  105. package/dist/src/budget/usage-extractor.js +165 -0
  106. package/dist/src/budget/usage-extractor.js.map +1 -0
  107. package/dist/src/cli.d.ts +3 -0
  108. package/dist/src/cli.d.ts.map +1 -0
  109. package/dist/src/cli.js +115 -0
  110. package/dist/src/cli.js.map +1 -0
  111. package/dist/src/config/defaults.d.ts +3 -0
  112. package/dist/src/config/defaults.d.ts.map +1 -0
  113. package/dist/src/config/defaults.js +243 -0
  114. package/dist/src/config/defaults.js.map +1 -0
  115. package/dist/src/config/validate.d.ts +15 -0
  116. package/dist/src/config/validate.d.ts.map +1 -0
  117. package/dist/src/config/validate.js +105 -0
  118. package/dist/src/config/validate.js.map +1 -0
  119. package/dist/src/dlp/composite-scanner.d.ts +47 -0
  120. package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
  121. package/dist/src/dlp/composite-scanner.js +186 -0
  122. package/dist/src/dlp/composite-scanner.js.map +1 -0
  123. package/dist/src/dlp/index.d.ts +10 -0
  124. package/dist/src/dlp/index.d.ts.map +1 -0
  125. package/dist/src/dlp/index.js +26 -0
  126. package/dist/src/dlp/index.js.map +1 -0
  127. package/dist/src/dlp/interfaces.d.ts +33 -0
  128. package/dist/src/dlp/interfaces.d.ts.map +1 -0
  129. package/dist/src/dlp/interfaces.js +3 -0
  130. package/dist/src/dlp/interfaces.js.map +1 -0
  131. package/dist/src/dlp/patterns.d.ts +9 -0
  132. package/dist/src/dlp/patterns.d.ts.map +1 -0
  133. package/dist/src/dlp/patterns.js +25 -0
  134. package/dist/src/dlp/patterns.js.map +1 -0
  135. package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
  136. package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
  137. package/dist/src/dlp/prompt-injection-backend.js +148 -0
  138. package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
  139. package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
  140. package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
  141. package/dist/src/dlp/prompt-injection-patterns.js +290 -0
  142. package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
  143. package/dist/src/dlp/regex-backend.d.ts +32 -0
  144. package/dist/src/dlp/regex-backend.d.ts.map +1 -0
  145. package/dist/src/dlp/regex-backend.js +153 -0
  146. package/dist/src/dlp/regex-backend.js.map +1 -0
  147. package/dist/src/dlp/scanner.d.ts +122 -0
  148. package/dist/src/dlp/scanner.d.ts.map +1 -0
  149. package/dist/src/dlp/scanner.js +444 -0
  150. package/dist/src/dlp/scanner.js.map +1 -0
  151. package/dist/src/dlp/text-normalizer.d.ts +41 -0
  152. package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
  153. package/dist/src/dlp/text-normalizer.js +203 -0
  154. package/dist/src/dlp/text-normalizer.js.map +1 -0
  155. package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
  156. package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
  157. package/dist/src/dlp/trufflehog-backend.js +151 -0
  158. package/dist/src/dlp/trufflehog-backend.js.map +1 -0
  159. package/dist/src/executor/http-executor.d.ts +25 -0
  160. package/dist/src/executor/http-executor.d.ts.map +1 -0
  161. package/dist/src/executor/http-executor.js +333 -0
  162. package/dist/src/executor/http-executor.js.map +1 -0
  163. package/dist/src/executor/index.d.ts +6 -0
  164. package/dist/src/executor/index.d.ts.map +1 -0
  165. package/dist/src/executor/index.js +12 -0
  166. package/dist/src/executor/index.js.map +1 -0
  167. package/dist/src/executor/interfaces.d.ts +11 -0
  168. package/dist/src/executor/interfaces.d.ts.map +1 -0
  169. package/dist/src/executor/interfaces.js +3 -0
  170. package/dist/src/executor/interfaces.js.map +1 -0
  171. package/dist/src/executor/noop-executor.d.ts +13 -0
  172. package/dist/src/executor/noop-executor.d.ts.map +1 -0
  173. package/dist/src/executor/noop-executor.js +21 -0
  174. package/dist/src/executor/noop-executor.js.map +1 -0
  175. package/dist/src/executor/registry.d.ts +30 -0
  176. package/dist/src/executor/registry.d.ts.map +1 -0
  177. package/dist/src/executor/registry.js +62 -0
  178. package/dist/src/executor/registry.js.map +1 -0
  179. package/dist/src/executor/slack-executor.d.ts +24 -0
  180. package/dist/src/executor/slack-executor.d.ts.map +1 -0
  181. package/dist/src/executor/slack-executor.js +147 -0
  182. package/dist/src/executor/slack-executor.js.map +1 -0
  183. package/dist/src/index.d.ts +25 -0
  184. package/dist/src/index.d.ts.map +1 -0
  185. package/dist/src/index.js +74 -0
  186. package/dist/src/index.js.map +1 -0
  187. package/dist/src/mcp/auth-verifier.d.ts +23 -0
  188. package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
  189. package/dist/src/mcp/auth-verifier.js +162 -0
  190. package/dist/src/mcp/auth-verifier.js.map +1 -0
  191. package/dist/src/mcp/bridge.d.ts +132 -0
  192. package/dist/src/mcp/bridge.d.ts.map +1 -0
  193. package/dist/src/mcp/bridge.js +734 -0
  194. package/dist/src/mcp/bridge.js.map +1 -0
  195. package/dist/src/mcp/http-transport.d.ts +32 -0
  196. package/dist/src/mcp/http-transport.d.ts.map +1 -0
  197. package/dist/src/mcp/http-transport.js +538 -0
  198. package/dist/src/mcp/http-transport.js.map +1 -0
  199. package/dist/src/mcp/index.d.ts +10 -0
  200. package/dist/src/mcp/index.d.ts.map +1 -0
  201. package/dist/src/mcp/index.js +17 -0
  202. package/dist/src/mcp/index.js.map +1 -0
  203. package/dist/src/mcp/oauth-pages.d.ts +23 -0
  204. package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
  205. package/dist/src/mcp/oauth-pages.js +121 -0
  206. package/dist/src/mcp/oauth-pages.js.map +1 -0
  207. package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
  208. package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
  209. package/dist/src/mcp/oauth-postgres-stores.js +226 -0
  210. package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
  211. package/dist/src/mcp/oauth-provider.d.ts +95 -0
  212. package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
  213. package/dist/src/mcp/oauth-provider.js +360 -0
  214. package/dist/src/mcp/oauth-provider.js.map +1 -0
  215. package/dist/src/mcp/oauth-stores.d.ts +62 -0
  216. package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
  217. package/dist/src/mcp/oauth-stores.js +154 -0
  218. package/dist/src/mcp/oauth-stores.js.map +1 -0
  219. package/dist/src/mcp/server.d.ts +18 -0
  220. package/dist/src/mcp/server.d.ts.map +1 -0
  221. package/dist/src/mcp/server.js +51 -0
  222. package/dist/src/mcp/server.js.map +1 -0
  223. package/dist/src/metrics/collector.d.ts +106 -0
  224. package/dist/src/metrics/collector.d.ts.map +1 -0
  225. package/dist/src/metrics/collector.js +311 -0
  226. package/dist/src/metrics/collector.js.map +1 -0
  227. package/dist/src/metrics/index.d.ts +2 -0
  228. package/dist/src/metrics/index.d.ts.map +1 -0
  229. package/dist/src/metrics/index.js +6 -0
  230. package/dist/src/metrics/index.js.map +1 -0
  231. package/dist/src/middleware/auth.d.ts +77 -0
  232. package/dist/src/middleware/auth.d.ts.map +1 -0
  233. package/dist/src/middleware/auth.js +720 -0
  234. package/dist/src/middleware/auth.js.map +1 -0
  235. package/dist/src/middleware/session.d.ts +18 -0
  236. package/dist/src/middleware/session.d.ts.map +1 -0
  237. package/dist/src/middleware/session.js +67 -0
  238. package/dist/src/middleware/session.js.map +1 -0
  239. package/dist/src/middleware/validate.d.ts +3 -0
  240. package/dist/src/middleware/validate.d.ts.map +1 -0
  241. package/dist/src/middleware/validate.js +85 -0
  242. package/dist/src/middleware/validate.js.map +1 -0
  243. package/dist/src/policy/engine.d.ts +107 -0
  244. package/dist/src/policy/engine.d.ts.map +1 -0
  245. package/dist/src/policy/engine.js +646 -0
  246. package/dist/src/policy/engine.js.map +1 -0
  247. package/dist/src/policy/index.d.ts +3 -0
  248. package/dist/src/policy/index.d.ts.map +1 -0
  249. package/dist/src/policy/index.js +8 -0
  250. package/dist/src/policy/index.js.map +1 -0
  251. package/dist/src/policy/opa-engine.d.ts +176 -0
  252. package/dist/src/policy/opa-engine.d.ts.map +1 -0
  253. package/dist/src/policy/opa-engine.js +790 -0
  254. package/dist/src/policy/opa-engine.js.map +1 -0
  255. package/dist/src/proxy/forward-proxy.d.ts +30 -0
  256. package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
  257. package/dist/src/proxy/forward-proxy.js +580 -0
  258. package/dist/src/proxy/forward-proxy.js.map +1 -0
  259. package/dist/src/proxy/index.d.ts +2 -0
  260. package/dist/src/proxy/index.d.ts.map +1 -0
  261. package/dist/src/proxy/index.js +8 -0
  262. package/dist/src/proxy/index.js.map +1 -0
  263. package/dist/src/ratelimit/limiter.d.ts +45 -0
  264. package/dist/src/ratelimit/limiter.d.ts.map +1 -0
  265. package/dist/src/ratelimit/limiter.js +158 -0
  266. package/dist/src/ratelimit/limiter.js.map +1 -0
  267. package/dist/src/replay/engine.d.ts +40 -0
  268. package/dist/src/replay/engine.d.ts.map +1 -0
  269. package/dist/src/replay/engine.js +106 -0
  270. package/dist/src/replay/engine.js.map +1 -0
  271. package/dist/src/replay/index.d.ts +2 -0
  272. package/dist/src/replay/index.d.ts.map +1 -0
  273. package/dist/src/replay/index.js +6 -0
  274. package/dist/src/replay/index.js.map +1 -0
  275. package/dist/src/saas/index.d.ts +2 -0
  276. package/dist/src/saas/index.d.ts.map +1 -0
  277. package/dist/src/saas/index.js +18 -0
  278. package/dist/src/saas/index.js.map +1 -0
  279. package/dist/src/saas/routes.d.ts +18 -0
  280. package/dist/src/saas/routes.d.ts.map +1 -0
  281. package/dist/src/saas/routes.js +1566 -0
  282. package/dist/src/saas/routes.js.map +1 -0
  283. package/dist/src/server/app.d.ts +44 -0
  284. package/dist/src/server/app.d.ts.map +1 -0
  285. package/dist/src/server/app.js +854 -0
  286. package/dist/src/server/app.js.map +1 -0
  287. package/dist/src/server/errors.d.ts +32 -0
  288. package/dist/src/server/errors.d.ts.map +1 -0
  289. package/dist/src/server/errors.js +39 -0
  290. package/dist/src/server/errors.js.map +1 -0
  291. package/dist/src/server/gateway.d.ts +165 -0
  292. package/dist/src/server/gateway.d.ts.map +1 -0
  293. package/dist/src/server/gateway.js +964 -0
  294. package/dist/src/server/gateway.js.map +1 -0
  295. package/dist/src/server/index.d.ts +2 -0
  296. package/dist/src/server/index.d.ts.map +1 -0
  297. package/dist/src/server/index.js +295 -0
  298. package/dist/src/server/index.js.map +1 -0
  299. package/dist/src/server/logger.d.ts +33 -0
  300. package/dist/src/server/logger.d.ts.map +1 -0
  301. package/dist/src/server/logger.js +230 -0
  302. package/dist/src/server/logger.js.map +1 -0
  303. package/dist/src/server/stream-proxy.d.ts +32 -0
  304. package/dist/src/server/stream-proxy.d.ts.map +1 -0
  305. package/dist/src/server/stream-proxy.js +184 -0
  306. package/dist/src/server/stream-proxy.js.map +1 -0
  307. package/dist/src/storage/file-persistence.d.ts +48 -0
  308. package/dist/src/storage/file-persistence.d.ts.map +1 -0
  309. package/dist/src/storage/file-persistence.js +280 -0
  310. package/dist/src/storage/file-persistence.js.map +1 -0
  311. package/dist/src/storage/index.d.ts +5 -0
  312. package/dist/src/storage/index.d.ts.map +1 -0
  313. package/dist/src/storage/index.js +21 -0
  314. package/dist/src/storage/index.js.map +1 -0
  315. package/dist/src/storage/interfaces.d.ts +237 -0
  316. package/dist/src/storage/interfaces.d.ts.map +1 -0
  317. package/dist/src/storage/interfaces.js +3 -0
  318. package/dist/src/storage/interfaces.js.map +1 -0
  319. package/dist/src/storage/memory.d.ts +162 -0
  320. package/dist/src/storage/memory.d.ts.map +1 -0
  321. package/dist/src/storage/memory.js +603 -0
  322. package/dist/src/storage/memory.js.map +1 -0
  323. package/dist/src/storage/postgres.d.ts +267 -0
  324. package/dist/src/storage/postgres.d.ts.map +1 -0
  325. package/dist/src/storage/postgres.js +1555 -0
  326. package/dist/src/storage/postgres.js.map +1 -0
  327. package/dist/src/storage/redis.d.ts +202 -0
  328. package/dist/src/storage/redis.d.ts.map +1 -0
  329. package/dist/src/storage/redis.js +629 -0
  330. package/dist/src/storage/redis.js.map +1 -0
  331. package/dist/src/tracing/index.d.ts +2 -0
  332. package/dist/src/tracing/index.d.ts.map +1 -0
  333. package/dist/src/tracing/index.js +6 -0
  334. package/dist/src/tracing/index.js.map +1 -0
  335. package/dist/src/tracing/provider.d.ts +43 -0
  336. package/dist/src/tracing/provider.d.ts.map +1 -0
  337. package/dist/src/tracing/provider.js +74 -0
  338. package/dist/src/tracing/provider.js.map +1 -0
  339. package/dist/src/trust/calculator.d.ts +54 -0
  340. package/dist/src/trust/calculator.d.ts.map +1 -0
  341. package/dist/src/trust/calculator.js +102 -0
  342. package/dist/src/trust/calculator.js.map +1 -0
  343. package/dist/src/trust/index.d.ts +2 -0
  344. package/dist/src/trust/index.d.ts.map +1 -0
  345. package/dist/src/trust/index.js +7 -0
  346. package/dist/src/trust/index.js.map +1 -0
  347. package/dist/src/types/budget.d.ts +30 -0
  348. package/dist/src/types/budget.d.ts.map +1 -0
  349. package/dist/src/types/budget.js +3 -0
  350. package/dist/src/types/budget.js.map +1 -0
  351. package/dist/src/types/config.d.ts +176 -0
  352. package/dist/src/types/config.d.ts.map +1 -0
  353. package/dist/src/types/config.js +3 -0
  354. package/dist/src/types/config.js.map +1 -0
  355. package/dist/src/types/events.d.ts +24 -0
  356. package/dist/src/types/events.d.ts.map +1 -0
  357. package/dist/src/types/events.js +3 -0
  358. package/dist/src/types/events.js.map +1 -0
  359. package/dist/src/types/index.d.ts +8 -0
  360. package/dist/src/types/index.d.ts.map +1 -0
  361. package/dist/src/types/index.js +24 -0
  362. package/dist/src/types/index.js.map +1 -0
  363. package/dist/src/types/policy.d.ts +60 -0
  364. package/dist/src/types/policy.d.ts.map +1 -0
  365. package/dist/src/types/policy.js +3 -0
  366. package/dist/src/types/policy.js.map +1 -0
  367. package/dist/src/types/stripe-config.d.ts +12 -0
  368. package/dist/src/types/stripe-config.d.ts.map +1 -0
  369. package/dist/src/types/stripe-config.js +3 -0
  370. package/dist/src/types/stripe-config.js.map +1 -0
  371. package/dist/src/types/subscription.d.ts +24 -0
  372. package/dist/src/types/subscription.d.ts.map +1 -0
  373. package/dist/src/types/subscription.js +38 -0
  374. package/dist/src/types/subscription.js.map +1 -0
  375. package/dist/src/types/tool-call.d.ts +42 -0
  376. package/dist/src/types/tool-call.d.ts.map +1 -0
  377. package/dist/src/types/tool-call.js +3 -0
  378. package/dist/src/types/tool-call.js.map +1 -0
  379. package/dist/src/types/tool-result.d.ts +58 -0
  380. package/dist/src/types/tool-result.d.ts.map +1 -0
  381. package/dist/src/types/tool-result.js +3 -0
  382. package/dist/src/types/tool-result.js.map +1 -0
  383. package/dist/src/types/user.d.ts +101 -0
  384. package/dist/src/types/user.d.ts.map +1 -0
  385. package/dist/src/types/user.js +6 -0
  386. package/dist/src/types/user.js.map +1 -0
  387. package/dist/tests/integration/api.test.d.ts +2 -0
  388. package/dist/tests/integration/api.test.d.ts.map +1 -0
  389. package/dist/tests/integration/api.test.js +1199 -0
  390. package/dist/tests/integration/api.test.js.map +1 -0
  391. package/dist/tests/integration/proxy.test.d.ts +2 -0
  392. package/dist/tests/integration/proxy.test.d.ts.map +1 -0
  393. package/dist/tests/integration/proxy.test.js +251 -0
  394. package/dist/tests/integration/proxy.test.js.map +1 -0
  395. package/dist/tests/integration/storage.test.d.ts +16 -0
  396. package/dist/tests/integration/storage.test.d.ts.map +1 -0
  397. package/dist/tests/integration/storage.test.js +826 -0
  398. package/dist/tests/integration/storage.test.js.map +1 -0
  399. package/dist/tests/unit/admin.test.d.ts +2 -0
  400. package/dist/tests/unit/admin.test.d.ts.map +1 -0
  401. package/dist/tests/unit/admin.test.js +698 -0
  402. package/dist/tests/unit/admin.test.js.map +1 -0
  403. package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
  404. package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
  405. package/dist/tests/unit/anomaly-detector.test.js +903 -0
  406. package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
  407. package/dist/tests/unit/approval-manager.test.d.ts +2 -0
  408. package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
  409. package/dist/tests/unit/approval-manager.test.js +528 -0
  410. package/dist/tests/unit/approval-manager.test.js.map +1 -0
  411. package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
  412. package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
  413. package/dist/tests/unit/approval-webhook.test.js +355 -0
  414. package/dist/tests/unit/approval-webhook.test.js.map +1 -0
  415. package/dist/tests/unit/audit-logger.test.d.ts +2 -0
  416. package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
  417. package/dist/tests/unit/audit-logger.test.js +635 -0
  418. package/dist/tests/unit/audit-logger.test.js.map +1 -0
  419. package/dist/tests/unit/auth-routes.test.d.ts +2 -0
  420. package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
  421. package/dist/tests/unit/auth-routes.test.js +281 -0
  422. package/dist/tests/unit/auth-routes.test.js.map +1 -0
  423. package/dist/tests/unit/auth.test.d.ts +2 -0
  424. package/dist/tests/unit/auth.test.d.ts.map +1 -0
  425. package/dist/tests/unit/auth.test.js +1382 -0
  426. package/dist/tests/unit/auth.test.js.map +1 -0
  427. package/dist/tests/unit/billing.test.d.ts +2 -0
  428. package/dist/tests/unit/billing.test.d.ts.map +1 -0
  429. package/dist/tests/unit/billing.test.js +579 -0
  430. package/dist/tests/unit/billing.test.js.map +1 -0
  431. package/dist/tests/unit/budget-manager.test.d.ts +2 -0
  432. package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
  433. package/dist/tests/unit/budget-manager.test.js +778 -0
  434. package/dist/tests/unit/budget-manager.test.js.map +1 -0
  435. package/dist/tests/unit/budget-race.test.d.ts +2 -0
  436. package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
  437. package/dist/tests/unit/budget-race.test.js +58 -0
  438. package/dist/tests/unit/budget-race.test.js.map +1 -0
  439. package/dist/tests/unit/cli.test.d.ts +2 -0
  440. package/dist/tests/unit/cli.test.d.ts.map +1 -0
  441. package/dist/tests/unit/cli.test.js +93 -0
  442. package/dist/tests/unit/cli.test.js.map +1 -0
  443. package/dist/tests/unit/concurrency.test.d.ts +2 -0
  444. package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
  445. package/dist/tests/unit/concurrency.test.js +1270 -0
  446. package/dist/tests/unit/concurrency.test.js.map +1 -0
  447. package/dist/tests/unit/config-validate.test.d.ts +2 -0
  448. package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
  449. package/dist/tests/unit/config-validate.test.js +230 -0
  450. package/dist/tests/unit/config-validate.test.js.map +1 -0
  451. package/dist/tests/unit/defaults.test.d.ts +2 -0
  452. package/dist/tests/unit/defaults.test.d.ts.map +1 -0
  453. package/dist/tests/unit/defaults.test.js +364 -0
  454. package/dist/tests/unit/defaults.test.js.map +1 -0
  455. package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
  456. package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
  457. package/dist/tests/unit/dlp-backends.test.js +563 -0
  458. package/dist/tests/unit/dlp-backends.test.js.map +1 -0
  459. package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
  460. package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
  461. package/dist/tests/unit/dlp-scanner.test.js +739 -0
  462. package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
  463. package/dist/tests/unit/error-responses.test.d.ts +2 -0
  464. package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
  465. package/dist/tests/unit/error-responses.test.js +101 -0
  466. package/dist/tests/unit/error-responses.test.js.map +1 -0
  467. package/dist/tests/unit/executor-registry.test.d.ts +2 -0
  468. package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
  469. package/dist/tests/unit/executor-registry.test.js +390 -0
  470. package/dist/tests/unit/executor-registry.test.js.map +1 -0
  471. package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
  472. package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
  473. package/dist/tests/unit/forward-proxy.test.js +621 -0
  474. package/dist/tests/unit/forward-proxy.test.js.map +1 -0
  475. package/dist/tests/unit/gateway-features.test.d.ts +2 -0
  476. package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
  477. package/dist/tests/unit/gateway-features.test.js +753 -0
  478. package/dist/tests/unit/gateway-features.test.js.map +1 -0
  479. package/dist/tests/unit/http-executor.test.d.ts +2 -0
  480. package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
  481. package/dist/tests/unit/http-executor.test.js +310 -0
  482. package/dist/tests/unit/http-executor.test.js.map +1 -0
  483. package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
  484. package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
  485. package/dist/tests/unit/mcp-bridge.test.js +1136 -0
  486. package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
  487. package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
  488. package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
  489. package/dist/tests/unit/mcp-http-transport.test.js +899 -0
  490. package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
  491. package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
  492. package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
  493. package/dist/tests/unit/mcp-oauth.test.js +759 -0
  494. package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
  495. package/dist/tests/unit/mcp-server.test.d.ts +15 -0
  496. package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
  497. package/dist/tests/unit/mcp-server.test.js +158 -0
  498. package/dist/tests/unit/mcp-server.test.js.map +1 -0
  499. package/dist/tests/unit/metrics.test.d.ts +2 -0
  500. package/dist/tests/unit/metrics.test.d.ts.map +1 -0
  501. package/dist/tests/unit/metrics.test.js +208 -0
  502. package/dist/tests/unit/metrics.test.js.map +1 -0
  503. package/dist/tests/unit/oauth.test.d.ts +2 -0
  504. package/dist/tests/unit/oauth.test.d.ts.map +1 -0
  505. package/dist/tests/unit/oauth.test.js +281 -0
  506. package/dist/tests/unit/oauth.test.js.map +1 -0
  507. package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
  508. package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
  509. package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
  510. package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
  511. package/dist/tests/unit/opa-engine.test.d.ts +2 -0
  512. package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
  513. package/dist/tests/unit/opa-engine.test.js +1813 -0
  514. package/dist/tests/unit/opa-engine.test.js.map +1 -0
  515. package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
  516. package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
  517. package/dist/tests/unit/pipeline-timing.test.js +528 -0
  518. package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
  519. package/dist/tests/unit/policy-engine.test.d.ts +2 -0
  520. package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
  521. package/dist/tests/unit/policy-engine.test.js +1345 -0
  522. package/dist/tests/unit/policy-engine.test.js.map +1 -0
  523. package/dist/tests/unit/policy-store.test.d.ts +2 -0
  524. package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
  525. package/dist/tests/unit/policy-store.test.js +60 -0
  526. package/dist/tests/unit/policy-store.test.js.map +1 -0
  527. package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
  528. package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
  529. package/dist/tests/unit/postgres-storage.test.js +614 -0
  530. package/dist/tests/unit/postgres-storage.test.js.map +1 -0
  531. package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
  532. package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
  533. package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
  534. package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
  535. package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
  536. package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
  537. package/dist/tests/unit/proxy-hardening.test.js +166 -0
  538. package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
  539. package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
  540. package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
  541. package/dist/tests/unit/rate-limiter.test.js +443 -0
  542. package/dist/tests/unit/rate-limiter.test.js.map +1 -0
  543. package/dist/tests/unit/redis-storage.test.d.ts +2 -0
  544. package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
  545. package/dist/tests/unit/redis-storage.test.js +766 -0
  546. package/dist/tests/unit/redis-storage.test.js.map +1 -0
  547. package/dist/tests/unit/replay-engine.test.d.ts +2 -0
  548. package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
  549. package/dist/tests/unit/replay-engine.test.js +371 -0
  550. package/dist/tests/unit/replay-engine.test.js.map +1 -0
  551. package/dist/tests/unit/saas-routes.test.d.ts +2 -0
  552. package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
  553. package/dist/tests/unit/saas-routes.test.js +1399 -0
  554. package/dist/tests/unit/saas-routes.test.js.map +1 -0
  555. package/dist/tests/unit/session.test.d.ts +2 -0
  556. package/dist/tests/unit/session.test.d.ts.map +1 -0
  557. package/dist/tests/unit/session.test.js +532 -0
  558. package/dist/tests/unit/session.test.js.map +1 -0
  559. package/dist/tests/unit/slack-executor.test.d.ts +2 -0
  560. package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
  561. package/dist/tests/unit/slack-executor.test.js +209 -0
  562. package/dist/tests/unit/slack-executor.test.js.map +1 -0
  563. package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
  564. package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
  565. package/dist/tests/unit/storage-hardening.test.js +165 -0
  566. package/dist/tests/unit/storage-hardening.test.js.map +1 -0
  567. package/dist/tests/unit/storage.test.d.ts +2 -0
  568. package/dist/tests/unit/storage.test.d.ts.map +1 -0
  569. package/dist/tests/unit/storage.test.js +698 -0
  570. package/dist/tests/unit/storage.test.js.map +1 -0
  571. package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
  572. package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
  573. package/dist/tests/unit/text-normalizer.test.js +229 -0
  574. package/dist/tests/unit/text-normalizer.test.js.map +1 -0
  575. package/dist/tests/unit/tracing.test.d.ts +2 -0
  576. package/dist/tests/unit/tracing.test.d.ts.map +1 -0
  577. package/dist/tests/unit/tracing.test.js +611 -0
  578. package/dist/tests/unit/tracing.test.js.map +1 -0
  579. package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
  580. package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
  581. package/dist/tests/unit/trust-calculator.test.js +497 -0
  582. package/dist/tests/unit/trust-calculator.test.js.map +1 -0
  583. package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
  584. package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
  585. package/dist/tests/unit/ts-sdk.test.js +421 -0
  586. package/dist/tests/unit/ts-sdk.test.js.map +1 -0
  587. package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
  588. package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
  589. package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
  590. package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
  591. package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
  592. package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
  593. package/dist/tests/unit/usage-extractor.test.js +271 -0
  594. package/dist/tests/unit/usage-extractor.test.js.map +1 -0
  595. package/dist/tests/unit/user-stores.test.d.ts +2 -0
  596. package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
  597. package/dist/tests/unit/user-stores.test.js +687 -0
  598. package/dist/tests/unit/user-stores.test.js.map +1 -0
  599. package/dist/tests/unit/validate.test.d.ts +2 -0
  600. package/dist/tests/unit/validate.test.d.ts.map +1 -0
  601. package/dist/tests/unit/validate.test.js +545 -0
  602. package/dist/tests/unit/validate.test.js.map +1 -0
  603. package/package.json +86 -0
  604. package/policy-packs/README.md +42 -0
  605. package/policy-packs/default.yaml +46 -0
  606. package/policy-packs/dev_fast.yaml +54 -0
  607. package/policy-packs/prod_strict.yaml +83 -0
@@ -0,0 +1,826 @@
1
+ "use strict";
2
+ /**
3
+ * Integration tests for Redis and PostgreSQL storage backends.
4
+ *
5
+ * These tests connect to real Redis / Postgres instances and are skipped
6
+ * automatically when the corresponding environment variable is not set:
7
+ *
8
+ * REDIS_URL -- e.g. redis://localhost:6379
9
+ * DATABASE_URL -- e.g. postgresql://user:pass@localhost:5432/testdb
10
+ *
11
+ * To run locally:
12
+ * REDIS_URL=redis://localhost:6379 DATABASE_URL=postgresql://... npx jest tests/integration/storage.test.ts
13
+ *
14
+ * In CI with docker-compose the env vars are set automatically.
15
+ */
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ const ioredis_1 = __importDefault(require("ioredis"));
21
+ const pg_1 = require("pg");
22
+ const crypto_1 = require("crypto");
23
+ const redis_1 = require("../../src/storage/redis");
24
+ const postgres_1 = require("../../src/storage/postgres");
25
+ // ---------------------------------------------------------------------------
26
+ // Skip logic: only run when services are reachable
27
+ // ---------------------------------------------------------------------------
28
+ const REDIS_URL = process.env.REDIS_URL;
29
+ const DATABASE_URL = process.env.DATABASE_URL;
30
+ const describeRedis = REDIS_URL ? describe : describe.skip;
31
+ const describePostgres = DATABASE_URL ? describe : describe.skip;
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+ /** Small delay to let fire-and-forget writes land in Redis / Postgres. */
36
+ const settle = (ms = 100) => new Promise((r) => setTimeout(r, ms));
37
+ function makeToolResult(overrides = {}) {
38
+ return {
39
+ tool_call_id: 'tc-001',
40
+ task_id: 'task-001',
41
+ status: 'ok',
42
+ policy: { decision: 'allow', reasons: [] },
43
+ dlp: { detected: [], redactions: [], severity: 'low' },
44
+ budget: { estimated_cost_usd: 0, spent_cost_usd_task: 0, remaining_cost_usd_task: 100 },
45
+ timing: { started_at: new Date().toISOString(), duration_ms: 10 },
46
+ ...overrides,
47
+ };
48
+ }
49
+ function makeApprovalRecord(overrides = {}) {
50
+ return {
51
+ approval_id: 'appr-001',
52
+ tool_call_id: 'tc-001',
53
+ task_id: 'task-001',
54
+ workspace_id: 'ws-001',
55
+ actor_id: 'actor-001',
56
+ tool_name: 'http.get',
57
+ tool_capability: 'read',
58
+ args_summary: '{}',
59
+ scope: 'global',
60
+ reason: 'test',
61
+ token_hash: 'hash-001',
62
+ status: 'pending',
63
+ created_at: new Date().toISOString(),
64
+ expires_at: new Date(Date.now() + 3600000).toISOString(),
65
+ ...overrides,
66
+ };
67
+ }
68
+ function makeAuditEvent(overrides = {}) {
69
+ return {
70
+ event_id: (0, crypto_1.randomUUID)(),
71
+ event_type: 'TOOL_CALL_RECEIVED',
72
+ timestamp: new Date().toISOString(),
73
+ tool_call_id: (0, crypto_1.randomUUID)(),
74
+ task_id: (0, crypto_1.randomUUID)(),
75
+ workspace_id: 'ws-integration',
76
+ actor_id: 'actor-integration',
77
+ tool_name: 'http.get',
78
+ metadata: {},
79
+ ...overrides,
80
+ };
81
+ }
82
+ function makeBudgetState(overrides = {}) {
83
+ return {
84
+ task_id: (0, crypto_1.randomUUID)(),
85
+ workspace_id: 'ws-integration',
86
+ actor_id: 'actor-integration',
87
+ spent_usd: 0,
88
+ steps: 0,
89
+ started_at: new Date().toISOString(),
90
+ ...overrides,
91
+ };
92
+ }
93
+ // ===========================================================================
94
+ // REDIS INTEGRATION TESTS
95
+ // ===========================================================================
96
+ describeRedis('Redis Storage Integration', () => {
97
+ let redis;
98
+ const TEST_PREFIX = `test:${Date.now()}:`;
99
+ let rateLimitStore;
100
+ let idempotencyStore;
101
+ let budgetStore;
102
+ let auditStore;
103
+ let approvalStore;
104
+ beforeAll(() => {
105
+ redis = new ioredis_1.default(REDIS_URL);
106
+ const stores = (0, redis_1.createRedisStores)(redis, TEST_PREFIX);
107
+ rateLimitStore = stores.rateLimitStore;
108
+ idempotencyStore = stores.idempotencyStore;
109
+ budgetStore = stores.budgetStore;
110
+ auditStore = stores.auditStore;
111
+ approvalStore = stores.approvalStore;
112
+ });
113
+ afterEach(async () => {
114
+ // Clean up all keys created by this test run
115
+ const pattern = TEST_PREFIX + '*';
116
+ let cursor = '0';
117
+ do {
118
+ const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 200);
119
+ cursor = nextCursor;
120
+ if (keys.length > 0) {
121
+ await redis.del(...keys);
122
+ }
123
+ } while (cursor !== '0');
124
+ // Reset in-memory caches so tests are independent
125
+ rateLimitStore.reset();
126
+ idempotencyStore.clear();
127
+ budgetStore.reset();
128
+ auditStore.clear();
129
+ approvalStore.clear();
130
+ // Let fire-and-forget deletes settle
131
+ await settle(50);
132
+ });
133
+ afterAll(async () => {
134
+ await redis.quit();
135
+ });
136
+ // -------------------------------------------------------------------------
137
+ // RedisRateLimitStore
138
+ // -------------------------------------------------------------------------
139
+ describe('RedisRateLimitStore', () => {
140
+ it('should allow requests within the limit', () => {
141
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
142
+ const result = rateLimitStore.hit(key, 60000, 10);
143
+ expect(result.allowed).toBe(true);
144
+ expect(result.current).toBe(1);
145
+ expect(result.limit).toBe(10);
146
+ expect(result.resetAt).toBeGreaterThan(Date.now() - 1000);
147
+ });
148
+ it('should deny requests exceeding the limit', () => {
149
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
150
+ const windowMs = 60000;
151
+ const maxRequests = 3;
152
+ // Fire 3 allowed hits
153
+ for (let i = 0; i < maxRequests; i++) {
154
+ const r = rateLimitStore.hit(key, windowMs, maxRequests);
155
+ expect(r.allowed).toBe(true);
156
+ expect(r.current).toBe(i + 1);
157
+ }
158
+ // 4th should be denied
159
+ const denied = rateLimitStore.hit(key, windowMs, maxRequests);
160
+ expect(denied.allowed).toBe(false);
161
+ expect(denied.current).toBe(maxRequests + 1);
162
+ });
163
+ it('should expire entries outside the sliding window', async () => {
164
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
165
+ const windowMs = 200; // 200ms window
166
+ rateLimitStore.hit(key, windowMs, 1);
167
+ // Wait for the window to expire
168
+ await settle(300);
169
+ // Next hit should be within the limit (old entries pruned)
170
+ const result = rateLimitStore.hit(key, windowMs, 1);
171
+ expect(result.allowed).toBe(true);
172
+ expect(result.current).toBe(1);
173
+ });
174
+ it('should persist hits to Redis sorted sets', async () => {
175
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
176
+ rateLimitStore.hit(key, 60000, 10);
177
+ rateLimitStore.hit(key, 60000, 10);
178
+ // Let the fire-and-forget pipeline execute
179
+ await settle(200);
180
+ const redisKey = TEST_PREFIX + 'rl:' + key;
181
+ const count = await redis.zcard(redisKey);
182
+ expect(count).toBe(2);
183
+ });
184
+ });
185
+ // -------------------------------------------------------------------------
186
+ // RedisIdempotencyStore
187
+ // -------------------------------------------------------------------------
188
+ describe('RedisIdempotencyStore', () => {
189
+ it('should set and get a cached result', async () => {
190
+ const id = (0, crypto_1.randomUUID)();
191
+ const result = makeToolResult({ tool_call_id: id });
192
+ idempotencyStore.set(id, result, 60000);
193
+ const cached = await idempotencyStore.get(id);
194
+ expect(cached?.tool_call_id).toBe(id);
195
+ expect(cached?.status).toBe('ok');
196
+ });
197
+ it('should report has() correctly', async () => {
198
+ const id = (0, crypto_1.randomUUID)();
199
+ expect(await idempotencyStore.has(id)).toBe(false);
200
+ idempotencyStore.set(id, makeToolResult({ tool_call_id: id }), 60000);
201
+ expect(await idempotencyStore.has(id)).toBe(true);
202
+ });
203
+ it('should expire entries after TTL', async () => {
204
+ const id = (0, crypto_1.randomUUID)();
205
+ const result = makeToolResult({ tool_call_id: id });
206
+ idempotencyStore.set(id, result, 150); // 150ms TTL
207
+ const cached = await idempotencyStore.get(id);
208
+ expect(cached?.tool_call_id).toBe(id);
209
+ // Wait for local cache expiry
210
+ await settle(250);
211
+ expect(await idempotencyStore.get(id)).toBeUndefined();
212
+ expect(await idempotencyStore.has(id)).toBe(false);
213
+ });
214
+ it('should persist to Redis with PX expiry', async () => {
215
+ const id = (0, crypto_1.randomUUID)();
216
+ const result = makeToolResult({ tool_call_id: id });
217
+ idempotencyStore.set(id, result, 30000);
218
+ // Let fire-and-forget SET complete
219
+ await settle(200);
220
+ const redisKey = TEST_PREFIX + 'idem:' + id;
221
+ const raw = await redis.get(redisKey);
222
+ expect(raw).not.toBeNull();
223
+ expect(JSON.parse(raw).tool_call_id).toBe(id);
224
+ const ttl = await redis.pttl(redisKey);
225
+ expect(ttl).toBeGreaterThan(0);
226
+ expect(ttl).toBeLessThanOrEqual(30000);
227
+ });
228
+ it('should return undefined for unknown keys', async () => {
229
+ expect(await idempotencyStore.get((0, crypto_1.randomUUID)())).toBeUndefined();
230
+ });
231
+ });
232
+ // -------------------------------------------------------------------------
233
+ // RedisBudgetStore
234
+ // -------------------------------------------------------------------------
235
+ describe('RedisBudgetStore', () => {
236
+ it('should set and get task state', () => {
237
+ const state = makeBudgetState();
238
+ budgetStore.setTaskState(state.task_id, state);
239
+ const retrieved = budgetStore.getTaskState(state.task_id);
240
+ expect(retrieved).toEqual(state);
241
+ });
242
+ it('should return undefined for unknown task', () => {
243
+ expect(budgetStore.getTaskState((0, crypto_1.randomUUID)())).toBeUndefined();
244
+ });
245
+ it('should increment and get counters', () => {
246
+ const key = `spend:${(0, crypto_1.randomUUID)()}`;
247
+ expect(budgetStore.getCounter(key)).toBe(0);
248
+ budgetStore.incrementCounter(key, 1.50);
249
+ expect(budgetStore.getCounter(key)).toBeCloseTo(1.50);
250
+ budgetStore.incrementCounter(key, 2.25);
251
+ expect(budgetStore.getCounter(key)).toBeCloseTo(3.75);
252
+ });
253
+ it('should track retry counts', () => {
254
+ const toolCallId = (0, crypto_1.randomUUID)();
255
+ expect(budgetStore.getRetryCount(toolCallId)).toBe(0);
256
+ const count1 = budgetStore.incrementRetryCount(toolCallId);
257
+ expect(count1).toBe(1);
258
+ expect(budgetStore.getRetryCount(toolCallId)).toBe(1);
259
+ const count2 = budgetStore.incrementRetryCount(toolCallId);
260
+ expect(count2).toBe(2);
261
+ expect(budgetStore.getRetryCount(toolCallId)).toBe(2);
262
+ });
263
+ it('should persist task state to Redis', async () => {
264
+ const state = makeBudgetState({ spent_usd: 5.99, steps: 3 });
265
+ budgetStore.setTaskState(state.task_id, state);
266
+ await settle(200);
267
+ const redisKey = TEST_PREFIX + 'budget:task:' + state.task_id;
268
+ const data = await redis.hgetall(redisKey);
269
+ expect(data.task_id).toBe(state.task_id);
270
+ expect(Number(data.spent_usd)).toBeCloseTo(5.99);
271
+ expect(Number(data.steps)).toBe(3);
272
+ });
273
+ it('should persist counters to Redis', async () => {
274
+ const key = `spend:${(0, crypto_1.randomUUID)()}`;
275
+ budgetStore.incrementCounter(key, 10.5);
276
+ await settle(200);
277
+ const redisKey = TEST_PREFIX + 'budget:counters';
278
+ const raw = await redis.hget(redisKey, key);
279
+ expect(Number(raw)).toBeCloseTo(10.5);
280
+ });
281
+ it('should hydrate from Redis after reset', async () => {
282
+ const state = makeBudgetState({ spent_usd: 7.77, steps: 5 });
283
+ budgetStore.setTaskState(state.task_id, state);
284
+ budgetStore.incrementCounter('daily:actor-1', 42);
285
+ await settle(200);
286
+ // Create a fresh store pointed at the same prefix
287
+ const freshStore = new redis_1.RedisBudgetStore(redis, TEST_PREFIX + 'budget:');
288
+ await freshStore.hydrate();
289
+ expect(freshStore.getTaskState(state.task_id)).toEqual(state);
290
+ expect(freshStore.getCounter('daily:actor-1')).toBe(42);
291
+ });
292
+ });
293
+ // -------------------------------------------------------------------------
294
+ // RedisAuditStore
295
+ // -------------------------------------------------------------------------
296
+ describe('RedisAuditStore', () => {
297
+ it('should append and retrieve events by task ID', () => {
298
+ const taskId = (0, crypto_1.randomUUID)();
299
+ const event1 = makeAuditEvent({ task_id: taskId, event_type: 'TOOL_CALL_RECEIVED' });
300
+ const event2 = makeAuditEvent({ task_id: taskId, event_type: 'POLICY_DECIDED' });
301
+ const otherEvent = makeAuditEvent({ task_id: (0, crypto_1.randomUUID)() });
302
+ auditStore.append(event1);
303
+ auditStore.append(event2);
304
+ auditStore.append(otherEvent);
305
+ const taskEvents = auditStore.getByTaskId(taskId);
306
+ expect(taskEvents).toHaveLength(2);
307
+ expect(taskEvents.map((e) => e.event_id)).toContain(event1.event_id);
308
+ expect(taskEvents.map((e) => e.event_id)).toContain(event2.event_id);
309
+ });
310
+ it('should retrieve events by tool call ID', () => {
311
+ const toolCallId = (0, crypto_1.randomUUID)();
312
+ const event = makeAuditEvent({ tool_call_id: toolCallId });
313
+ const other = makeAuditEvent();
314
+ auditStore.append(event);
315
+ auditStore.append(other);
316
+ const results = auditStore.getByToolCallId(toolCallId);
317
+ expect(results).toHaveLength(1);
318
+ expect(results[0].event_id).toBe(event.event_id);
319
+ });
320
+ it('should retrieve events by event type', () => {
321
+ const e1 = makeAuditEvent({ event_type: 'DLP_SCANNED' });
322
+ const e2 = makeAuditEvent({ event_type: 'DLP_SCANNED' });
323
+ const e3 = makeAuditEvent({ event_type: 'BUDGET_CHECKED' });
324
+ auditStore.append(e1);
325
+ auditStore.append(e2);
326
+ auditStore.append(e3);
327
+ const dlpEvents = auditStore.getByEventType('DLP_SCANNED');
328
+ expect(dlpEvents).toHaveLength(2);
329
+ const budgetEvents = auditStore.getByEventType('BUDGET_CHECKED');
330
+ expect(budgetEvents).toHaveLength(1);
331
+ });
332
+ it('should return all events via getAll()', () => {
333
+ auditStore.append(makeAuditEvent());
334
+ auditStore.append(makeAuditEvent());
335
+ auditStore.append(makeAuditEvent());
336
+ const all = auditStore.getAll();
337
+ expect(all).toHaveLength(3);
338
+ });
339
+ it('should persist events to Redis and support hydration', async () => {
340
+ const taskId = (0, crypto_1.randomUUID)();
341
+ const event = makeAuditEvent({ task_id: taskId });
342
+ auditStore.append(event);
343
+ await settle(200);
344
+ // Verify data key in Redis
345
+ const dataKey = TEST_PREFIX + 'audit:data';
346
+ const raw = await redis.hget(dataKey, event.event_id);
347
+ expect(raw).not.toBeNull();
348
+ expect(JSON.parse(raw).event_id).toBe(event.event_id);
349
+ // Verify task index in Redis
350
+ const taskKey = TEST_PREFIX + 'audit:task:' + taskId;
351
+ const members = await redis.zrange(taskKey, 0, -1);
352
+ expect(members).toContain(event.event_id);
353
+ // Test hydration with a fresh store
354
+ const freshStore = new redis_1.RedisAuditStore(redis, TEST_PREFIX + 'audit:');
355
+ await freshStore.hydrate();
356
+ const hydrated = freshStore.getAll();
357
+ expect(hydrated).toHaveLength(1);
358
+ expect(hydrated[0].event_id).toBe(event.event_id);
359
+ });
360
+ });
361
+ // -------------------------------------------------------------------------
362
+ // RedisApprovalStore
363
+ // -------------------------------------------------------------------------
364
+ describe('RedisApprovalStore', () => {
365
+ it('should save and retrieve by ID', () => {
366
+ const id = (0, crypto_1.randomUUID)();
367
+ const toolCallId = (0, crypto_1.randomUUID)();
368
+ const approval = makeApprovalRecord({
369
+ approval_id: id,
370
+ tool_call_id: toolCallId,
371
+ workspace_id: 'ws-test',
372
+ });
373
+ approvalStore.save(id, approval);
374
+ const retrieved = approvalStore.getById(id);
375
+ expect(retrieved).toEqual(approval);
376
+ });
377
+ it('should return undefined for unknown approval', () => {
378
+ expect(approvalStore.getById((0, crypto_1.randomUUID)())).toBeUndefined();
379
+ });
380
+ it('should index and retrieve by token', () => {
381
+ const id = (0, crypto_1.randomUUID)();
382
+ const token = `tok-${(0, crypto_1.randomUUID)()}`;
383
+ const approval = makeApprovalRecord({
384
+ approval_id: id,
385
+ workspace_id: 'ws-test',
386
+ });
387
+ approvalStore.save(id, approval);
388
+ approvalStore.indexToken(token, id);
389
+ const byToken = approvalStore.getByToken(token);
390
+ expect(byToken).toEqual(approval);
391
+ });
392
+ it('should return undefined for unknown token', () => {
393
+ expect(approvalStore.getByToken('nonexistent-token')).toBeUndefined();
394
+ });
395
+ it('should retrieve by tool_call_id', () => {
396
+ const toolCallId = (0, crypto_1.randomUUID)();
397
+ const id = (0, crypto_1.randomUUID)();
398
+ const approval = makeApprovalRecord({
399
+ approval_id: id,
400
+ tool_call_id: toolCallId,
401
+ workspace_id: 'ws-test',
402
+ });
403
+ approvalStore.save(id, approval);
404
+ const result = approvalStore.getByToolCallId(toolCallId);
405
+ expect(result).toEqual(approval);
406
+ });
407
+ it('should find pending approvals', () => {
408
+ const pending1 = makeApprovalRecord({
409
+ approval_id: (0, crypto_1.randomUUID)(),
410
+ status: 'pending',
411
+ workspace_id: 'ws-a',
412
+ });
413
+ const pending2 = makeApprovalRecord({
414
+ approval_id: (0, crypto_1.randomUUID)(),
415
+ status: 'pending',
416
+ workspace_id: 'ws-b',
417
+ });
418
+ const approved = makeApprovalRecord({
419
+ approval_id: (0, crypto_1.randomUUID)(),
420
+ status: 'approved',
421
+ workspace_id: 'ws-a',
422
+ });
423
+ approvalStore.save(pending1.approval_id, pending1);
424
+ approvalStore.save(pending2.approval_id, pending2);
425
+ approvalStore.save(approved.approval_id, approved);
426
+ // All pending
427
+ const allPending = approvalStore.findPending();
428
+ expect(allPending).toHaveLength(2);
429
+ // Filtered by workspace
430
+ const wsAPending = approvalStore.findPending('ws-a');
431
+ expect(wsAPending).toHaveLength(1);
432
+ expect(wsAPending[0].workspace_id).toBe('ws-a');
433
+ });
434
+ it('should update approval status from pending to approved', () => {
435
+ const id = (0, crypto_1.randomUUID)();
436
+ const approval = makeApprovalRecord({
437
+ approval_id: id,
438
+ workspace_id: 'ws-test',
439
+ });
440
+ approvalStore.save(id, approval);
441
+ expect(approvalStore.findPending()).toHaveLength(1);
442
+ // Update status
443
+ const updated = { ...approval, status: 'approved' };
444
+ approvalStore.save(id, updated);
445
+ expect(approvalStore.getById(id).status).toBe('approved');
446
+ expect(approvalStore.findPending()).toHaveLength(0);
447
+ });
448
+ it('should persist to Redis and support hydration', async () => {
449
+ const id = (0, crypto_1.randomUUID)();
450
+ const token = `tok-${(0, crypto_1.randomUUID)()}`;
451
+ const approval = makeApprovalRecord({
452
+ approval_id: id,
453
+ workspace_id: 'ws-hydrate',
454
+ });
455
+ approvalStore.save(id, approval);
456
+ approvalStore.indexToken(token, id);
457
+ await settle(200);
458
+ // Verify data in Redis
459
+ const dataKey = TEST_PREFIX + 'approvals:data';
460
+ const raw = await redis.hget(dataKey, id);
461
+ expect(raw).not.toBeNull();
462
+ expect(JSON.parse(raw).approval_id).toBe(id);
463
+ // Verify token index in Redis
464
+ const tokensKey = TEST_PREFIX + 'approvals:tokens';
465
+ const tokenApprovalId = await redis.hget(tokensKey, token);
466
+ expect(tokenApprovalId).toBe(id);
467
+ // Hydrate a fresh store
468
+ const freshStore = new redis_1.RedisApprovalStore(redis, TEST_PREFIX + 'approvals:');
469
+ await freshStore.hydrate();
470
+ expect(freshStore.getById(id)).toEqual(approval);
471
+ expect(freshStore.getByToken(token)).toEqual(approval);
472
+ });
473
+ });
474
+ });
475
+ // ===========================================================================
476
+ // POSTGRESQL INTEGRATION TESTS
477
+ // ===========================================================================
478
+ describePostgres('PostgreSQL Storage Integration', () => {
479
+ let pool;
480
+ let budgetStore;
481
+ let auditStore;
482
+ let approvalStore;
483
+ let idempotencyStore;
484
+ let rateLimitStore;
485
+ beforeAll(async () => {
486
+ pool = new pg_1.Pool({ connectionString: DATABASE_URL });
487
+ // Create tables (idempotent DDL)
488
+ await (0, postgres_1.initPostgresStorage)(pool);
489
+ budgetStore = new postgres_1.PostgresBudgetStore(pool);
490
+ auditStore = new postgres_1.PostgresAuditStore(pool);
491
+ approvalStore = new postgres_1.PostgresApprovalStore(pool);
492
+ idempotencyStore = new postgres_1.PostgresIdempotencyStore(pool);
493
+ rateLimitStore = new postgres_1.PostgresRateLimitStore(pool);
494
+ });
495
+ afterEach(async () => {
496
+ // Truncate all tables between tests for isolation.
497
+ // These are synchronous resets on the in-memory cache + async TRUNCATE.
498
+ budgetStore.reset();
499
+ auditStore.clear();
500
+ approvalStore.clear();
501
+ idempotencyStore.clear();
502
+ rateLimitStore.reset();
503
+ // Wait for the async truncate queries to complete
504
+ await settle(200);
505
+ });
506
+ afterAll(async () => {
507
+ // Drop test tables so we leave the database clean
508
+ await pool.query('DROP TABLE IF EXISTS budget_task_states CASCADE');
509
+ await pool.query('DROP TABLE IF EXISTS budget_counters CASCADE');
510
+ await pool.query('DROP TABLE IF EXISTS budget_retry_counts CASCADE');
511
+ await pool.query('DROP TABLE IF EXISTS audit_events CASCADE');
512
+ await pool.query('DROP TABLE IF EXISTS approvals CASCADE');
513
+ await pool.query('DROP TABLE IF EXISTS approval_tokens CASCADE');
514
+ await pool.query('DROP TABLE IF EXISTS idempotency_cache CASCADE');
515
+ await pool.query('DROP TABLE IF EXISTS rate_limit_hits CASCADE');
516
+ await pool.end();
517
+ });
518
+ // -------------------------------------------------------------------------
519
+ // PostgresRateLimitStore
520
+ // -------------------------------------------------------------------------
521
+ describe('PostgresRateLimitStore', () => {
522
+ it('should allow requests within the limit', () => {
523
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
524
+ const result = rateLimitStore.hit(key, 60000, 10);
525
+ expect(result.allowed).toBe(true);
526
+ expect(result.current).toBe(1);
527
+ expect(result.limit).toBe(10);
528
+ });
529
+ it('should deny requests exceeding the limit', () => {
530
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
531
+ const maxRequests = 3;
532
+ for (let i = 0; i < maxRequests; i++) {
533
+ const r = rateLimitStore.hit(key, 60000, maxRequests);
534
+ expect(r.allowed).toBe(true);
535
+ }
536
+ const denied = rateLimitStore.hit(key, 60000, maxRequests);
537
+ expect(denied.allowed).toBe(false);
538
+ expect(denied.current).toBe(maxRequests + 1);
539
+ });
540
+ it('should expire entries outside the sliding window', async () => {
541
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
542
+ const windowMs = 200;
543
+ rateLimitStore.hit(key, windowMs, 1);
544
+ await settle(300);
545
+ const result = rateLimitStore.hit(key, windowMs, 1);
546
+ expect(result.allowed).toBe(true);
547
+ expect(result.current).toBe(1);
548
+ });
549
+ it('should persist hits to Postgres', async () => {
550
+ const key = `actor:${(0, crypto_1.randomUUID)()}`;
551
+ rateLimitStore.hit(key, 60000, 10);
552
+ rateLimitStore.hit(key, 60000, 10);
553
+ await settle(200);
554
+ const { rows } = await pool.query('SELECT COUNT(*) AS cnt FROM rate_limit_hits WHERE key = $1', [key]);
555
+ expect(Number(rows[0].cnt)).toBe(2);
556
+ });
557
+ });
558
+ // -------------------------------------------------------------------------
559
+ // PostgresIdempotencyStore
560
+ // -------------------------------------------------------------------------
561
+ describe('PostgresIdempotencyStore', () => {
562
+ it('should set and get a cached result', async () => {
563
+ const id = (0, crypto_1.randomUUID)();
564
+ const result = makeToolResult({ tool_call_id: id });
565
+ idempotencyStore.set(id, result, 60000);
566
+ const cached = await idempotencyStore.get(id);
567
+ expect(cached?.tool_call_id).toBe(id);
568
+ expect(cached?.status).toBe('ok');
569
+ });
570
+ it('should report has() correctly', async () => {
571
+ const id = (0, crypto_1.randomUUID)();
572
+ expect(await idempotencyStore.has(id)).toBe(false);
573
+ idempotencyStore.set(id, makeToolResult({ tool_call_id: id }), 60000);
574
+ expect(await idempotencyStore.has(id)).toBe(true);
575
+ });
576
+ it('should expire entries after TTL', async () => {
577
+ const id = (0, crypto_1.randomUUID)();
578
+ const result = makeToolResult({ tool_call_id: id });
579
+ idempotencyStore.set(id, result, 150);
580
+ const cached = await idempotencyStore.get(id);
581
+ expect(cached?.tool_call_id).toBe(id);
582
+ await settle(250);
583
+ expect(await idempotencyStore.get(id)).toBeUndefined();
584
+ });
585
+ it('should persist to Postgres', async () => {
586
+ const id = (0, crypto_1.randomUUID)();
587
+ const result = makeToolResult({ tool_call_id: id });
588
+ idempotencyStore.set(id, result, 60000);
589
+ await settle(200);
590
+ const { rows } = await pool.query('SELECT result FROM idempotency_cache WHERE tool_call_id = $1', [id]);
591
+ expect(rows).toHaveLength(1);
592
+ expect(rows[0].result.tool_call_id).toBe(id);
593
+ });
594
+ it('should hydrate from Postgres after cache clear', async () => {
595
+ const id = (0, crypto_1.randomUUID)();
596
+ const result = makeToolResult({ tool_call_id: id });
597
+ idempotencyStore.set(id, result, 60000);
598
+ await settle(200);
599
+ // Create a fresh store and hydrate from Postgres
600
+ const freshStore = new postgres_1.PostgresIdempotencyStore(pool);
601
+ await freshStore.hydrate();
602
+ const hydrated = await freshStore.get(id);
603
+ expect(hydrated).toBeDefined();
604
+ expect(hydrated?.tool_call_id).toBe(id);
605
+ });
606
+ });
607
+ // -------------------------------------------------------------------------
608
+ // PostgresBudgetStore
609
+ // -------------------------------------------------------------------------
610
+ describe('PostgresBudgetStore', () => {
611
+ it('should set and get task state', () => {
612
+ const state = makeBudgetState();
613
+ budgetStore.setTaskState(state.task_id, state);
614
+ const retrieved = budgetStore.getTaskState(state.task_id);
615
+ expect(retrieved).toEqual(state);
616
+ });
617
+ it('should return undefined for unknown task', () => {
618
+ expect(budgetStore.getTaskState((0, crypto_1.randomUUID)())).toBeUndefined();
619
+ });
620
+ it('should increment and get counters', () => {
621
+ const key = `spend:${(0, crypto_1.randomUUID)()}`;
622
+ expect(budgetStore.getCounter(key)).toBe(0);
623
+ budgetStore.incrementCounter(key, 2.50);
624
+ expect(budgetStore.getCounter(key)).toBeCloseTo(2.50);
625
+ budgetStore.incrementCounter(key, 3.75);
626
+ expect(budgetStore.getCounter(key)).toBeCloseTo(6.25);
627
+ });
628
+ it('should track retry counts', () => {
629
+ const toolCallId = (0, crypto_1.randomUUID)();
630
+ expect(budgetStore.getRetryCount(toolCallId)).toBe(0);
631
+ expect(budgetStore.incrementRetryCount(toolCallId)).toBe(1);
632
+ expect(budgetStore.getRetryCount(toolCallId)).toBe(1);
633
+ expect(budgetStore.incrementRetryCount(toolCallId)).toBe(2);
634
+ expect(budgetStore.getRetryCount(toolCallId)).toBe(2);
635
+ });
636
+ it('should persist task state to Postgres', async () => {
637
+ const state = makeBudgetState({ spent_usd: 12.34, steps: 7 });
638
+ budgetStore.setTaskState(state.task_id, state);
639
+ await settle(200);
640
+ const { rows } = await pool.query('SELECT * FROM budget_task_states WHERE task_id = $1', [state.task_id]);
641
+ expect(rows).toHaveLength(1);
642
+ expect(Number(rows[0].spent_usd)).toBeCloseTo(12.34);
643
+ expect(Number(rows[0].steps)).toBe(7);
644
+ });
645
+ it('should persist counters to Postgres', async () => {
646
+ const key = `daily:${(0, crypto_1.randomUUID)()}`;
647
+ budgetStore.incrementCounter(key, 99.9);
648
+ await settle(200);
649
+ const { rows } = await pool.query('SELECT value FROM budget_counters WHERE key = $1', [key]);
650
+ expect(rows).toHaveLength(1);
651
+ expect(Number(rows[0].value)).toBeCloseTo(99.9);
652
+ });
653
+ it('should hydrate from Postgres', async () => {
654
+ const state = makeBudgetState({ spent_usd: 4.56, steps: 2 });
655
+ budgetStore.setTaskState(state.task_id, state);
656
+ budgetStore.incrementCounter('monthly:org-1', 100);
657
+ await settle(200);
658
+ const freshStore = new postgres_1.PostgresBudgetStore(pool);
659
+ await freshStore.hydrate();
660
+ expect(freshStore.getTaskState(state.task_id)).toEqual(state);
661
+ expect(freshStore.getCounter('monthly:org-1')).toBe(100);
662
+ });
663
+ });
664
+ // -------------------------------------------------------------------------
665
+ // PostgresAuditStore
666
+ // -------------------------------------------------------------------------
667
+ describe('PostgresAuditStore', () => {
668
+ it('should append and retrieve events by task ID', () => {
669
+ const taskId = (0, crypto_1.randomUUID)();
670
+ const e1 = makeAuditEvent({ task_id: taskId, event_type: 'TOOL_CALL_RECEIVED' });
671
+ const e2 = makeAuditEvent({ task_id: taskId, event_type: 'TOOL_EXECUTED' });
672
+ const other = makeAuditEvent();
673
+ auditStore.append(e1);
674
+ auditStore.append(e2);
675
+ auditStore.append(other);
676
+ const taskEvents = auditStore.getByTaskId(taskId);
677
+ expect(taskEvents).toHaveLength(2);
678
+ expect(taskEvents.map((e) => e.event_id)).toContain(e1.event_id);
679
+ expect(taskEvents.map((e) => e.event_id)).toContain(e2.event_id);
680
+ });
681
+ it('should retrieve events by tool call ID', () => {
682
+ const toolCallId = (0, crypto_1.randomUUID)();
683
+ const event = makeAuditEvent({ tool_call_id: toolCallId });
684
+ auditStore.append(event);
685
+ auditStore.append(makeAuditEvent());
686
+ const results = auditStore.getByToolCallId(toolCallId);
687
+ expect(results).toHaveLength(1);
688
+ expect(results[0].event_id).toBe(event.event_id);
689
+ });
690
+ it('should retrieve events by event type', () => {
691
+ auditStore.append(makeAuditEvent({ event_type: 'DLP_SCANNED' }));
692
+ auditStore.append(makeAuditEvent({ event_type: 'DLP_SCANNED' }));
693
+ auditStore.append(makeAuditEvent({ event_type: 'BUDGET_CHECKED' }));
694
+ expect(auditStore.getByEventType('DLP_SCANNED')).toHaveLength(2);
695
+ expect(auditStore.getByEventType('BUDGET_CHECKED')).toHaveLength(1);
696
+ });
697
+ it('should return all events', () => {
698
+ auditStore.append(makeAuditEvent());
699
+ auditStore.append(makeAuditEvent());
700
+ expect(auditStore.getAll()).toHaveLength(2);
701
+ });
702
+ it('should persist events to Postgres', async () => {
703
+ const event = makeAuditEvent();
704
+ auditStore.append(event);
705
+ await settle(200);
706
+ const { rows } = await pool.query('SELECT * FROM audit_events WHERE event_id = $1', [event.event_id]);
707
+ expect(rows).toHaveLength(1);
708
+ expect(rows[0].event_type).toBe(event.event_type);
709
+ expect(rows[0].task_id).toBe(event.task_id);
710
+ expect(rows[0].tool_name).toBe(event.tool_name);
711
+ });
712
+ it('should hydrate from Postgres', async () => {
713
+ const event = makeAuditEvent({ event_type: 'POLICY_DECIDED' });
714
+ auditStore.append(event);
715
+ await settle(200);
716
+ const freshStore = new postgres_1.PostgresAuditStore(pool);
717
+ await freshStore.hydrate();
718
+ const all = freshStore.getAll();
719
+ expect(all).toHaveLength(1);
720
+ expect(all[0].event_id).toBe(event.event_id);
721
+ expect(all[0].event_type).toBe('POLICY_DECIDED');
722
+ });
723
+ });
724
+ // -------------------------------------------------------------------------
725
+ // PostgresApprovalStore
726
+ // -------------------------------------------------------------------------
727
+ describe('PostgresApprovalStore', () => {
728
+ it('should save and retrieve by ID', () => {
729
+ const id = (0, crypto_1.randomUUID)();
730
+ const toolCallId = (0, crypto_1.randomUUID)();
731
+ const approval = makeApprovalRecord({
732
+ approval_id: id,
733
+ tool_call_id: toolCallId,
734
+ workspace_id: 'ws-pg-test',
735
+ });
736
+ approvalStore.save(id, approval);
737
+ expect(approvalStore.getById(id)).toEqual(approval);
738
+ });
739
+ it('should return undefined for unknown approval', () => {
740
+ expect(approvalStore.getById((0, crypto_1.randomUUID)())).toBeUndefined();
741
+ });
742
+ it('should index and retrieve by token', () => {
743
+ const id = (0, crypto_1.randomUUID)();
744
+ const token = `tok-${(0, crypto_1.randomUUID)()}`;
745
+ const approval = makeApprovalRecord({
746
+ approval_id: id,
747
+ workspace_id: 'ws-pg',
748
+ });
749
+ approvalStore.save(id, approval);
750
+ approvalStore.indexToken(token, id);
751
+ expect(approvalStore.getByToken(token)).toEqual(approval);
752
+ });
753
+ it('should retrieve by tool_call_id', () => {
754
+ const toolCallId = (0, crypto_1.randomUUID)();
755
+ const id = (0, crypto_1.randomUUID)();
756
+ const approval = makeApprovalRecord({
757
+ approval_id: id,
758
+ tool_call_id: toolCallId,
759
+ workspace_id: 'ws-pg',
760
+ });
761
+ approvalStore.save(id, approval);
762
+ expect(approvalStore.getByToolCallId(toolCallId)).toEqual(approval);
763
+ });
764
+ it('should find pending approvals with workspace filter', () => {
765
+ const p1 = makeApprovalRecord({
766
+ approval_id: 'p1',
767
+ status: 'pending',
768
+ workspace_id: 'ws-alpha',
769
+ });
770
+ const p2 = makeApprovalRecord({
771
+ approval_id: 'p2',
772
+ status: 'pending',
773
+ workspace_id: 'ws-beta',
774
+ });
775
+ const a1 = makeApprovalRecord({
776
+ approval_id: 'a1',
777
+ status: 'approved',
778
+ workspace_id: 'ws-alpha',
779
+ });
780
+ approvalStore.save('p1', p1);
781
+ approvalStore.save('p2', p2);
782
+ approvalStore.save('a1', a1);
783
+ expect(approvalStore.findPending()).toHaveLength(2);
784
+ expect(approvalStore.findPending('ws-alpha')).toHaveLength(1);
785
+ expect(approvalStore.findPending('ws-beta')).toHaveLength(1);
786
+ expect(approvalStore.findPending('ws-nonexistent')).toHaveLength(0);
787
+ });
788
+ it('should update status from pending to denied', () => {
789
+ const id = (0, crypto_1.randomUUID)();
790
+ const approval = makeApprovalRecord({
791
+ approval_id: id,
792
+ workspace_id: 'ws-pg',
793
+ });
794
+ approvalStore.save(id, approval);
795
+ expect(approvalStore.findPending()).toHaveLength(1);
796
+ approvalStore.save(id, { ...approval, status: 'denied' });
797
+ expect(approvalStore.findPending()).toHaveLength(0);
798
+ expect(approvalStore.getById(id).status).toBe('denied');
799
+ });
800
+ it('should persist to Postgres and support hydration', async () => {
801
+ const id = (0, crypto_1.randomUUID)();
802
+ const token = `tok-${(0, crypto_1.randomUUID)()}`;
803
+ const approval = makeApprovalRecord({
804
+ approval_id: id,
805
+ workspace_id: 'ws-hydrate-pg',
806
+ });
807
+ approvalStore.save(id, approval);
808
+ approvalStore.indexToken(token, id);
809
+ await settle(200);
810
+ // Verify row in Postgres
811
+ const { rows } = await pool.query('SELECT * FROM approvals WHERE approval_id = $1', [id]);
812
+ expect(rows).toHaveLength(1);
813
+ expect(rows[0].status).toBe('pending');
814
+ // Verify token row
815
+ const { rows: tokenRows } = await pool.query('SELECT * FROM approval_tokens WHERE token = $1', [token]);
816
+ expect(tokenRows).toHaveLength(1);
817
+ expect(tokenRows[0].approval_id).toBe(id);
818
+ // Hydrate a fresh store
819
+ const freshStore = new postgres_1.PostgresApprovalStore(pool);
820
+ await freshStore.hydrate();
821
+ expect(freshStore.getById(id)).toEqual(approval);
822
+ expect(freshStore.getByToken(token)).toEqual(approval);
823
+ });
824
+ });
825
+ });
826
+ //# sourceMappingURL=storage.test.js.map