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.
- package/LICENSE +21 -0
- package/README.md +716 -0
- package/dist/sdk/typescript/src/client.d.ts +71 -0
- package/dist/sdk/typescript/src/client.d.ts.map +1 -0
- package/dist/sdk/typescript/src/client.js +176 -0
- package/dist/sdk/typescript/src/client.js.map +1 -0
- package/dist/sdk/typescript/src/errors.d.ts +50 -0
- package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
- package/dist/sdk/typescript/src/errors.js +103 -0
- package/dist/sdk/typescript/src/errors.js.map +1 -0
- package/dist/sdk/typescript/src/index.d.ts +4 -0
- package/dist/sdk/typescript/src/index.d.ts.map +1 -0
- package/dist/sdk/typescript/src/index.js +15 -0
- package/dist/sdk/typescript/src/index.js.map +1 -0
- package/dist/sdk/typescript/src/types.d.ts +101 -0
- package/dist/sdk/typescript/src/types.d.ts.map +1 -0
- package/dist/sdk/typescript/src/types.js +6 -0
- package/dist/sdk/typescript/src/types.js.map +1 -0
- package/dist/src/admin/index.d.ts +2 -0
- package/dist/src/admin/index.d.ts.map +1 -0
- package/dist/src/admin/index.js +6 -0
- package/dist/src/admin/index.js.map +1 -0
- package/dist/src/admin/routes.d.ts +5 -0
- package/dist/src/admin/routes.d.ts.map +1 -0
- package/dist/src/admin/routes.js +471 -0
- package/dist/src/admin/routes.js.map +1 -0
- package/dist/src/admin/templates.d.ts +51 -0
- package/dist/src/admin/templates.d.ts.map +1 -0
- package/dist/src/admin/templates.js +500 -0
- package/dist/src/admin/templates.js.map +1 -0
- package/dist/src/anomaly/detector.d.ts +141 -0
- package/dist/src/anomaly/detector.d.ts.map +1 -0
- package/dist/src/anomaly/detector.js +554 -0
- package/dist/src/anomaly/detector.js.map +1 -0
- package/dist/src/anomaly/index.d.ts +2 -0
- package/dist/src/anomaly/index.d.ts.map +1 -0
- package/dist/src/anomaly/index.js +7 -0
- package/dist/src/anomaly/index.js.map +1 -0
- package/dist/src/approval/manager.d.ts +147 -0
- package/dist/src/approval/manager.d.ts.map +1 -0
- package/dist/src/approval/manager.js +511 -0
- package/dist/src/approval/manager.js.map +1 -0
- package/dist/src/approval/webhook.d.ts +36 -0
- package/dist/src/approval/webhook.d.ts.map +1 -0
- package/dist/src/approval/webhook.js +135 -0
- package/dist/src/approval/webhook.js.map +1 -0
- package/dist/src/audit/logger.d.ts +70 -0
- package/dist/src/audit/logger.d.ts.map +1 -0
- package/dist/src/audit/logger.js +440 -0
- package/dist/src/audit/logger.js.map +1 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +22 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/password.d.ts +3 -0
- package/dist/src/auth/password.d.ts.map +1 -0
- package/dist/src/auth/password.js +25 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/auth/pkce.d.ts +13 -0
- package/dist/src/auth/pkce.d.ts.map +1 -0
- package/dist/src/auth/pkce.js +58 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/auth/providers.d.ts +28 -0
- package/dist/src/auth/providers.d.ts.map +1 -0
- package/dist/src/auth/providers.js +198 -0
- package/dist/src/auth/providers.js.map +1 -0
- package/dist/src/auth/routes.d.ts +14 -0
- package/dist/src/auth/routes.d.ts.map +1 -0
- package/dist/src/auth/routes.js +431 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/session.d.ts +24 -0
- package/dist/src/auth/session.d.ts.map +1 -0
- package/dist/src/auth/session.js +105 -0
- package/dist/src/auth/session.js.map +1 -0
- package/dist/src/billing/index.d.ts +7 -0
- package/dist/src/billing/index.d.ts.map +1 -0
- package/dist/src/billing/index.js +14 -0
- package/dist/src/billing/index.js.map +1 -0
- package/dist/src/billing/plan-enforcer.d.ts +44 -0
- package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
- package/dist/src/billing/plan-enforcer.js +110 -0
- package/dist/src/billing/plan-enforcer.js.map +1 -0
- package/dist/src/billing/routes.d.ts +15 -0
- package/dist/src/billing/routes.d.ts.map +1 -0
- package/dist/src/billing/routes.js +193 -0
- package/dist/src/billing/routes.js.map +1 -0
- package/dist/src/billing/stripe-client.d.ts +14 -0
- package/dist/src/billing/stripe-client.d.ts.map +1 -0
- package/dist/src/billing/stripe-client.js +51 -0
- package/dist/src/billing/stripe-client.js.map +1 -0
- package/dist/src/billing/webhook-handler.d.ts +19 -0
- package/dist/src/billing/webhook-handler.d.ts.map +1 -0
- package/dist/src/billing/webhook-handler.js +169 -0
- package/dist/src/billing/webhook-handler.js.map +1 -0
- package/dist/src/billing/webhook-routes.d.ts +5 -0
- package/dist/src/billing/webhook-routes.d.ts.map +1 -0
- package/dist/src/billing/webhook-routes.js +30 -0
- package/dist/src/billing/webhook-routes.js.map +1 -0
- package/dist/src/budget/manager.d.ts +95 -0
- package/dist/src/budget/manager.d.ts.map +1 -0
- package/dist/src/budget/manager.js +547 -0
- package/dist/src/budget/manager.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +38 -0
- package/dist/src/budget/usage-extractor.d.ts.map +1 -0
- package/dist/src/budget/usage-extractor.js +165 -0
- package/dist/src/budget/usage-extractor.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +115 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config/defaults.d.ts +3 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +243 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/config/validate.d.ts +15 -0
- package/dist/src/config/validate.d.ts.map +1 -0
- package/dist/src/config/validate.js +105 -0
- package/dist/src/config/validate.js.map +1 -0
- package/dist/src/dlp/composite-scanner.d.ts +47 -0
- package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
- package/dist/src/dlp/composite-scanner.js +186 -0
- package/dist/src/dlp/composite-scanner.js.map +1 -0
- package/dist/src/dlp/index.d.ts +10 -0
- package/dist/src/dlp/index.d.ts.map +1 -0
- package/dist/src/dlp/index.js +26 -0
- package/dist/src/dlp/index.js.map +1 -0
- package/dist/src/dlp/interfaces.d.ts +33 -0
- package/dist/src/dlp/interfaces.d.ts.map +1 -0
- package/dist/src/dlp/interfaces.js +3 -0
- package/dist/src/dlp/interfaces.js.map +1 -0
- package/dist/src/dlp/patterns.d.ts +9 -0
- package/dist/src/dlp/patterns.d.ts.map +1 -0
- package/dist/src/dlp/patterns.js +25 -0
- package/dist/src/dlp/patterns.js.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.js +148 -0
- package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.js +290 -0
- package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
- package/dist/src/dlp/regex-backend.d.ts +32 -0
- package/dist/src/dlp/regex-backend.d.ts.map +1 -0
- package/dist/src/dlp/regex-backend.js +153 -0
- package/dist/src/dlp/regex-backend.js.map +1 -0
- package/dist/src/dlp/scanner.d.ts +122 -0
- package/dist/src/dlp/scanner.d.ts.map +1 -0
- package/dist/src/dlp/scanner.js +444 -0
- package/dist/src/dlp/scanner.js.map +1 -0
- package/dist/src/dlp/text-normalizer.d.ts +41 -0
- package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
- package/dist/src/dlp/text-normalizer.js +203 -0
- package/dist/src/dlp/text-normalizer.js.map +1 -0
- package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
- package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
- package/dist/src/dlp/trufflehog-backend.js +151 -0
- package/dist/src/dlp/trufflehog-backend.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts +25 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -0
- package/dist/src/executor/http-executor.js +333 -0
- package/dist/src/executor/http-executor.js.map +1 -0
- package/dist/src/executor/index.d.ts +6 -0
- package/dist/src/executor/index.d.ts.map +1 -0
- package/dist/src/executor/index.js +12 -0
- package/dist/src/executor/index.js.map +1 -0
- package/dist/src/executor/interfaces.d.ts +11 -0
- package/dist/src/executor/interfaces.d.ts.map +1 -0
- package/dist/src/executor/interfaces.js +3 -0
- package/dist/src/executor/interfaces.js.map +1 -0
- package/dist/src/executor/noop-executor.d.ts +13 -0
- package/dist/src/executor/noop-executor.d.ts.map +1 -0
- package/dist/src/executor/noop-executor.js +21 -0
- package/dist/src/executor/noop-executor.js.map +1 -0
- package/dist/src/executor/registry.d.ts +30 -0
- package/dist/src/executor/registry.d.ts.map +1 -0
- package/dist/src/executor/registry.js +62 -0
- package/dist/src/executor/registry.js.map +1 -0
- package/dist/src/executor/slack-executor.d.ts +24 -0
- package/dist/src/executor/slack-executor.d.ts.map +1 -0
- package/dist/src/executor/slack-executor.js +147 -0
- package/dist/src/executor/slack-executor.js.map +1 -0
- package/dist/src/index.d.ts +25 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +74 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts +23 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
- package/dist/src/mcp/auth-verifier.js +162 -0
- package/dist/src/mcp/auth-verifier.js.map +1 -0
- package/dist/src/mcp/bridge.d.ts +132 -0
- package/dist/src/mcp/bridge.d.ts.map +1 -0
- package/dist/src/mcp/bridge.js +734 -0
- package/dist/src/mcp/bridge.js.map +1 -0
- package/dist/src/mcp/http-transport.d.ts +32 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -0
- package/dist/src/mcp/http-transport.js +538 -0
- package/dist/src/mcp/http-transport.js.map +1 -0
- package/dist/src/mcp/index.d.ts +10 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +17 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/oauth-pages.d.ts +23 -0
- package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
- package/dist/src/mcp/oauth-pages.js +121 -0
- package/dist/src/mcp/oauth-pages.js.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.js +226 -0
- package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +95 -0
- package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
- package/dist/src/mcp/oauth-provider.js +360 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-stores.d.ts +62 -0
- package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-stores.js +154 -0
- package/dist/src/mcp/oauth-stores.js.map +1 -0
- package/dist/src/mcp/server.d.ts +18 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +51 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/metrics/collector.d.ts +106 -0
- package/dist/src/metrics/collector.d.ts.map +1 -0
- package/dist/src/metrics/collector.js +311 -0
- package/dist/src/metrics/collector.js.map +1 -0
- package/dist/src/metrics/index.d.ts +2 -0
- package/dist/src/metrics/index.d.ts.map +1 -0
- package/dist/src/metrics/index.js +6 -0
- package/dist/src/metrics/index.js.map +1 -0
- package/dist/src/middleware/auth.d.ts +77 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +720 -0
- package/dist/src/middleware/auth.js.map +1 -0
- package/dist/src/middleware/session.d.ts +18 -0
- package/dist/src/middleware/session.d.ts.map +1 -0
- package/dist/src/middleware/session.js +67 -0
- package/dist/src/middleware/session.js.map +1 -0
- package/dist/src/middleware/validate.d.ts +3 -0
- package/dist/src/middleware/validate.d.ts.map +1 -0
- package/dist/src/middleware/validate.js +85 -0
- package/dist/src/middleware/validate.js.map +1 -0
- package/dist/src/policy/engine.d.ts +107 -0
- package/dist/src/policy/engine.d.ts.map +1 -0
- package/dist/src/policy/engine.js +646 -0
- package/dist/src/policy/engine.js.map +1 -0
- package/dist/src/policy/index.d.ts +3 -0
- package/dist/src/policy/index.d.ts.map +1 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/opa-engine.d.ts +176 -0
- package/dist/src/policy/opa-engine.d.ts.map +1 -0
- package/dist/src/policy/opa-engine.js +790 -0
- package/dist/src/policy/opa-engine.js.map +1 -0
- package/dist/src/proxy/forward-proxy.d.ts +30 -0
- package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
- package/dist/src/proxy/forward-proxy.js +580 -0
- package/dist/src/proxy/forward-proxy.js.map +1 -0
- package/dist/src/proxy/index.d.ts +2 -0
- package/dist/src/proxy/index.d.ts.map +1 -0
- package/dist/src/proxy/index.js +8 -0
- package/dist/src/proxy/index.js.map +1 -0
- package/dist/src/ratelimit/limiter.d.ts +45 -0
- package/dist/src/ratelimit/limiter.d.ts.map +1 -0
- package/dist/src/ratelimit/limiter.js +158 -0
- package/dist/src/ratelimit/limiter.js.map +1 -0
- package/dist/src/replay/engine.d.ts +40 -0
- package/dist/src/replay/engine.d.ts.map +1 -0
- package/dist/src/replay/engine.js +106 -0
- package/dist/src/replay/engine.js.map +1 -0
- package/dist/src/replay/index.d.ts +2 -0
- package/dist/src/replay/index.d.ts.map +1 -0
- package/dist/src/replay/index.js +6 -0
- package/dist/src/replay/index.js.map +1 -0
- package/dist/src/saas/index.d.ts +2 -0
- package/dist/src/saas/index.d.ts.map +1 -0
- package/dist/src/saas/index.js +18 -0
- package/dist/src/saas/index.js.map +1 -0
- package/dist/src/saas/routes.d.ts +18 -0
- package/dist/src/saas/routes.d.ts.map +1 -0
- package/dist/src/saas/routes.js +1566 -0
- package/dist/src/saas/routes.js.map +1 -0
- package/dist/src/server/app.d.ts +44 -0
- package/dist/src/server/app.d.ts.map +1 -0
- package/dist/src/server/app.js +854 -0
- package/dist/src/server/app.js.map +1 -0
- package/dist/src/server/errors.d.ts +32 -0
- package/dist/src/server/errors.d.ts.map +1 -0
- package/dist/src/server/errors.js +39 -0
- package/dist/src/server/errors.js.map +1 -0
- package/dist/src/server/gateway.d.ts +165 -0
- package/dist/src/server/gateway.d.ts.map +1 -0
- package/dist/src/server/gateway.js +964 -0
- package/dist/src/server/gateway.js.map +1 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +295 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/logger.d.ts +33 -0
- package/dist/src/server/logger.d.ts.map +1 -0
- package/dist/src/server/logger.js +230 -0
- package/dist/src/server/logger.js.map +1 -0
- package/dist/src/server/stream-proxy.d.ts +32 -0
- package/dist/src/server/stream-proxy.d.ts.map +1 -0
- package/dist/src/server/stream-proxy.js +184 -0
- package/dist/src/server/stream-proxy.js.map +1 -0
- package/dist/src/storage/file-persistence.d.ts +48 -0
- package/dist/src/storage/file-persistence.d.ts.map +1 -0
- package/dist/src/storage/file-persistence.js +280 -0
- package/dist/src/storage/file-persistence.js.map +1 -0
- package/dist/src/storage/index.d.ts +5 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +21 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/interfaces.d.ts +237 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -0
- package/dist/src/storage/interfaces.js +3 -0
- package/dist/src/storage/interfaces.js.map +1 -0
- package/dist/src/storage/memory.d.ts +162 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +603 -0
- package/dist/src/storage/memory.js.map +1 -0
- package/dist/src/storage/postgres.d.ts +267 -0
- package/dist/src/storage/postgres.d.ts.map +1 -0
- package/dist/src/storage/postgres.js +1555 -0
- package/dist/src/storage/postgres.js.map +1 -0
- package/dist/src/storage/redis.d.ts +202 -0
- package/dist/src/storage/redis.d.ts.map +1 -0
- package/dist/src/storage/redis.js +629 -0
- package/dist/src/storage/redis.js.map +1 -0
- package/dist/src/tracing/index.d.ts +2 -0
- package/dist/src/tracing/index.d.ts.map +1 -0
- package/dist/src/tracing/index.js +6 -0
- package/dist/src/tracing/index.js.map +1 -0
- package/dist/src/tracing/provider.d.ts +43 -0
- package/dist/src/tracing/provider.d.ts.map +1 -0
- package/dist/src/tracing/provider.js +74 -0
- package/dist/src/tracing/provider.js.map +1 -0
- package/dist/src/trust/calculator.d.ts +54 -0
- package/dist/src/trust/calculator.d.ts.map +1 -0
- package/dist/src/trust/calculator.js +102 -0
- package/dist/src/trust/calculator.js.map +1 -0
- package/dist/src/trust/index.d.ts +2 -0
- package/dist/src/trust/index.d.ts.map +1 -0
- package/dist/src/trust/index.js +7 -0
- package/dist/src/trust/index.js.map +1 -0
- package/dist/src/types/budget.d.ts +30 -0
- package/dist/src/types/budget.d.ts.map +1 -0
- package/dist/src/types/budget.js +3 -0
- package/dist/src/types/budget.js.map +1 -0
- package/dist/src/types/config.d.ts +176 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +3 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/events.d.ts +24 -0
- package/dist/src/types/events.d.ts.map +1 -0
- package/dist/src/types/events.js +3 -0
- package/dist/src/types/events.js.map +1 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +24 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/policy.d.ts +60 -0
- package/dist/src/types/policy.d.ts.map +1 -0
- package/dist/src/types/policy.js +3 -0
- package/dist/src/types/policy.js.map +1 -0
- package/dist/src/types/stripe-config.d.ts +12 -0
- package/dist/src/types/stripe-config.d.ts.map +1 -0
- package/dist/src/types/stripe-config.js +3 -0
- package/dist/src/types/stripe-config.js.map +1 -0
- package/dist/src/types/subscription.d.ts +24 -0
- package/dist/src/types/subscription.d.ts.map +1 -0
- package/dist/src/types/subscription.js +38 -0
- package/dist/src/types/subscription.js.map +1 -0
- package/dist/src/types/tool-call.d.ts +42 -0
- package/dist/src/types/tool-call.d.ts.map +1 -0
- package/dist/src/types/tool-call.js +3 -0
- package/dist/src/types/tool-call.js.map +1 -0
- package/dist/src/types/tool-result.d.ts +58 -0
- package/dist/src/types/tool-result.d.ts.map +1 -0
- package/dist/src/types/tool-result.js +3 -0
- package/dist/src/types/tool-result.js.map +1 -0
- package/dist/src/types/user.d.ts +101 -0
- package/dist/src/types/user.d.ts.map +1 -0
- package/dist/src/types/user.js +6 -0
- package/dist/src/types/user.js.map +1 -0
- package/dist/tests/integration/api.test.d.ts +2 -0
- package/dist/tests/integration/api.test.d.ts.map +1 -0
- package/dist/tests/integration/api.test.js +1199 -0
- package/dist/tests/integration/api.test.js.map +1 -0
- package/dist/tests/integration/proxy.test.d.ts +2 -0
- package/dist/tests/integration/proxy.test.d.ts.map +1 -0
- package/dist/tests/integration/proxy.test.js +251 -0
- package/dist/tests/integration/proxy.test.js.map +1 -0
- package/dist/tests/integration/storage.test.d.ts +16 -0
- package/dist/tests/integration/storage.test.d.ts.map +1 -0
- package/dist/tests/integration/storage.test.js +826 -0
- package/dist/tests/integration/storage.test.js.map +1 -0
- package/dist/tests/unit/admin.test.d.ts +2 -0
- package/dist/tests/unit/admin.test.d.ts.map +1 -0
- package/dist/tests/unit/admin.test.js +698 -0
- package/dist/tests/unit/admin.test.js.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.js +903 -0
- package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
- package/dist/tests/unit/approval-manager.test.d.ts +2 -0
- package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-manager.test.js +528 -0
- package/dist/tests/unit/approval-manager.test.js.map +1 -0
- package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
- package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-webhook.test.js +355 -0
- package/dist/tests/unit/approval-webhook.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.d.ts +2 -0
- package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +635 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -0
- package/dist/tests/unit/auth-routes.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes.test.js +281 -0
- package/dist/tests/unit/auth-routes.test.js.map +1 -0
- package/dist/tests/unit/auth.test.d.ts +2 -0
- package/dist/tests/unit/auth.test.d.ts.map +1 -0
- package/dist/tests/unit/auth.test.js +1382 -0
- package/dist/tests/unit/auth.test.js.map +1 -0
- package/dist/tests/unit/billing.test.d.ts +2 -0
- package/dist/tests/unit/billing.test.d.ts.map +1 -0
- package/dist/tests/unit/billing.test.js +579 -0
- package/dist/tests/unit/billing.test.js.map +1 -0
- package/dist/tests/unit/budget-manager.test.d.ts +2 -0
- package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-manager.test.js +778 -0
- package/dist/tests/unit/budget-manager.test.js.map +1 -0
- package/dist/tests/unit/budget-race.test.d.ts +2 -0
- package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-race.test.js +58 -0
- package/dist/tests/unit/budget-race.test.js.map +1 -0
- package/dist/tests/unit/cli.test.d.ts +2 -0
- package/dist/tests/unit/cli.test.d.ts.map +1 -0
- package/dist/tests/unit/cli.test.js +93 -0
- package/dist/tests/unit/cli.test.js.map +1 -0
- package/dist/tests/unit/concurrency.test.d.ts +2 -0
- package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
- package/dist/tests/unit/concurrency.test.js +1270 -0
- package/dist/tests/unit/concurrency.test.js.map +1 -0
- package/dist/tests/unit/config-validate.test.d.ts +2 -0
- package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
- package/dist/tests/unit/config-validate.test.js +230 -0
- package/dist/tests/unit/config-validate.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.d.ts +2 -0
- package/dist/tests/unit/defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/defaults.test.js +364 -0
- package/dist/tests/unit/defaults.test.js.map +1 -0
- package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
- package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-backends.test.js +563 -0
- package/dist/tests/unit/dlp-backends.test.js.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.js +739 -0
- package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
- package/dist/tests/unit/error-responses.test.d.ts +2 -0
- package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
- package/dist/tests/unit/error-responses.test.js +101 -0
- package/dist/tests/unit/error-responses.test.js.map +1 -0
- package/dist/tests/unit/executor-registry.test.d.ts +2 -0
- package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
- package/dist/tests/unit/executor-registry.test.js +390 -0
- package/dist/tests/unit/executor-registry.test.js.map +1 -0
- package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
- package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/forward-proxy.test.js +621 -0
- package/dist/tests/unit/forward-proxy.test.js.map +1 -0
- package/dist/tests/unit/gateway-features.test.d.ts +2 -0
- package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-features.test.js +753 -0
- package/dist/tests/unit/gateway-features.test.js.map +1 -0
- package/dist/tests/unit/http-executor.test.d.ts +2 -0
- package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor.test.js +310 -0
- package/dist/tests/unit/http-executor.test.js.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.js +1136 -0
- package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.js +899 -0
- package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.js +759 -0
- package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
- package/dist/tests/unit/mcp-server.test.d.ts +15 -0
- package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-server.test.js +158 -0
- package/dist/tests/unit/mcp-server.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.d.ts +2 -0
- package/dist/tests/unit/metrics.test.d.ts.map +1 -0
- package/dist/tests/unit/metrics.test.js +208 -0
- package/dist/tests/unit/metrics.test.js.map +1 -0
- package/dist/tests/unit/oauth.test.d.ts +2 -0
- package/dist/tests/unit/oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth.test.js +281 -0
- package/dist/tests/unit/oauth.test.js.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
- package/dist/tests/unit/opa-engine.test.d.ts +2 -0
- package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-engine.test.js +1813 -0
- package/dist/tests/unit/opa-engine.test.js.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.js +528 -0
- package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.d.ts +2 -0
- package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +1345 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -0
- package/dist/tests/unit/policy-store.test.d.ts +2 -0
- package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-store.test.js +60 -0
- package/dist/tests/unit/policy-store.test.js.map +1 -0
- package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
- package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/postgres-storage.test.js +614 -0
- package/dist/tests/unit/postgres-storage.test.js.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
- package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.js +166 -0
- package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
- package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
- package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
- package/dist/tests/unit/rate-limiter.test.js +443 -0
- package/dist/tests/unit/rate-limiter.test.js.map +1 -0
- package/dist/tests/unit/redis-storage.test.d.ts +2 -0
- package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/redis-storage.test.js +766 -0
- package/dist/tests/unit/redis-storage.test.js.map +1 -0
- package/dist/tests/unit/replay-engine.test.d.ts +2 -0
- package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/replay-engine.test.js +371 -0
- package/dist/tests/unit/replay-engine.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +1399 -0
- package/dist/tests/unit/saas-routes.test.js.map +1 -0
- package/dist/tests/unit/session.test.d.ts +2 -0
- package/dist/tests/unit/session.test.d.ts.map +1 -0
- package/dist/tests/unit/session.test.js +532 -0
- package/dist/tests/unit/session.test.js.map +1 -0
- package/dist/tests/unit/slack-executor.test.d.ts +2 -0
- package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/slack-executor.test.js +209 -0
- package/dist/tests/unit/slack-executor.test.js.map +1 -0
- package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
- package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/storage-hardening.test.js +165 -0
- package/dist/tests/unit/storage-hardening.test.js.map +1 -0
- package/dist/tests/unit/storage.test.d.ts +2 -0
- package/dist/tests/unit/storage.test.d.ts.map +1 -0
- package/dist/tests/unit/storage.test.js +698 -0
- package/dist/tests/unit/storage.test.js.map +1 -0
- package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
- package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
- package/dist/tests/unit/text-normalizer.test.js +229 -0
- package/dist/tests/unit/text-normalizer.test.js.map +1 -0
- package/dist/tests/unit/tracing.test.d.ts +2 -0
- package/dist/tests/unit/tracing.test.d.ts.map +1 -0
- package/dist/tests/unit/tracing.test.js +611 -0
- package/dist/tests/unit/tracing.test.js.map +1 -0
- package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
- package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
- package/dist/tests/unit/trust-calculator.test.js +497 -0
- package/dist/tests/unit/trust-calculator.test.js.map +1 -0
- package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
- package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
- package/dist/tests/unit/ts-sdk.test.js +421 -0
- package/dist/tests/unit/ts-sdk.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
- package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +271 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -0
- package/dist/tests/unit/user-stores.test.d.ts +2 -0
- package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/user-stores.test.js +687 -0
- package/dist/tests/unit/user-stores.test.js.map +1 -0
- package/dist/tests/unit/validate.test.d.ts +2 -0
- package/dist/tests/unit/validate.test.d.ts.map +1 -0
- package/dist/tests/unit/validate.test.js +545 -0
- package/dist/tests/unit/validate.test.js.map +1 -0
- package/package.json +86 -0
- package/policy-packs/README.md +42 -0
- package/policy-packs/default.yaml +46 -0
- package/policy-packs/dev_fast.yaml +54 -0
- 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
|