veto-sdk 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +933 -57
  2. package/dist/admin/client.d.ts +93 -0
  3. package/dist/admin/client.d.ts.map +1 -0
  4. package/dist/admin/client.js +365 -0
  5. package/dist/admin/client.js.map +1 -0
  6. package/dist/admin/types.d.ts +205 -0
  7. package/dist/admin/types.d.ts.map +1 -0
  8. package/dist/admin/types.js +2 -0
  9. package/dist/admin/types.js.map +1 -0
  10. package/dist/audit/chain.d.ts +13 -0
  11. package/dist/audit/chain.d.ts.map +1 -0
  12. package/dist/audit/chain.js +32 -0
  13. package/dist/audit/chain.js.map +1 -0
  14. package/dist/browser/types.d.ts +1 -1
  15. package/dist/browser/types.d.ts.map +1 -1
  16. package/dist/browser/veto.d.ts.map +1 -1
  17. package/dist/browser/veto.js +24 -2
  18. package/dist/browser/veto.js.map +1 -1
  19. package/dist/cli/compile.js +2 -2
  20. package/dist/cli/compile.js.map +1 -1
  21. package/dist/cli/repl-generate.js +1 -1
  22. package/dist/cli/repl-generate.js.map +1 -1
  23. package/dist/cli/runner.d.ts.map +1 -1
  24. package/dist/cli/runner.js +129 -8
  25. package/dist/cli/runner.js.map +1 -1
  26. package/dist/cli/templates.d.ts +1 -1
  27. package/dist/cli/templates.d.ts.map +1 -1
  28. package/dist/cli/templates.js +1 -1
  29. package/dist/core/history.d.ts +14 -0
  30. package/dist/core/history.d.ts.map +1 -1
  31. package/dist/core/history.js +73 -13
  32. package/dist/core/history.js.map +1 -1
  33. package/dist/core/validator.d.ts +4 -0
  34. package/dist/core/validator.d.ts.map +1 -1
  35. package/dist/core/validator.js +119 -97
  36. package/dist/core/validator.js.map +1 -1
  37. package/dist/core/veto.d.ts +35 -1
  38. package/dist/core/veto.d.ts.map +1 -1
  39. package/dist/core/veto.js +98 -6
  40. package/dist/core/veto.js.map +1 -1
  41. package/dist/custom/types.d.ts +1 -1
  42. package/dist/custom/types.d.ts.map +1 -1
  43. package/dist/index.d.ts +11 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +9 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/integrations/langchain/middleware.d.ts +1 -1
  48. package/dist/integrations/langchain/middleware.js +1 -1
  49. package/dist/integrations/vercel-ai/middleware.d.ts +1 -1
  50. package/dist/integrations/vercel-ai/middleware.js +1 -1
  51. package/dist/observability/otel.d.ts +29 -0
  52. package/dist/observability/otel.d.ts.map +1 -0
  53. package/dist/observability/otel.js +43 -0
  54. package/dist/observability/otel.js.map +1 -0
  55. package/dist/proxy/anthropic-interceptor.d.ts +51 -0
  56. package/dist/proxy/anthropic-interceptor.d.ts.map +1 -0
  57. package/dist/proxy/anthropic-interceptor.js +132 -0
  58. package/dist/proxy/anthropic-interceptor.js.map +1 -0
  59. package/dist/proxy/interceptor.d.ts +55 -0
  60. package/dist/proxy/interceptor.d.ts.map +1 -0
  61. package/dist/proxy/interceptor.js +111 -0
  62. package/dist/proxy/interceptor.js.map +1 -0
  63. package/dist/proxy/server.d.ts +21 -0
  64. package/dist/proxy/server.d.ts.map +1 -0
  65. package/dist/proxy/server.js +545 -0
  66. package/dist/proxy/server.js.map +1 -0
  67. package/dist/proxy/types.d.ts +18 -0
  68. package/dist/proxy/types.d.ts.map +1 -0
  69. package/dist/proxy/types.js +7 -0
  70. package/dist/proxy/types.js.map +1 -0
  71. package/dist/rate-limiting/evaluator.d.ts +17 -0
  72. package/dist/rate-limiting/evaluator.d.ts.map +1 -0
  73. package/dist/rate-limiting/evaluator.js +48 -0
  74. package/dist/rate-limiting/evaluator.js.map +1 -0
  75. package/dist/rate-limiting/redis-store.d.ts +36 -0
  76. package/dist/rate-limiting/redis-store.d.ts.map +1 -0
  77. package/dist/rate-limiting/redis-store.js +68 -0
  78. package/dist/rate-limiting/redis-store.js.map +1 -0
  79. package/dist/rate-limiting/store.d.ts +8 -0
  80. package/dist/rate-limiting/store.d.ts.map +1 -0
  81. package/dist/rate-limiting/store.js +60 -0
  82. package/dist/rate-limiting/store.js.map +1 -0
  83. package/dist/rate-limiting/types.d.ts +9 -0
  84. package/dist/rate-limiting/types.d.ts.map +1 -0
  85. package/dist/rate-limiting/types.js +2 -0
  86. package/dist/rate-limiting/types.js.map +1 -0
  87. package/dist/rules/loader.d.ts.map +1 -1
  88. package/dist/rules/loader.js +2 -0
  89. package/dist/rules/loader.js.map +1 -1
  90. package/dist/rules/policy-ir-schema.d.ts +43 -1
  91. package/dist/rules/policy-ir-schema.d.ts.map +1 -1
  92. package/dist/rules/policy-ir-schema.js +27 -1
  93. package/dist/rules/policy-ir-schema.js.map +1 -1
  94. package/dist/rules/types.d.ts +20 -1
  95. package/dist/rules/types.d.ts.map +1 -1
  96. package/dist/rules/types.js.map +1 -1
  97. package/dist/testing/runner.d.ts +21 -0
  98. package/dist/testing/runner.d.ts.map +1 -0
  99. package/dist/testing/runner.js +239 -0
  100. package/dist/testing/runner.js.map +1 -0
  101. package/dist/testing/types.d.ts +39 -0
  102. package/dist/testing/types.d.ts.map +1 -0
  103. package/dist/testing/types.js +7 -0
  104. package/dist/testing/types.js.map +1 -0
  105. package/dist/types/config.d.ts +1 -0
  106. package/dist/types/config.d.ts.map +1 -1
  107. package/dist/types/config.js.map +1 -1
  108. package/package.json +20 -14
