palaryn 0.1.0 → 0.3.2

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 (344) hide show
  1. package/README.md +243 -588
  2. package/dist/sdk/typescript/src/client.js +2 -2
  3. package/dist/sdk/typescript/src/client.js.map +1 -1
  4. package/dist/src/anomaly/detector.d.ts +7 -4
  5. package/dist/src/anomaly/detector.d.ts.map +1 -1
  6. package/dist/src/anomaly/detector.js +22 -12
  7. package/dist/src/anomaly/detector.js.map +1 -1
  8. package/dist/src/audit/logger.d.ts +10 -0
  9. package/dist/src/audit/logger.d.ts.map +1 -1
  10. package/dist/src/audit/logger.js +52 -38
  11. package/dist/src/audit/logger.js.map +1 -1
  12. package/dist/src/auth/routes.d.ts.map +1 -1
  13. package/dist/src/auth/routes.js +35 -0
  14. package/dist/src/auth/routes.js.map +1 -1
  15. package/dist/src/budget/manager.d.ts +5 -0
  16. package/dist/src/budget/manager.d.ts.map +1 -1
  17. package/dist/src/budget/manager.js +32 -0
  18. package/dist/src/budget/manager.js.map +1 -1
  19. package/dist/src/budget/model-pricing.d.ts +20 -0
  20. package/dist/src/budget/model-pricing.d.ts.map +1 -0
  21. package/dist/src/budget/model-pricing.js +107 -0
  22. package/dist/src/budget/model-pricing.js.map +1 -0
  23. package/dist/src/budget/usage-extractor.d.ts +3 -1
  24. package/dist/src/budget/usage-extractor.d.ts.map +1 -1
  25. package/dist/src/budget/usage-extractor.js +47 -3
  26. package/dist/src/budget/usage-extractor.js.map +1 -1
  27. package/dist/src/config/defaults.d.ts.map +1 -1
  28. package/dist/src/config/defaults.js +65 -13
  29. package/dist/src/config/defaults.js.map +1 -1
  30. package/dist/src/dlp/tool-patterns.d.ts +7 -0
  31. package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
  32. package/dist/src/dlp/tool-patterns.js +34 -0
  33. package/dist/src/dlp/tool-patterns.js.map +1 -0
  34. package/dist/src/executor/filesystem-executor.d.ts +28 -0
  35. package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
  36. package/dist/src/executor/filesystem-executor.js +192 -0
  37. package/dist/src/executor/filesystem-executor.js.map +1 -0
  38. package/dist/src/executor/http-executor.d.ts.map +1 -1
  39. package/dist/src/executor/http-executor.js +22 -2
  40. package/dist/src/executor/http-executor.js.map +1 -1
  41. package/dist/src/executor/index.d.ts +4 -0
  42. package/dist/src/executor/index.d.ts.map +1 -1
  43. package/dist/src/executor/index.js +9 -1
  44. package/dist/src/executor/index.js.map +1 -1
  45. package/dist/src/executor/shell-executor.d.ts +22 -0
  46. package/dist/src/executor/shell-executor.d.ts.map +1 -0
  47. package/dist/src/executor/shell-executor.js +119 -0
  48. package/dist/src/executor/shell-executor.js.map +1 -0
  49. package/dist/src/executor/sql-executor.d.ts +29 -0
  50. package/dist/src/executor/sql-executor.d.ts.map +1 -0
  51. package/dist/src/executor/sql-executor.js +114 -0
  52. package/dist/src/executor/sql-executor.js.map +1 -0
  53. package/dist/src/executor/websocket-executor.d.ts +26 -0
  54. package/dist/src/executor/websocket-executor.d.ts.map +1 -0
  55. package/dist/src/executor/websocket-executor.js +205 -0
  56. package/dist/src/executor/websocket-executor.js.map +1 -0
  57. package/dist/src/interceptor/index.d.ts +2 -0
  58. package/dist/src/interceptor/index.d.ts.map +1 -0
  59. package/dist/src/interceptor/index.js +6 -0
  60. package/dist/src/interceptor/index.js.map +1 -0
  61. package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
  62. package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
  63. package/dist/src/interceptor/provider-interceptor.js +302 -0
  64. package/dist/src/interceptor/provider-interceptor.js.map +1 -0
  65. package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
  66. package/dist/src/mcp/auth-verifier.js +3 -2
  67. package/dist/src/mcp/auth-verifier.js.map +1 -1
  68. package/dist/src/mcp/bridge.d.ts +14 -10
  69. package/dist/src/mcp/bridge.d.ts.map +1 -1
  70. package/dist/src/mcp/bridge.js +51 -227
  71. package/dist/src/mcp/bridge.js.map +1 -1
  72. package/dist/src/mcp/http-transport.d.ts +2 -0
  73. package/dist/src/mcp/http-transport.d.ts.map +1 -1
  74. package/dist/src/mcp/http-transport.js +117 -66
  75. package/dist/src/mcp/http-transport.js.map +1 -1
  76. package/dist/src/mcp/internal-auth.d.ts +13 -0
  77. package/dist/src/mcp/internal-auth.d.ts.map +1 -0
  78. package/dist/src/mcp/internal-auth.js +12 -0
  79. package/dist/src/mcp/internal-auth.js.map +1 -0
  80. package/dist/src/mcp/tool-definitions.d.ts +41 -0
  81. package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
  82. package/dist/src/mcp/tool-definitions.js +491 -0
  83. package/dist/src/mcp/tool-definitions.js.map +1 -0
  84. package/dist/src/middleware/auth.js.map +1 -1
  85. package/dist/src/middleware/session.js.map +1 -1
  86. package/dist/src/middleware/validate.d.ts +8 -0
  87. package/dist/src/middleware/validate.d.ts.map +1 -1
  88. package/dist/src/middleware/validate.js +45 -0
  89. package/dist/src/middleware/validate.js.map +1 -1
  90. package/dist/src/policy/engine.d.ts +4 -0
  91. package/dist/src/policy/engine.d.ts.map +1 -1
  92. package/dist/src/policy/engine.js +117 -0
  93. package/dist/src/policy/engine.js.map +1 -1
  94. package/dist/src/saas/routes.d.ts.map +1 -1
  95. package/dist/src/saas/routes.js +355 -22
  96. package/dist/src/saas/routes.js.map +1 -1
  97. package/dist/src/server/app.d.ts.map +1 -1
  98. package/dist/src/server/app.js +24 -3
  99. package/dist/src/server/app.js.map +1 -1
  100. package/dist/src/server/gateway.d.ts.map +1 -1
  101. package/dist/src/server/gateway.js +17 -0
  102. package/dist/src/server/gateway.js.map +1 -1
  103. package/dist/src/server/index.d.ts.map +1 -1
  104. package/dist/src/server/index.js +18 -0
  105. package/dist/src/server/index.js.map +1 -1
  106. package/dist/src/storage/interfaces.d.ts +14 -3
  107. package/dist/src/storage/interfaces.d.ts.map +1 -1
  108. package/dist/src/storage/memory.d.ts +2 -0
  109. package/dist/src/storage/memory.d.ts.map +1 -1
  110. package/dist/src/storage/memory.js +6 -0
  111. package/dist/src/storage/memory.js.map +1 -1
  112. package/dist/src/storage/postgres.d.ts +5 -0
  113. package/dist/src/storage/postgres.d.ts.map +1 -1
  114. package/dist/src/storage/postgres.js +16 -0
  115. package/dist/src/storage/postgres.js.map +1 -1
  116. package/dist/src/storage/redis.d.ts +10 -0
  117. package/dist/src/storage/redis.d.ts.map +1 -1
  118. package/dist/src/storage/redis.js +65 -0
  119. package/dist/src/storage/redis.js.map +1 -1
  120. package/dist/src/types/budget.d.ts +4 -0
  121. package/dist/src/types/budget.d.ts.map +1 -1
  122. package/dist/src/types/config.d.ts +58 -0
  123. package/dist/src/types/config.d.ts.map +1 -1
  124. package/dist/src/types/events.d.ts +1 -0
  125. package/dist/src/types/events.d.ts.map +1 -1
  126. package/dist/src/types/policy.d.ts +11 -1
  127. package/dist/src/types/policy.d.ts.map +1 -1
  128. package/dist/src/types/tool-result.d.ts +11 -0
  129. package/dist/src/types/tool-result.d.ts.map +1 -1
  130. package/dist/tests/unit/app-routes.test.d.ts +2 -0
  131. package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
  132. package/dist/tests/unit/app-routes.test.js +715 -0
  133. package/dist/tests/unit/app-routes.test.js.map +1 -0
  134. package/dist/tests/unit/audit-logger.test.js +105 -0
  135. package/dist/tests/unit/audit-logger.test.js.map +1 -1
  136. package/dist/tests/unit/auth-providers.test.d.ts +2 -0
  137. package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
  138. package/dist/tests/unit/auth-providers.test.js +279 -0
  139. package/dist/tests/unit/auth-providers.test.js.map +1 -0
  140. package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
  141. package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
  142. package/dist/tests/unit/auth-routes-extended.test.js +993 -0
  143. package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
  144. package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
  145. package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
  146. package/dist/tests/unit/auth-verifier.test.js +505 -0
  147. package/dist/tests/unit/auth-verifier.test.js.map +1 -0
  148. package/dist/tests/unit/billing-routes.test.d.ts +2 -0
  149. package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
  150. package/dist/tests/unit/billing-routes.test.js +432 -0
  151. package/dist/tests/unit/billing-routes.test.js.map +1 -0
  152. package/dist/tests/unit/config-defaults.test.d.ts +2 -0
  153. package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
  154. package/dist/tests/unit/config-defaults.test.js +119 -0
  155. package/dist/tests/unit/config-defaults.test.js.map +1 -0
  156. package/dist/tests/unit/defaults.test.js +0 -10
  157. package/dist/tests/unit/defaults.test.js.map +1 -1
  158. package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
  159. package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
  160. package/dist/tests/unit/filesystem-executor.test.js +280 -0
  161. package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
  162. package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
  163. package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
  164. package/dist/tests/unit/gateway-branches.test.js +1039 -0
  165. package/dist/tests/unit/gateway-branches.test.js.map +1 -0
  166. package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
  167. package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
  168. package/dist/tests/unit/http-executor-branches.test.js +495 -0
  169. package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
  170. package/dist/tests/unit/logger.test.d.ts +2 -0
  171. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  172. package/dist/tests/unit/logger.test.js +97 -0
  173. package/dist/tests/unit/logger.test.js.map +1 -0
  174. package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
  175. package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
  176. package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
  177. package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
  178. package/dist/tests/unit/metrics.test.js +102 -0
  179. package/dist/tests/unit/metrics.test.js.map +1 -1
  180. package/dist/tests/unit/model-pricing.test.d.ts +2 -0
  181. package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
  182. package/dist/tests/unit/model-pricing.test.js +87 -0
  183. package/dist/tests/unit/model-pricing.test.js.map +1 -0
  184. package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
  185. package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
  186. package/dist/tests/unit/oauth-stores.test.js +260 -0
  187. package/dist/tests/unit/oauth-stores.test.js.map +1 -0
  188. package/dist/tests/unit/policy-engine.test.js +466 -0
  189. package/dist/tests/unit/policy-engine.test.js.map +1 -1
  190. package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
  191. package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
  192. package/dist/tests/unit/provider-interceptor.test.js +472 -0
  193. package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
  194. package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
  195. package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
  196. package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
  197. package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
  198. package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
  199. package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
  200. package/dist/tests/unit/saas-routes-crud.test.js +332 -0
  201. package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
  202. package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
  203. package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
  204. package/dist/tests/unit/saas-routes-data.test.js +405 -0
  205. package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
  206. package/dist/tests/unit/saas-routes.test.js +3 -3
  207. package/dist/tests/unit/saas-routes.test.js.map +1 -1
  208. package/dist/tests/unit/shell-executor.test.d.ts +2 -0
  209. package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
  210. package/dist/tests/unit/shell-executor.test.js +145 -0
  211. package/dist/tests/unit/shell-executor.test.js.map +1 -0
  212. package/dist/tests/unit/sql-executor.test.d.ts +2 -0
  213. package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
  214. package/dist/tests/unit/sql-executor.test.js +177 -0
  215. package/dist/tests/unit/sql-executor.test.js.map +1 -0
  216. package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
  217. package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
  218. package/dist/tests/unit/stream-proxy.test.js +147 -0
  219. package/dist/tests/unit/stream-proxy.test.js.map +1 -0
  220. package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
  221. package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
  222. package/dist/tests/unit/tool-definitions.test.js +184 -0
  223. package/dist/tests/unit/tool-definitions.test.js.map +1 -0
  224. package/dist/tests/unit/usage-extractor.test.js +140 -0
  225. package/dist/tests/unit/usage-extractor.test.js.map +1 -1
  226. package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
  227. package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
  228. package/dist/tests/unit/webhook-handler.test.js +453 -0
  229. package/dist/tests/unit/webhook-handler.test.js.map +1 -0
  230. package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
  231. package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
  232. package/dist/tests/unit/webhook-routes.test.js +69 -0
  233. package/dist/tests/unit/webhook-routes.test.js.map +1 -0
  234. package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
  235. package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
  236. package/dist/tests/unit/websocket-executor.test.js +121 -0
  237. package/dist/tests/unit/websocket-executor.test.js.map +1 -0
  238. package/package.json +8 -2
  239. package/policy-packs/demo_fail.yaml +41 -0
  240. package/policy-packs/full_tools.yaml +136 -0
  241. package/src/admin/index.ts +1 -0
  242. package/src/admin/routes.ts +509 -0
  243. package/src/admin/templates.ts +572 -0
  244. package/src/anomaly/detector.ts +730 -0
  245. package/src/anomaly/index.ts +1 -0
  246. package/src/approval/manager.ts +569 -0
  247. package/src/approval/webhook.ts +133 -0
  248. package/src/audit/logger.ts +490 -0
  249. package/src/auth/index.ts +5 -0
  250. package/src/auth/password.ts +21 -0
  251. package/src/auth/pkce.ts +22 -0
  252. package/src/auth/providers.ts +208 -0
  253. package/src/auth/routes.ts +561 -0
  254. package/src/auth/session.ts +84 -0
  255. package/src/billing/index.ts +6 -0
  256. package/src/billing/plan-enforcer.ts +135 -0
  257. package/src/billing/routes.ts +229 -0
  258. package/src/billing/stripe-client.ts +58 -0
  259. package/src/billing/webhook-handler.ts +182 -0
  260. package/src/billing/webhook-routes.ts +28 -0
  261. package/src/budget/manager.ts +679 -0
  262. package/src/budget/model-pricing.ts +119 -0
  263. package/src/budget/usage-extractor.ts +214 -0
  264. package/src/cli.ts +91 -0
  265. package/src/config/defaults.ts +261 -0
  266. package/src/config/validate.ts +88 -0
  267. package/src/dlp/composite-scanner.ts +213 -0
  268. package/src/dlp/index.ts +9 -0
  269. package/src/dlp/interfaces.ts +34 -0
  270. package/src/dlp/patterns.ts +30 -0
  271. package/src/dlp/prompt-injection-backend.ts +181 -0
  272. package/src/dlp/prompt-injection-patterns.ts +302 -0
  273. package/src/dlp/regex-backend.ts +181 -0
  274. package/src/dlp/scanner.ts +502 -0
  275. package/src/dlp/text-normalizer.ts +225 -0
  276. package/src/dlp/tool-patterns.ts +35 -0
  277. package/src/dlp/trufflehog-backend.ts +190 -0
  278. package/src/executor/filesystem-executor.ts +196 -0
  279. package/src/executor/http-executor.ts +349 -0
  280. package/src/executor/index.ts +9 -0
  281. package/src/executor/interfaces.ts +11 -0
  282. package/src/executor/noop-executor.ts +23 -0
  283. package/src/executor/registry.ts +64 -0
  284. package/src/executor/shell-executor.ts +148 -0
  285. package/src/executor/slack-executor.ts +176 -0
  286. package/src/executor/sql-executor.ts +146 -0
  287. package/src/executor/websocket-executor.ts +211 -0
  288. package/src/index.ts +24 -0
  289. package/src/interceptor/index.ts +1 -0
  290. package/src/interceptor/provider-interceptor.ts +315 -0
  291. package/src/mcp/auth-verifier.ts +152 -0
  292. package/src/mcp/bridge.ts +703 -0
  293. package/src/mcp/http-transport.ts +698 -0
  294. package/src/mcp/index.ts +9 -0
  295. package/src/mcp/internal-auth.ts +14 -0
  296. package/src/mcp/oauth-pages.ts +139 -0
  297. package/src/mcp/oauth-postgres-stores.ts +278 -0
  298. package/src/mcp/oauth-provider.ts +536 -0
  299. package/src/mcp/oauth-stores.ts +202 -0
  300. package/src/mcp/server.ts +55 -0
  301. package/src/mcp/tool-definitions.ts +562 -0
  302. package/src/metrics/collector.ts +357 -0
  303. package/src/metrics/index.ts +1 -0
  304. package/src/middleware/auth.ts +814 -0
  305. package/src/middleware/session.ts +85 -0
  306. package/src/middleware/validate.ts +130 -0
  307. package/src/policy/engine.ts +815 -0
  308. package/src/policy/index.ts +2 -0
  309. package/src/policy/opa-engine.ts +829 -0
  310. package/src/proxy/forward-proxy.ts +649 -0
  311. package/src/proxy/index.ts +1 -0
  312. package/src/ratelimit/limiter.ts +196 -0
  313. package/src/replay/engine.ts +142 -0
  314. package/src/replay/index.ts +1 -0
  315. package/src/saas/index.ts +1 -0
  316. package/src/saas/routes.ts +2178 -0
  317. package/src/server/app.ts +985 -0
  318. package/src/server/errors.ts +49 -0
  319. package/src/server/gateway.ts +1130 -0
  320. package/src/server/index.ts +307 -0
  321. package/src/server/logger.ts +255 -0
  322. package/src/server/stream-proxy.ts +202 -0
  323. package/src/storage/file-persistence.ts +315 -0
  324. package/src/storage/index.ts +4 -0
  325. package/src/storage/interfaces.ts +287 -0
  326. package/src/storage/memory.ts +686 -0
  327. package/src/storage/postgres.ts +1831 -0
  328. package/src/storage/redis.ts +835 -0
  329. package/src/tracing/index.ts +1 -0
  330. package/src/tracing/provider.ts +100 -0
  331. package/src/trust/calculator.ts +141 -0
  332. package/src/trust/index.ts +7 -0
  333. package/src/types/budget.ts +36 -0
  334. package/src/types/config.ts +278 -0
  335. package/src/types/events.ts +41 -0
  336. package/src/types/express.d.ts +14 -0
  337. package/src/types/index.ts +7 -0
  338. package/src/types/policy.ts +83 -0
  339. package/src/types/stripe-config.ts +11 -0
  340. package/src/types/subscription.ts +59 -0
  341. package/src/types/tool-call.ts +47 -0
  342. package/src/types/tool-result.ts +82 -0
  343. package/src/types/user.ts +125 -0
  344. package/tsconfig.json +24 -0
@@ -0,0 +1,196 @@
1
+ import { RateLimitConfig } from '../types/config';
2
+ import { RateLimitStore } from '../storage/interfaces';
3
+ import { ToolCall } from '../types/tool-call';
4
+
5
+ interface WindowEntry {
6
+ timestamps: number[];
7
+ }
8
+
9
+ export interface RateLimitResult {
10
+ allowed: boolean;
11
+ current: number;
12
+ limit: number;
13
+ remaining: number;
14
+ reset_at: string;
15
+ blocked_by?: 'actor' | 'workspace';
16
+ }
17
+
18
+ /**
19
+ * Sliding-window rate limiter.
20
+ * Tracks request counts per actor and per workspace within a configurable window.
21
+ * Optionally delegates to an external RateLimitStore (e.g. Redis) for cross-process state.
22
+ */
23
+ export class RateLimiter {
24
+ private config: RateLimitConfig;
25
+ private windows: Map<string, WindowEntry> = new Map();
26
+ private store?: RateLimitStore;
27
+ private cleanupTimer?: ReturnType<typeof setInterval>;
28
+
29
+ constructor(config: RateLimitConfig, store?: RateLimitStore) {
30
+ this.config = config;
31
+ this.store = store;
32
+
33
+ // Periodic cleanup of stale entries (every 60 seconds)
34
+ if (config.enabled) {
35
+ this.cleanupTimer = setInterval(() => {
36
+ this.cleanupStaleEntries();
37
+ }, 60_000);
38
+ if (this.cleanupTimer && typeof this.cleanupTimer === 'object' && 'unref' in this.cleanupTimer) {
39
+ this.cleanupTimer.unref();
40
+ }
41
+ }
42
+ }
43
+
44
+ /** Remove entries with no timestamps in the current window */
45
+ private cleanupStaleEntries(): void {
46
+ const cutoff = Date.now() - this.config.window_ms;
47
+ for (const [key, entry] of this.windows) {
48
+ const valid = entry.timestamps.filter(t => t > cutoff);
49
+ if (valid.length === 0) {
50
+ this.windows.delete(key);
51
+ } else {
52
+ entry.timestamps = valid;
53
+ }
54
+ }
55
+ }
56
+
57
+ /** Stop the periodic cleanup timer (for tests and shutdown) */
58
+ destroy(): void {
59
+ if (this.cleanupTimer) {
60
+ clearInterval(this.cleanupTimer);
61
+ this.cleanupTimer = undefined;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Check if a tool call is within rate limits.
67
+ * Records the hit and returns the result.
68
+ * Optional overrides allow per-workspace rate limit configuration.
69
+ */
70
+ check(toolCall: ToolCall, overrides?: { actor_max_per_window?: number; workspace_max_per_window?: number; window_ms?: number }): RateLimitResult {
71
+ if (!this.config.enabled) {
72
+ return { allowed: true, current: 0, limit: 0, remaining: 0, reset_at: '' };
73
+ }
74
+
75
+ const now = Date.now();
76
+ const windowMs = overrides?.window_ms ?? this.config.window_ms;
77
+ const actorMax = overrides?.actor_max_per_window ?? this.config.actor_max_per_window;
78
+ const wsMax = overrides?.workspace_max_per_window ?? this.config.workspace_max_per_window;
79
+
80
+ // Check actor rate limit
81
+ const actorKey = `actor:${toolCall.actor.id}`;
82
+ const actorResult = this.hit(actorKey, now, windowMs, actorMax);
83
+ if (!actorResult.allowed) {
84
+ return {
85
+ allowed: false,
86
+ current: actorResult.current,
87
+ limit: actorResult.limit,
88
+ remaining: 0,
89
+ reset_at: new Date(actorResult.resetAt).toISOString(),
90
+ blocked_by: 'actor',
91
+ };
92
+ }
93
+
94
+ // Check workspace rate limit
95
+ const wsKey = `workspace:${toolCall.workspace_id}`;
96
+ const wsResult = this.hit(wsKey, now, windowMs, wsMax);
97
+ if (!wsResult.allowed) {
98
+ // Roll back the actor hit since we're rejecting
99
+ this.rollback(actorKey, now);
100
+ return {
101
+ allowed: false,
102
+ current: wsResult.current,
103
+ limit: wsResult.limit,
104
+ remaining: 0,
105
+ reset_at: new Date(wsResult.resetAt).toISOString(),
106
+ blocked_by: 'workspace',
107
+ };
108
+ }
109
+
110
+ const remaining = Math.min(
111
+ actorMax - actorResult.current,
112
+ wsMax - wsResult.current
113
+ );
114
+
115
+ return {
116
+ allowed: true,
117
+ current: actorResult.current,
118
+ limit: actorMax,
119
+ remaining,
120
+ reset_at: new Date(actorResult.resetAt).toISOString(),
121
+ };
122
+ }
123
+
124
+ private hit(key: string, now: number, windowMs: number, maxRequests: number) {
125
+ // Delegate to external store if available
126
+ if (this.store) {
127
+ return this.store.hit(key, windowMs, maxRequests);
128
+ }
129
+
130
+ const cutoff = now - windowMs;
131
+ let entry = this.windows.get(key);
132
+ if (!entry) {
133
+ entry = { timestamps: [] };
134
+ this.windows.set(key, entry);
135
+ }
136
+
137
+ // Prune expired timestamps
138
+ entry.timestamps = entry.timestamps.filter(t => t > cutoff);
139
+
140
+ // If all timestamps expired, remove the entry entirely to free memory
141
+ if (entry.timestamps.length === 0) {
142
+ // But we still need to record this hit, so keep the entry
143
+ }
144
+
145
+ // Record this hit
146
+ entry.timestamps.push(now);
147
+
148
+ const current = entry.timestamps.length;
149
+ const allowed = current <= maxRequests;
150
+ const resetAt = entry.timestamps.length > 0 ? entry.timestamps[0] + windowMs : now + windowMs;
151
+
152
+ // Periodic cleanup of stale windows (every 1000 entries)
153
+ if (this.windows.size > 1000) {
154
+ for (const [k, w] of this.windows) {
155
+ const validTimestamps = w.timestamps.filter(t => t > cutoff);
156
+ if (validTimestamps.length === 0) {
157
+ this.windows.delete(k);
158
+ }
159
+ }
160
+ }
161
+
162
+ return { allowed, current, limit: maxRequests, resetAt };
163
+ }
164
+
165
+ private rollback(key: string, timestamp: number) {
166
+ if (this.store) {
167
+ // Delegate rollback to the store if available
168
+ if ('rollback' in this.store && typeof (this.store as any).rollback === 'function') {
169
+ (this.store as any).rollback(key, this.config.window_ms);
170
+ }
171
+ return;
172
+ }
173
+ const entry = this.windows.get(key);
174
+ if (entry) {
175
+ const idx = entry.timestamps.lastIndexOf(timestamp);
176
+ if (idx !== -1) entry.timestamps.splice(idx, 1);
177
+ }
178
+ }
179
+
180
+ /** Wait for all pending store writes to complete. */
181
+ async flush(): Promise<void> {
182
+ if (this.store && 'flush' in this.store && typeof (this.store as any).flush === 'function') {
183
+ await (this.store as any).flush();
184
+ }
185
+ }
186
+
187
+ reset(): void {
188
+ this.windows.clear();
189
+ this.store?.reset();
190
+ }
191
+
192
+ /** Get the number of tracked window entries (for diagnostics) */
193
+ getWindowCount(): number {
194
+ return this.windows.size;
195
+ }
196
+ }
@@ -0,0 +1,142 @@
1
+ import { AuditLogger } from '../audit/logger';
2
+ import { PolicyEngine } from '../policy/engine';
3
+ import { ToolCall } from '../types/tool-call';
4
+ import { AuditEvent } from '../types/events';
5
+
6
+ export interface ReplayComparison {
7
+ tool_call_id: string;
8
+ tool_name: string;
9
+ original_decision: string;
10
+ original_rule_id: string;
11
+ replay_decision: string;
12
+ replay_rule_id: string;
13
+ changed: boolean;
14
+ replay_reasons: string[];
15
+ }
16
+
17
+ export interface ReplayResult {
18
+ task_id: string;
19
+ policy_pack_path: string;
20
+ total_calls: number;
21
+ changed_count: number;
22
+ comparisons: ReplayComparison[];
23
+ }
24
+
25
+ /** Maximum number of tool calls to replay per session */
26
+ const MAX_REPLAY_CALLS = 100;
27
+
28
+ export class SessionReplayEngine {
29
+ private auditLogger: AuditLogger;
30
+
31
+ constructor(auditLogger: AuditLogger) {
32
+ this.auditLogger = auditLogger;
33
+ }
34
+
35
+ /**
36
+ * Replay a task's tool calls against an alternative policy pack.
37
+ *
38
+ * 1. Gets all events for the task_id from the audit logger.
39
+ * 2. Finds TOOL_CALL_RECEIVED events (which contain args_snapshot in metadata).
40
+ * 3. Reconstructs ToolCall objects from event data + args_snapshot.
41
+ * 4. Finds the original POLICY_DECIDED events for each tool_call_id.
42
+ * 5. Creates a new PolicyEngine with the alternative policy pack path.
43
+ * 6. Re-evaluates each ToolCall against the new policy.
44
+ * 7. Returns a comparison report.
45
+ */
46
+ replay(taskId: string, altPolicyPackPath: string): ReplayResult {
47
+ const events = this.auditLogger.getTaskTrace(taskId);
48
+
49
+ if (events.length === 0) {
50
+ return {
51
+ task_id: taskId,
52
+ policy_pack_path: altPolicyPackPath,
53
+ total_calls: 0,
54
+ changed_count: 0,
55
+ comparisons: [],
56
+ };
57
+ }
58
+
59
+ // Collect TOOL_CALL_RECEIVED and POLICY_DECIDED events
60
+ const receivedEvents = events.filter(e => e.event_type === 'TOOL_CALL_RECEIVED');
61
+ const policyEvents = events.filter(e => e.event_type === 'POLICY_DECIDED');
62
+
63
+ // Build a map of tool_call_id -> POLICY_DECIDED event for quick lookup
64
+ const policyMap = new Map<string, AuditEvent>();
65
+ for (const pe of policyEvents) {
66
+ policyMap.set(pe.tool_call_id, pe);
67
+ }
68
+
69
+ // Cap at MAX_REPLAY_CALLS
70
+ const capped = receivedEvents.slice(0, MAX_REPLAY_CALLS);
71
+
72
+ // Create the alternative policy engine
73
+ const altEngine = new PolicyEngine(altPolicyPackPath);
74
+
75
+ const comparisons: ReplayComparison[] = [];
76
+
77
+ for (const event of capped) {
78
+ // Reconstruct ToolCall from event metadata + args_snapshot
79
+ const toolCall = this.reconstructToolCall(event);
80
+
81
+ // Get original policy decision
82
+ const originalPolicy = policyMap.get(event.tool_call_id);
83
+ const originalDecision = originalPolicy?.metadata?.decision as string || 'unknown';
84
+ const originalRuleId = originalPolicy?.metadata?.rule_id as string || 'unknown';
85
+
86
+ // Re-evaluate against alternative policy
87
+ const replayResult = altEngine.evaluate(toolCall);
88
+
89
+ const changed = originalDecision !== replayResult.decision ||
90
+ originalRuleId !== replayResult.rule_id;
91
+
92
+ comparisons.push({
93
+ tool_call_id: event.tool_call_id,
94
+ tool_name: event.tool_name,
95
+ original_decision: originalDecision,
96
+ original_rule_id: originalRuleId,
97
+ replay_decision: replayResult.decision,
98
+ replay_rule_id: replayResult.rule_id,
99
+ changed,
100
+ replay_reasons: replayResult.reasons,
101
+ });
102
+ }
103
+
104
+ return {
105
+ task_id: taskId,
106
+ policy_pack_path: altPolicyPackPath,
107
+ total_calls: comparisons.length,
108
+ changed_count: comparisons.filter(c => c.changed).length,
109
+ comparisons,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Reconstruct a ToolCall from an audit event's stored data.
115
+ * Uses the args_snapshot from metadata for the args field.
116
+ */
117
+ private reconstructToolCall(event: AuditEvent): ToolCall {
118
+ const metadata = event.metadata;
119
+ const argsSnapshot = (metadata.args_snapshot as Record<string, unknown>) || {};
120
+
121
+ return {
122
+ tool_call_id: event.tool_call_id,
123
+ task_id: event.task_id,
124
+ workspace_id: event.workspace_id,
125
+ actor: {
126
+ type: 'agent',
127
+ id: event.actor_id,
128
+ },
129
+ source: {
130
+ platform: (metadata.platform as string) || 'unknown',
131
+ },
132
+ tool: {
133
+ name: event.tool_name,
134
+ capability: (metadata.capability as 'read' | 'write' | 'delete' | 'admin') || 'read',
135
+ },
136
+ args: argsSnapshot,
137
+ context: {
138
+ labels: (metadata.labels as string[]) || [],
139
+ },
140
+ };
141
+ }
142
+ }
@@ -0,0 +1 @@
1
+ export { SessionReplayEngine, ReplayComparison, ReplayResult } from './engine';
@@ -0,0 +1 @@
1
+ export * from './routes';