package/README.md CHANGED
@@ -3,21 +3,19 @@
3
3
  [![npm](https://img.shields.io/npm/v/veto-sdk?color=000000)](https://www.npmjs.com/package/veto-sdk)
4
4
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE)
5
5
 
6
- A guardrail system for AI agent tool calls. Veto intercepts and validates tool calls made by AI models before execution blocking, allowing, or routing to human approval.
6
+ Guardrails for AI agent tool calls. Veto intercepts and validates tool calls before execution -- blocking, allowing, or routing to human approval. The agent never knows.
7
7
 
8
8
  ## How it works
9
9
 
10
10
  1. **Initialize** Veto (loads your YAML rules).
11
11
  2. **Wrap** your tools with `veto.wrap()`.
12
- 3. **Pass** the wrapped tools to your agent types preserved, interface unchanged.
12
+ 3. **Pass** the wrapped tools to your agent -- types preserved, interface unchanged.
13
13
 
14
14
  When the AI calls a tool, Veto automatically:
15
15
 
16
16
  1. Intercepts the call.
17
17
  2. Validates arguments against your rules (deterministic conditions first, optional LLM for semantic rules).
18
- 3. **allow** executes · **block** denied with reason · **ask** approval queue.
19
-
20
- The agent is unaware of the guardrail.
18
+ 3. **allow** -- executes. **block** -- denied with reason. **ask** -- approval queue.
21
19
 
22
20
  ## Installation
23
21
 
@@ -25,11 +23,16 @@ The agent is unaware of the guardrail.
25
23
  npm install veto-sdk
26
24
  ```
27
25
 
28
- For a complete human-in-the-loop example, see the [HITL guide](../../docs/hitl-guide.md).
26
+ Optional peer dependencies:
27
+
28
+ ```bash
29
+ npm install @opentelemetry/api # OpenTelemetry tracing
30
+ npm install redis # Distributed rate limiting
31
+ ```
29
32
 
30
33
  ## Quick start
31
34
 
32
- ### 1. Initialize Veto
35
+ ### 1. Initialize
33
36
 
34
37
  ```bash
35
38
  npx veto init
@@ -39,15 +42,10 @@ Creates `./veto/veto.config.yaml` and default rules.
39
42
 
40
43
  ### 2. Wrap your tools
41
44
 
42
- `wrap()` is provider-agnostic works with LangChain, Vercel AI SDK, or any custom tool object.
45
+ `wrap()` is provider-agnostic -- works with LangChain, Vercel AI SDK, or any custom tool object.
43
46
 
44
47
  ```typescript
45
- import { Veto } from 'veto-sdk';
46
- import { tool } from '@langchain/core/tools';
47
-
48
- const myTools = [
49
- tool(async (args) => { /* ... */ }, { name: 'my_tool', /* ... */ }),
50
- ];
48
+ import { Veto } from "veto-sdk";
51
49
 
52
50
  const veto = await Veto.init();
53
51
 
@@ -81,18 +79,23 @@ rules:
81
79
  ```yaml
82
80
  version: "1.0"
83
81
 
84
- # "strict" blocks calls, "log" only logs them
82
+ # "strict" blocks calls, "log" only logs them, "shadow" computes decisions but never blocks
85
83
  mode: "strict"
86
84
 
87
85
  # Validation backend
88
86
  validation:
89
- mode: "custom" # "api", "kernel", or "custom"
87
+ mode: "local" # "local" | "api" | "kernel" | "custom" | "cloud"
90
88
 
91
- # Custom provider (if mode is custom)
89
+ # Custom LLM provider (if mode is "custom")
92
90
  custom:
93
- provider: "gemini" # openai | anthropic | gemini
91
+ provider: "gemini" # openai | anthropic | gemini | openrouter
94
92
  model: "gemini-3-flash-preview"
95
93
 
94
+ # Cloud mode
95
+ cloud:
96
+ apiKey: "veto_..."
97
+ baseUrl: "https://api.veto.so"
98
+
96
99
  logging:
97
100
  level: "info"
98
101
 
@@ -100,30 +103,81 @@ rules:
100
103
  directory: "./rules"
101
104
  recursive: true
102
105
 
103
- # Human-in-the-loop approval (for action: ask)
104
- # approval:
105
- # callbackUrl: "http://localhost:8787/approvals"
106
- # timeout: 30000
107
- # timeoutBehavior: "block"
106
+ # Human-in-the-loop approval (for action: require_approval)
107
+ approval:
108
+ callbackUrl: "http://localhost:8787/approvals"
109
+ timeout: 30000
110
+ timeoutBehavior: "block" # "block" | "allow"
111
+
112
+ # Webhook event routing
113
+ events:
114
+ webhook:
115
+ url: "https://hooks.slack.com/services/..."
116
+ on: [deny, require_approval, budget_exceeded]
117
+ min_severity: high
118
+ format: slack # slack | pagerduty | generic | cef
119
+
120
+ # Tamper-evident audit log
121
+ audit:
122
+ enabled: true
123
+ path: ".veto/audit.log"
124
+
125
+ # Economic authorization (x402, MPP, AP2)
126
+ economic:
127
+ budgets:
128
+ session: { limit: 500, currency: "USD" }
129
+ cost_extraction:
130
+ field: "arguments.amount"
131
+ ```
132
+
133
+ ### VetoOptions
134
+
135
+ ```typescript
136
+ const veto = await Veto.init({
137
+ configDir: "./veto",
138
+ mode: "strict", // 'strict' | 'log' | 'shadow'
139
+ logLevel: "info",
140
+ sessionId: "sess_123",
141
+ agentId: "agent_1",
142
+ userId: "user_42",
143
+ role: "trader",
144
+ apiKey: "veto_...", // auto-detects cloud mode
145
+ validators: [myCustomValidator],
146
+ onApprovalRequired: (ctx, approvalId) => {
147
+ /* show UI */
148
+ },
149
+ onDecisionMade: (result) => {
150
+ /* log, emit metrics */
151
+ },
152
+ telemetry: {
153
+ enabled: true,
154
+ serviceName: "my-agent",
155
+ },
156
+ audit: {
157
+ enabled: true,
158
+ path: ".veto/audit.log",
159
+ },
160
+ });
108
161
  ```
109
162
 
110
- ## API reference
163
+ ## API Reference
111
164
 
112
- ### `Veto.init(options?)`
165
+ ### `Veto.init(options?): Promise<Veto>`
113
166
 
114
- Initialize Veto. Loads configuration from `./veto` by default.
167
+ Initialize Veto. Loads configuration and rules from `./veto` by default.
115
168
 
116
169
  ```typescript
117
170
  const veto = await Veto.init();
171
+ const veto = await Veto.init({ configDir: "./policies", mode: "log" });
118
172
  ```
119
173
 
120
174
  ### `veto.wrap<T>(tools: T[]): T[]`
121
175
 
122
- Wrap an array of tools. Injects Veto validation into each tool's execution handler. Preserves types for full framework compatibility.
176
+ Wrap an array of tools. Injects validation into each tool's execution handler. Preserves types for full framework compatibility.
123
177
 
124
178
  ```typescript
125
179
  const wrappedForLangChain = veto.wrap(langChainTools);
126
- const wrappedForVercel = veto.wrap(vercelTools);
180
+ const wrappedForVercel = veto.wrap(vercelTools);
127
181
  ```
128
182
 
129
183
  ### `veto.wrapTool<T>(tool: T): T`
@@ -134,7 +188,60 @@ Wrap a single tool.
134
188
  const safeTool = veto.wrapTool(myTool);
135
189
  ```
136
190
 
137
- ### `veto.getHistoryStats()`
191
+ ### `veto.guard(toolName, args, context?): Promise<GuardResult>`
192
+
193
+ Standalone validation without wrapping or executing a tool. Returns the raw decision.
194
+
195
+ ```typescript
196
+ const result = await veto.guard("transfer_funds", { amount: 5000 });
197
+ // { decision: 'deny', reason: 'Amount exceeds limit', ruleId: 'limit-transfers', severity: 'high' }
198
+
199
+ const result = await veto.guard(
200
+ "send_email",
201
+ { to: "ceo@corp.com" },
202
+ {
203
+ sessionId: "sess_123",
204
+ agentId: "agent_1",
205
+ userId: "user_42",
206
+ }
207
+ );
208
+ ```
209
+
210
+ `GuardResult`:
211
+
212
+ ```typescript
213
+ interface GuardResult {
214
+ decision: "allow" | "deny" | "require_approval";
215
+ reason?: string;
216
+ ruleId?: string;
217
+ severity?: "critical" | "high" | "medium" | "low" | "info";
218
+ approvalId?: string;
219
+ shadow?: boolean;
220
+ economicDenial?: EconomicDenialDetails;
221
+ }
222
+ ```
223
+
224
+ ### `protect(tools, options?)`
225
+
226
+ One-call alternative to `Veto.init()` + `wrap()`. Accepts inline rules, policy packs, or cloud API keys.
227
+
228
+ ```typescript
229
+ import { protect } from "veto-sdk";
230
+
231
+ const safeTools = await protect(myTools, {
232
+ rules: [
233
+ {
234
+ id: "no-delete",
235
+ name: "Block deletes",
236
+ action: "block",
237
+ tools: ["delete_file"],
238
+ },
239
+ ],
240
+ mode: "strict",
241
+ });
242
+ ```
243
+
244
+ ### `veto.getHistoryStats(): HistoryStats`
138
245
 
139
246
  Statistics on allowed vs blocked calls.
140
247
 
@@ -145,60 +252,829 @@ const stats = veto.getHistoryStats();
145
252
 
146
253
  ### `veto.clearHistory()`
147
254
 
148
- Reset history statistics.
255
+ Reset decision history.
149
256
 
150
- ### `veto.exportDecisions(format)`
257
+ ### `veto.exportDecisions(format): string`
151
258
 
152
259
  Export decision history as JSON or CSV.
153
260
 
154
261
  ```typescript
155
262
  const json = veto.exportDecisions("json");
156
- const csv = veto.exportDecisions("csv");
263
+ const csv = veto.exportDecisions("csv");
264
+ ```
265
+
266
+ ### `veto.dispose()`
267
+
268
+ Clean up resources (timers, connections).
269
+
270
+ ---
271
+
272
+ ## Rate Limiting
273
+
274
+ Rules can include sliding-window rate limits. When the limit is exceeded, the rule's `action` fires.
275
+
276
+ ```yaml
277
+ rules:
278
+ - id: api-rate-limit
279
+ name: Limit API calls
280
+ action: block
281
+ tools:
282
+ - call_external_api
283
+ rate_limits:
284
+ - scope: session # agent | user | session | global
285
+ max_calls: 10
286
+ window_seconds: 60
287
+ ```
288
+
289
+ By default, rate limit state is stored in memory. For distributed systems, plug in Redis:
290
+
291
+ ```typescript
292
+ import { RedisRateLimitStore } from "veto-sdk";
293
+ import { createClient } from "redis";
294
+
295
+ const redis = createClient();
296
+ await redis.connect();
297
+
298
+ const store = new RedisRateLimitStore(redis, "veto:rl:");
299
+ ```
300
+
301
+ The `RedisRateLimitStore` uses a Lua script for atomic sliding-window checks (no TOCTOU race).
302
+
303
+ ### `RateLimitStore` interface
304
+
305
+ Implement this to bring your own store:
306
+
307
+ ```typescript
308
+ interface RateLimitStore {
309
+ checkAndRecord(
310
+ key: string,
311
+ maxCalls: number,
312
+ windowMs: number
313
+ ): boolean | Promise<boolean>;
314
+ clear(): void | Promise<void>;
315
+ }
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Audit Chain
321
+
322
+ Tamper-evident append-only audit log. Each decision is hashed with SHA-256 over the previous hash + the record, forming a hash chain. Any mutation to a historical record invalidates all subsequent hashes.
323
+
324
+ ```typescript
325
+ import { computeChainHash, GENESIS_HASH } from "veto-sdk";
326
+
327
+ let prevHash = GENESIS_HASH; // empty string
328
+
329
+ const entry1 = { tool: "transfer_funds", decision: "allow", ts: Date.now() };
330
+ prevHash = computeChainHash(prevHash, entry1);
331
+
332
+ const entry2 = { tool: "delete_account", decision: "deny", ts: Date.now() };
333
+ prevHash = computeChainHash(prevHash, entry2);
334
+ ```
335
+
336
+ Enable via config:
337
+
338
+ ```yaml
339
+ audit:
340
+ enabled: true
341
+ path: ".veto/audit.log"
342
+ ```
343
+
344
+ Verify integrity from the CLI:
345
+
346
+ ```bash
347
+ npx veto-cli audit verify
348
+ ```
349
+
350
+ ---
351
+
352
+ ## OpenTelemetry
353
+
354
+ Optional tracing via `@opentelemetry/api`. Zero overhead when the package is not installed -- all calls become no-ops.
355
+
356
+ ```typescript
357
+ import { tryLoadOtel, SpanStatusCode } from "veto-sdk";
358
+
359
+ const tracer = await tryLoadOtel("my-service");
360
+
361
+ const span = tracer.startSpan("validate-tool-call");
362
+ span.setAttribute("tool", "transfer_funds");
363
+ span.setStatus({ code: SpanStatusCode.OK });
364
+ span.end();
157
365
  ```
158
366
 
159
- ## CLI commands
367
+ Veto instruments itself automatically when `@opentelemetry/api` is present. Disable via config:
368
+
369
+ ```typescript
370
+ const veto = await Veto.init({
371
+ telemetry: { enabled: false },
372
+ });
373
+ ```
374
+
375
+ ### `VetoTracer` / `VetoSpan`
376
+
377
+ ```typescript
378
+ interface VetoTracer {
379
+ startSpan(name: string): VetoSpan;
380
+ }
381
+
382
+ interface VetoSpan {
383
+ setAttribute(key: string, value: string | number | boolean): void;
384
+ setStatus(status: { code: number; message?: string }): void;
385
+ end(): void;
386
+ }
387
+ ```
388
+
389
+ ---
390
+
391
+ ## SSE Proxy (`veto intercept`)
392
+
393
+ HTTP proxy that intercepts OpenAI and Anthropic streaming responses, validates tool calls in SSE streams before forwarding. Zero code changes to your agent.
394
+
395
+ ### CLI usage
396
+
397
+ ```bash
398
+ # Proxy OpenAI (default target: https://api.openai.com)
399
+ npx veto-cli intercept --port 8080
400
+
401
+ # Proxy Anthropic
402
+ npx veto-cli intercept --port 8080 --target https://api.anthropic.com --format anthropic
403
+
404
+ # Auto-detect format from target URL
405
+ npx veto-cli intercept --port 8080 --target https://api.anthropic.com
406
+ ```
407
+
408
+ Then point your SDK at the proxy:
409
+
410
+ ```typescript
411
+ const openai = new OpenAI({ baseURL: "http://localhost:8080/v1" });
412
+ ```
413
+
414
+ ### Programmatic usage
415
+
416
+ ```typescript
417
+ import { startProxyServer } from "veto-sdk/proxy";
418
+
419
+ const stop = await startProxyServer({
420
+ port: 8080,
421
+ target: "https://api.openai.com",
422
+ maxBufferBytes: 1024 * 1024, // 1 MB
423
+ configDir: "./veto",
424
+ format: "auto", // 'openai' | 'anthropic' | 'auto'
425
+ });
426
+
427
+ // Later:
428
+ await stop();
429
+ ```
430
+
431
+ ### `ProxyConfig`
432
+
433
+ ```typescript
434
+ interface ProxyConfig {
435
+ port: number; // default: 8080
436
+ target: string; // default: https://api.openai.com
437
+ maxBufferBytes: number; // default: 1 MB
438
+ configDir: string; // default: ./veto
439
+ format?: "openai" | "anthropic" | "auto";
440
+ }
441
+ ```
442
+
443
+ Architecture: non-tool-call responses are passed through. Tool-call responses are buffered until the stream signals completion, validated, then either flushed (allow) or replaced with a synthetic error (block). Buffer overflow beyond `maxBufferBytes` flushes without validation.
444
+
445
+ ---
446
+
447
+ ## Policy Testing
448
+
449
+ Deterministic test runner for policy rules. Loads YAML fixture files and evaluates test cases against your rules. No LLM, no network. Pure local replay.
450
+
451
+ ### CLI
160
452
 
161
453
  ```bash
162
- npx veto-cli@latest # Veto Studio (interactive TUI)
163
- npx veto-cli@latest policy generate --tool <name> --prompt <text>
164
- npx veto-cli@latest guard check --tool <name> --args <json> --json
165
- npx veto-cli@latest scan --fail-uncovered # CI gate
454
+ npx veto-cli test
455
+ npx veto-cli test --fixtures ./veto/tests --policy ./veto --coverage
456
+ ```
457
+
458
+ ### Programmatic
459
+
460
+ ```typescript
461
+ import { runTests } from "veto-sdk";
462
+
463
+ const result = await runTests({
464
+ fixturesPath: "./veto/tests",
465
+ policyPath: "./veto",
466
+ coverage: true,
467
+ quiet: false,
468
+ });
469
+
470
+ console.log(`${result.passed}/${result.total} passed, ${result.failed} failed`);
471
+ ```
472
+
473
+ ### Fixture format
474
+
475
+ ```yaml
476
+ suite: Financial rules
477
+ tests:
478
+ - id: block-large-transfer
479
+ description: Block transfers over $1000
480
+ tool: transfer_funds
481
+ arguments:
482
+ amount: 5000
483
+ recipient: vendor_123
484
+ expect:
485
+ decision: block
486
+ rule_id: limit-transfers
487
+
488
+ - id: allow-small-transfer
489
+ tool: transfer_funds
490
+ arguments:
491
+ amount: 50
492
+ expect:
493
+ decision: allow
494
+ ```
495
+
496
+ ### `RunTestsOptions`
497
+
498
+ ```typescript
499
+ interface RunTestsOptions {
500
+ fixturesPath?: string; // default: ./veto/tests
501
+ policyPath?: string; // default: ./veto
502
+ coverage?: boolean; // report untested rule IDs
503
+ quiet?: boolean; // suppress console output
504
+ }
505
+ ```
506
+
507
+ ### `VetoTestRunResult`
508
+
509
+ ```typescript
510
+ interface VetoTestRunResult {
511
+ total: number;
512
+ passed: number;
513
+ failed: number;
514
+ results: VetoTestResult[];
515
+ loadErrors?: string[];
516
+ }
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Provider Adapters
522
+
523
+ Convert tool definitions and tool calls between Veto's internal format and provider-specific formats.
524
+
525
+ ### OpenAI
526
+
527
+ ```typescript
528
+ import { toOpenAITools, fromOpenAI, fromOpenAIToolCall } from "veto-sdk";
529
+
530
+ // Veto definitions -> OpenAI format
531
+ const openaiTools = toOpenAITools(definitions);
532
+
533
+ // OpenAI tool -> Veto definition
534
+ const vetoDef = fromOpenAI(openaiTool);
535
+
536
+ // OpenAI tool_call -> Veto ToolCall
537
+ const vetoCall = fromOpenAIToolCall(toolCall);
538
+ ```
539
+
540
+ ### Anthropic
541
+
542
+ ```typescript
543
+ import {
544
+ toAnthropicTools,
545
+ fromAnthropic,
546
+ fromAnthropicToolUse,
547
+ } from "veto-sdk";
548
+
549
+ const anthropicTools = toAnthropicTools(definitions);
550
+ const vetoDef = fromAnthropic(anthropicTool);
551
+ const vetoCall = fromAnthropicToolUse(toolUseBlock);
552
+ ```
553
+
554
+ ### Google (Gemini)
555
+
556
+ ```typescript
557
+ import { toGoogleTool, fromGoogleFunctionCall } from "veto-sdk";
558
+
559
+ const googleTool = toGoogleTool(definitions);
560
+ const vetoCall = fromGoogleFunctionCall(functionCall);
561
+ ```
562
+
563
+ ### MCP (Model Context Protocol)
564
+
565
+ ```typescript
566
+ import { toMCPTools, fromMCP, fromMCPToolCall, isMCPTool } from "veto-sdk";
567
+
568
+ const mcpTools = toMCPTools(definitions);
569
+ const vetoDef = fromMCP(mcpTool);
570
+ const vetoCall = fromMCPToolCall("tool_name", mcpArgs);
571
+ ```
572
+
573
+ ---
574
+
575
+ ## Output Redaction
576
+
577
+ Built-in regex patterns for detecting sensitive data in tool outputs.
578
+
579
+ ```typescript
580
+ import {
581
+ OUTPUT_PATTERNS,
582
+ OUTPUT_PATTERN_SSN,
583
+ OUTPUT_PATTERN_CREDIT_CARD,
584
+ OUTPUT_PATTERN_OPENAI_API_KEY,
585
+ OUTPUT_PATTERN_GITHUB_API_KEY,
586
+ OUTPUT_PATTERN_AWS_API_KEY,
587
+ OUTPUT_PATTERN_EMAIL,
588
+ OUTPUT_PATTERN_US_PHONE,
589
+ } from "veto-sdk";
590
+
591
+ // OUTPUT_PATTERNS is an object with all patterns:
592
+ // { ssn, creditCard, openAIApiKey, githubApiKey, awsApiKey, email, usPhone }
593
+ ```
594
+
595
+ Use these in output rules to block or redact sensitive data from tool responses:
596
+
597
+ ```yaml
598
+ output_rules:
599
+ - id: redact-ssn
600
+ name: Redact SSNs from output
601
+ action: redact
602
+ redact_with: "[REDACTED SSN]"
603
+ output_conditions:
604
+ - field: output
605
+ operator: matches
606
+ value: '\b\d{3}-\d{2}-\d{4}\b'
607
+
608
+ - id: block-api-keys
609
+ name: Block responses containing API keys
610
+ action: block
611
+ severity: critical
612
+ output_conditions:
613
+ - field: output
614
+ operator: matches
615
+ value: '\bsk-(?:proj-)?[A-Za-z0-9]{20,}\b'
616
+ ```
617
+
618
+ ---
619
+
620
+ ## Webhooks
621
+
622
+ Route policy decision events to external systems. Four payload formats are supported.
623
+
624
+ ### Configuration
625
+
626
+ ```yaml
627
+ events:
628
+ webhook:
629
+ url: "https://hooks.slack.com/services/T00/B00/xxx"
630
+ on: [deny, require_approval, budget_exceeded]
631
+ min_severity: high
632
+ format: slack
633
+ redact_arguments: true # or ["password", "ssn"] for selective redaction
634
+ ```
635
+
636
+ Event types: `deny`, `require_approval`, `budget_exceeded`, `budget_warning`, `approval_triggered`, `spend_committed`, `protocol_detected`.
637
+
638
+ ### Programmatic formatting
639
+
640
+ ```typescript
641
+ import {
642
+ formatSlackPayload,
643
+ formatPagerDutyPayload,
644
+ formatGenericPayload,
645
+ formatCefPayload,
646
+ } from "veto-sdk";
647
+
648
+ const event = {
649
+ eventType: "deny",
650
+ toolName: "transfer_funds",
651
+ arguments: { amount: 5000 },
652
+ decision: "deny",
653
+ reason: "Amount exceeds limit",
654
+ ruleId: "limit-transfers",
655
+ severity: "high",
656
+ timestamp: new Date().toISOString(),
657
+ };
658
+
659
+ const slack = formatSlackPayload(event); // Slack Block Kit
660
+ const pd = formatPagerDutyPayload(event); // PagerDuty Events API v2
661
+ const json = formatGenericPayload(event); // Plain JSON
662
+ const cef = formatCefPayload(event); // ArcSight CEF string
663
+ ```
664
+
665
+ ---
666
+
667
+ ## VetoAdmin (Cloud Management)
668
+
669
+ Management client for the Veto Cloud API. Full CRUD for policies, decisions, approvals, MCP upstreams, and API keys.
670
+
671
+ ```typescript
672
+ import { VetoAdmin } from "veto-sdk";
673
+
674
+ const admin = new VetoAdmin({
675
+ apiKey: process.env.VETO_API_KEY!,
676
+ baseUrl: "https://api.veto.so", // optional, this is the default
677
+ timeout: 30000, // optional
678
+ });
679
+ ```
680
+
681
+ ### Policies
682
+
683
+ ```typescript
684
+ const policies = await admin.listPolicies();
685
+ const policy = await admin.getPolicy("transfer_funds");
686
+
687
+ await admin.createPolicy({
688
+ toolName: "transfer_funds",
689
+ mode: "deterministic",
690
+ constraints: [{ argumentName: "amount", maximum: 1000, enabled: true }],
691
+ });
692
+
693
+ await admin.updatePolicy("transfer_funds", {
694
+ mode: "llm",
695
+ llmConfig: { description: "..." },
696
+ });
697
+ await admin.activatePolicy("transfer_funds");
698
+ await admin.deactivatePolicy("transfer_funds");
699
+ await admin.deletePolicy("transfer_funds");
700
+ const yaml = await admin.exportPolicies({ format: "yaml" });
701
+ ```
702
+
703
+ ### Decisions
704
+
705
+ ```typescript
706
+ const decisions = await admin.listDecisions({
707
+ toolName: "transfer_funds",
708
+ limit: 50,
709
+ });
710
+ const decision = await admin.getDecision("dec_123");
711
+ const stats = await admin.getDecisionStats({ startDate: "2025-01-01" });
712
+ const csv = await admin.exportDecisions({ format: "csv" });
713
+ ```
714
+
715
+ ### Approvals
716
+
717
+ ```typescript
718
+ const pending = await admin.listPendingApprovals();
719
+ const approval = await admin.getApproval("apr_123");
720
+ await admin.resolveApproval("apr_123", "approve", "user@corp.com");
721
+ await admin.batchResolveApprovals([
722
+ { id: "apr_1", action: "approve", resolvedBy: "admin" },
723
+ { id: "apr_2", action: "deny", resolvedBy: "admin" },
724
+ ]);
725
+ ```
726
+
727
+ ### MCP Gateway
728
+
729
+ ```typescript
730
+ const upstreams = await admin.listUpstreams();
731
+ await admin.createUpstream({
732
+ name: "my-server",
733
+ transport: "mcp-sse",
734
+ url: "http://localhost:3001/mcp",
735
+ });
736
+ const test = await admin.testUpstream("ups_123");
737
+ await admin.deleteUpstream("ups_123");
738
+ ```
739
+
740
+ ### API Keys
741
+
742
+ ```typescript
743
+ const keys = await admin.listApiKeys();
744
+ const newKey = await admin.createApiKey({ name: "ci-pipeline" });
745
+ console.log(newKey.key); // only shown once
746
+ await admin.revokeApiKey(newKey._id);
747
+ ```
748
+
749
+ ### Policy Drafts
750
+
751
+ ```typescript
752
+ const draft = await admin.createPolicyDraft({
753
+ name: "New financial rules",
754
+ rules: [
755
+ {
756
+ /* ... */
757
+ },
758
+ ],
759
+ status: "pending_review",
760
+ });
761
+ await admin.approvePolicyDraft(draft._id);
762
+ await admin.rejectPolicyDraft(draft._id, "Missing edge case coverage");
763
+ ```
764
+
765
+ ### Real-time Events (SSE)
766
+
767
+ ```typescript
768
+ // Callback-based
769
+ const sub = admin.onEvent(["deny", "require_approval"], (event) => {
770
+ console.log(event.type, event.data);
771
+ });
772
+ sub.unsubscribe();
773
+
774
+ // Async iterator
775
+ for await (const event of admin.subscribeEvents({ types: ["deny"] })) {
776
+ console.log(event.type, event.data);
777
+ }
778
+ ```
779
+
780
+ ---
781
+
782
+ ## Cloud Client
783
+
784
+ For direct integration with Veto Cloud's validation and approval workflow.
785
+
786
+ ```typescript
787
+ import { VetoCloudClient, ApprovalTimeoutError } from "veto-sdk";
788
+ ```
789
+
790
+ When you pass `apiKey` to `Veto.init()`, cloud mode is auto-detected. The SDK registers tools, validates calls against cloud policies, and polls for approval resolution.
791
+
792
+ ```typescript
793
+ const veto = await Veto.init({
794
+ apiKey: "veto_...",
795
+ onApprovalRequired: (ctx, approvalId) => {
796
+ console.log(`Approval required: ${approvalId}`);
797
+ },
798
+ });
799
+ ```
800
+
801
+ `ApprovalTimeoutError` is thrown when an approval poll exceeds the configured timeout.
802
+
803
+ ---
804
+
805
+ ## Economic Authorization
806
+
807
+ Protocol-agnostic economic policy enforcement for agent payments across x402 (EVM L2), Stripe MPP, and Google AP2.
808
+
809
+ ```typescript
810
+ import {
811
+ LocalBudgetEngine,
812
+ EconomicEvaluator,
813
+ createX402Connector,
814
+ createMPPConnector,
815
+ createAP2Connector,
816
+ } from "veto-sdk";
817
+ ```
818
+
819
+ ### Budget tracking
820
+
821
+ ```typescript
822
+ const budget = new LocalBudgetEngine({
823
+ budgets: { session: { limit: 500, currency: "USD" } },
824
+ });
825
+
826
+ const check = budget.check("session", 100);
827
+ // { allowed: true, remaining: 400 }
828
+
829
+ budget.commit("session", 100);
830
+ ```
831
+
832
+ ### Protocol connectors
833
+
834
+ ```typescript
835
+ const x402 = createX402Connector({ chainId: 8453 });
836
+ const mpp = createMPPConnector({ sessionId: "sess_..." });
837
+ const ap2 = createAP2Connector({ mandateId: "mandate_..." });
838
+ ```
839
+
840
+ ### Rule-based payment gates
841
+
842
+ ```yaml
843
+ rules:
844
+ - id: paid-api-call
845
+ name: Require payment for premium API
846
+ action: require_payment
847
+ tools:
848
+ - premium_search
849
+ payment:
850
+ protocol: x402
851
+ amount: 0.01
852
+ currency: USDC
853
+ chain_id: 8453
854
+ ```
855
+
856
+ See the [economic authorization guide](../../docs/economic-authorization.md) for full details.
857
+
858
+ ---
859
+
860
+ ## Advanced
861
+
862
+ ### Policy Compiler
863
+
864
+ AST-based policy expression engine. Compile expressions, evaluate against context, and type-check for errors. No runtime `eval()`.
865
+
866
+ ```typescript
867
+ import { compile, evaluate, typeCheck } from "veto-sdk";
868
+
869
+ const ast = compile('amount > 1000 && currency == "USD"');
870
+ const result = evaluate(ast, { amount: 1500, currency: "USD" });
871
+ // result === true
872
+
873
+ const issues = typeCheck(ast);
874
+ // TypeCheckResult { valid: boolean, issues: TypeIssue[] }
875
+ ```
876
+
877
+ Use expressions in rule conditions:
878
+
879
+ ```yaml
880
+ rules:
881
+ - id: complex-check
882
+ name: Multi-field validation
883
+ action: block
884
+ conditions:
885
+ - expression: 'amount > 1000 && currency != "USD"'
886
+ ```
887
+
888
+ ### Local Evaluator
889
+
890
+ Offline deterministic rule evaluation. No API calls, sub-millisecond latency. Supports all 14 condition operators, dot-notation field paths, AND/OR condition groups, and tool filtering.
891
+
892
+ ```typescript
893
+ import { evaluateRulesLocally } from "veto-sdk";
894
+
895
+ const result = evaluateRulesLocally(rules, {
896
+ toolName: "transfer_funds",
897
+ arguments: { amount: 5000 },
898
+ });
899
+ // { decision: 'deny', reason: '...', ruleId: 'limit-transfers' }
900
+ ```
901
+
902
+ ### Deterministic Constraints
903
+
904
+ Programmatic constraint definitions for cloud-managed deterministic policies.
905
+
906
+ ```typescript
907
+ import type {
908
+ ArgumentConstraint,
909
+ SessionConstraints,
910
+ DeterministicPolicy,
911
+ } from "veto-sdk";
912
+
913
+ const constraint: ArgumentConstraint = {
914
+ argumentName: "amount",
915
+ enabled: true,
916
+ maximum: 1000,
917
+ dynamicMaximum: "session.remaining * 0.15",
918
+ };
919
+
920
+ const session: SessionConstraints = {
921
+ maxCalls: 100,
922
+ budget: 500,
923
+ cumulativeLimits: [{ argumentName: "amount", maxValue: 500 }],
924
+ counters: {
925
+ open_positions: {
926
+ increment: ["buy_shares"],
927
+ decrement: ["sell_shares"],
928
+ max: 3,
929
+ maxAction: "require_approval",
930
+ },
931
+ },
932
+ };
933
+ ```
934
+
935
+ Dynamic expressions have access to `session.budget`, `session.spent`, `session.remaining`, `session.counter.<name>`, and `args.<name>`.
936
+
937
+ ### Content Extraction
938
+
939
+ Extract structured entities from text content.
940
+
941
+ ```typescript
942
+ import { extractEntities } from "veto-sdk";
943
+
944
+ const entities = extractEntities(text, { patterns: ["email", "ssn"] });
945
+ // { emails: [...], ssns: [...] }
166
946
  ```
167
947
 
168
- → [Full CLI reference](../cli/README.md)
948
+ ---
169
949
 
170
- ## Rule YAML format
950
+ ## Rule YAML Reference
951
+
952
+ ### Input rules
171
953
 
172
954
  ```yaml
173
955
  rules:
174
- - id: unique-rule-id # required
175
- name: Human readable name # required
176
- enabled: true # optional, default: true
177
- severity: high # critical | high | medium | low | info
178
- action: block # block | warn | log | allow | ask
179
-
180
- # Scope: which tools does this rule apply to?
181
- # Omit or leave empty to apply to ALL tools (global rule).
956
+ - id: unique-rule-id # required
957
+ name: Human readable name # required
958
+ description: "..." # optional, used for LLM validation
959
+ enabled: true # default: true
960
+ severity: high # critical | high | medium | low | info
961
+ action: block # block | warn | log | allow | require_approval | require_payment
962
+
963
+ # Scope: which tools (omit for global rule)
182
964
  tools:
183
965
  - make_payment
184
966
 
185
- # Static conditions (optional) — evaluated locally, zero latency
967
+ # Agent scope (optional)
968
+ agents: [agent_1, agent_2]
969
+ # or exclude: agents: { not: [agent_3] }
970
+
971
+ # Static conditions (AND logic, zero latency)
186
972
  conditions:
187
- - field: arguments.amount # dot notation for nested args
188
- operator: greater_than # equals | contains | starts_with | ends_with | greater_than | less_than
973
+ - field: arguments.amount
974
+ operator: greater_than
189
975
  value: 1000
976
+ - field: arguments.currency
977
+ operator: in
978
+ value: [BTC, ETH]
979
+
980
+ # OR logic between groups
981
+ condition_groups:
982
+ - - field: arguments.amount
983
+ operator: greater_than
984
+ value: 10000
985
+ - - field: arguments.destination
986
+ operator: matches
987
+ value: '^offshore_.*'
988
+
989
+ # Expression syntax (alternative to field/operator/value)
990
+ conditions:
991
+ - expression: 'amount > 1000 && currency != "USD"'
992
+
993
+ # Rate limits (sliding window)
994
+ rate_limits:
995
+ - scope: session
996
+ max_calls: 10
997
+ window_seconds: 60
998
+
999
+ # Cross-tool sequence constraints
1000
+ blocked_by:
1001
+ - tool: disable_mfa
1002
+ within: 3600
1003
+ requires:
1004
+ - tool: verify_identity
1005
+
1006
+ # Payment gate
1007
+ payment:
1008
+ protocol: x402
1009
+ amount: 0.01
1010
+ currency: USDC
1011
+ ```
190
1012
 
191
- # Semantic guidance for LLM validation (optional)
192
- description: "Ensure the payment recipient is a verified vendor."
1013
+ ### Condition operators
1014
+
1015
+ | Operator | Description |
1016
+ | --------------------- | ------------------------------------------ |
1017
+ | `equals` | Exact match (case-insensitive for strings) |
1018
+ | `not_equals` | Inverse of equals |
1019
+ | `contains` | Substring match |
1020
+ | `not_contains` | Inverse of contains |
1021
+ | `starts_with` | Prefix match |
1022
+ | `ends_with` | Suffix match |
1023
+ | `matches` | Regex match |
1024
+ | `greater_than` | Numeric comparison |
1025
+ | `less_than` | Numeric comparison |
1026
+ | `in` | Value in array |
1027
+ | `not_in` | Value not in array |
1028
+ | `length_greater_than` | String/array length |
1029
+ | `percent_of` | Percentage of reference field |
1030
+ | `within_hours` | Time window match |
1031
+ | `outside_hours` | Inverse time window |
1032
+
1033
+ ### Output rules
1034
+
1035
+ ```yaml
1036
+ output_rules:
1037
+ - id: unique-output-rule-id
1038
+ name: Redact sensitive data
1039
+ enabled: true
1040
+ severity: high
1041
+ action: redact # block | redact | log
1042
+ tools:
1043
+ - query_database
1044
+ redact_with: "[REDACTED]"
1045
+ output_conditions:
1046
+ - field: output
1047
+ operator: matches
1048
+ value: '\b\d{3}-\d{2}-\d{4}\b'
193
1049
  ```
194
1050
 
195
- ## Rule matching logic
1051
+ ### Time window conditions
196
1052
 
197
- Veto uses a two-step process:
1053
+ ```yaml
1054
+ conditions:
1055
+ - field: "@timestamp"
1056
+ operator: outside_hours
1057
+ value:
1058
+ start: "09:00"
1059
+ end: "17:00"
1060
+ timezone: "America/New_York"
1061
+ days: [mon, tue, wed, thu, fri]
1062
+ ```
1063
+
1064
+ ## CLI Commands
1065
+
1066
+ ```bash
1067
+ npx veto-cli@latest # Veto Studio (interactive TUI)
1068
+ npx veto-cli@latest policy generate --tool <name> # Generate policy from tool
1069
+ npx veto-cli@latest guard check --tool <name> --args <json> # Test a guard check
1070
+ npx veto-cli@latest scan --fail-uncovered # CI gate
1071
+ npx veto-cli@latest test # Run policy tests
1072
+ npx veto-cli@latest intercept --port 8080 # Start SSE proxy
1073
+ npx veto-cli@latest audit verify # Verify audit chain
1074
+ ```
198
1075
 
199
- 1. **Rule selection** — rules with a matching `tools` list apply. Rules with no `tools` (global rules) apply to every call.
200
- 2. **Validation** — static `conditions` are checked first (local, no API call). If conditions match, the rule triggers immediately. Otherwise, the rule's `name` and `description` are sent to the LLM for semantic validation.
1076
+ Full CLI reference: [`veto-cli`](../cli/README.md)
201
1077
 
202
1078
  ## License
203
1079
 
204
- Apache-2.0 © [Plaw, Inc.](https://plaw.io)
1080
+ Apache-2.0 (c) [Plaw, Inc.](https://plaw.io)