veto-sdk 2.8.1 → 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,1078 +3,156 @@
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
- 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.
6
+ TypeScript policy runtime for AI agent tool calls. Veto wraps your tools, evaluates deterministic policy before each handler runs, and preserves the original tool interface.
7
7
 
8
- ## How it works
9
-
10
- 1. **Initialize** Veto (loads your YAML rules).
11
- 2. **Wrap** your tools with `veto.wrap()`.
12
- 3. **Pass** the wrapped tools to your agent -- types preserved, interface unchanged.
13
-
14
- When the AI calls a tool, Veto automatically:
15
-
16
- 1. Intercepts the call.
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
- ## Installation
8
+ ## Install
21
9
 
22
10
  ```bash
23
11
  npm install veto-sdk
24
12
  ```
25
13
 
26
- Optional peer dependencies:
14
+ ## Quick start
27
15
 
28
- ```bash
29
- npm install @opentelemetry/api # OpenTelemetry tracing
30
- npm install redis # Distributed rate limiting
16
+ ```ts
17
+ import { protect } from "veto-sdk";
18
+
19
+ const safeTools = await protect(tools);
20
+ const agent = createAgent({ tools: safeTools });
31
21
  ```
32
22
 
33
- ## Quick start
23
+ `protect(tools)` is the public entrypoint. It loads `./veto/veto.config.yaml` and `./veto/rules/*.yaml` when present. Without local policy or explicit options, it uses the built-in `@veto/safe-defaults` pack in observe mode: suspicious destructive shell, file, database, or money-movement network patterns are warned/logged, not blocked.
34
24
 
35
- ### 1. Initialize
25
+ ## 60-second denied call
36
26
 
37
27
  ```bash
28
+ npm i veto-sdk openai
38
29
  npx veto init
30
+ node examples/60-second-denied-call/denied-call.mjs
39
31
  ```
40
32
 
41
- Creates `./veto/veto.config.yaml` and default rules.
42
-
43
- ### 2. Wrap your tools
44
-
45
- `wrap()` is provider-agnostic -- works with LangChain, Vercel AI SDK, or any custom tool object.
33
+ `npx veto init` creates blocking local defaults in `veto/rules/defaults.yaml`, so the example deterministically denies `bash` with `rm -rf` before the handler runs.
46
34
 
47
- ```typescript
48
- import { Veto } from "veto-sdk";
49
-
50
- const veto = await Veto.init();
35
+ ## Python parity
51
36
 
52
- // Types are preserved: wrappedTools has the same type as myTools
53
- const wrappedTools = veto.wrap(myTools);
37
+ ```python
38
+ from veto import protect
54
39
 
55
- const agent = createAgent({ tools: wrappedTools });
40
+ safe = await protect(tools)
41
+ agent = create_agent(tools=safe)
56
42
  ```
57
43
 
58
- ### 3. Configure rules
44
+ ## Local policy
59
45
 
60
- Edit `veto/rules/financial.yaml`:
46
+ ```bash
47
+ npx veto init
48
+ ```
61
49
 
62
50
  ```yaml
63
51
  rules:
64
- - id: limit-transfers
65
- name: Limit large transfers
52
+ - id: block-large-transfers
53
+ name: Block transfers over $1,000
54
+ enabled: true
55
+ severity: high
66
56
  action: block
67
- tools:
68
- - transfer_funds
57
+ tools: [transfer_funds]
69
58
  conditions:
70
59
  - field: arguments.amount
71
60
  operator: greater_than
72
61
  value: 1000
73
62
  ```
74
63
 
75
- ## Configuration
76
-
77
- ### veto.config.yaml
78
-
79
- ```yaml
80
- version: "1.0"
81
-
82
- # "strict" blocks calls, "log" only logs them, "shadow" computes decisions but never blocks
83
- mode: "strict"
84
-
85
- # Validation backend
86
- validation:
87
- mode: "local" # "local" | "api" | "kernel" | "custom" | "cloud"
88
-
89
- # Custom LLM provider (if mode is "custom")
90
- custom:
91
- provider: "gemini" # openai | anthropic | gemini | openrouter
92
- model: "gemini-3-flash-preview"
93
-
94
- # Cloud mode
95
- cloud:
96
- apiKey: "veto_..."
97
- baseUrl: "https://api.veto.so"
98
-
99
- logging:
100
- level: "info"
101
-
102
- rules:
103
- directory: "./rules"
104
- recursive: true
105
-
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
- });
161
- ```
162
-
163
- ## API Reference
164
-
165
- ### `Veto.init(options?): Promise<Veto>`
166
-
167
- Initialize Veto. Loads configuration and rules from `./veto` by default.
168
-
169
- ```typescript
170
- const veto = await Veto.init();
171
- const veto = await Veto.init({ configDir: "./policies", mode: "log" });
172
- ```
173
-
174
- ### `veto.wrap<T>(tools: T[]): T[]`
64
+ Actions are `block`, `allow`, `warn`, `log`, and `require_approval`.
175
65
 
176
- Wrap an array of tools. Injects validation into each tool's execution handler. Preserves types for full framework compatibility.
177
-
178
- ```typescript
179
- const wrappedForLangChain = veto.wrap(langChainTools);
180
- const wrappedForVercel = veto.wrap(vercelTools);
181
- ```
182
-
183
- ### `veto.wrapTool<T>(tool: T): T`
184
-
185
- Wrap a single tool.
186
-
187
- ```typescript
188
- const safeTool = veto.wrapTool(myTool);
189
- ```
190
-
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
- ```
66
+ ## API
223
67
 
224
68
  ### `protect(tools, options?)`
225
69
 
226
- One-call alternative to `Veto.init()` + `wrap()`. Accepts inline rules, policy packs, or cloud API keys.
227
-
228
- ```typescript
70
+ ```ts
229
71
  import { protect } from "veto-sdk";
230
72
 
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`
245
-
246
- Statistics on allowed vs blocked calls.
247
-
248
- ```typescript
249
- const stats = veto.getHistoryStats();
250
- // { totalCalls: 5, allowedCalls: 4, deniedCalls: 1, ... }
251
- ```
252
-
253
- ### `veto.clearHistory()`
254
-
255
- Reset decision history.
256
-
257
- ### `veto.exportDecisions(format): string`
258
-
259
- Export decision history as JSON or CSV.
260
-
261
- ```typescript
262
- const json = veto.exportDecisions("json");
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();
365
- ```
366
-
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
452
-
453
- ```bash
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
- });
73
+ const safeTools = await protect(tools);
679
74
  ```
680
75
 
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
- });
76
+ Options mirror the advanced runtime configuration when you need explicit policy sources:
692
77
 
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",
78
+ ```ts
79
+ const safeTools = await protect(tools, {
754
80
  rules: [
755
81
  {
756
- /* ... */
82
+ id: "no-prod-deploy",
83
+ name: "Block direct production deploys",
84
+ enabled: true,
85
+ severity: "critical",
86
+ action: "block",
87
+ tools: ["deploy"],
88
+ conditions: [
89
+ {
90
+ field: "arguments.environment",
91
+ operator: "equals",
92
+ value: "production",
93
+ },
94
+ ],
757
95
  },
758
96
  ],
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" } },
97
+ mode: "strict",
824
98
  });
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
99
  ```
887
100
 
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
- ```
101
+ Supported policy sources:
901
102
 
902
- ### Deterministic Constraints
103
+ - `rules`: inline deterministic rules
104
+ - `pack`: built-in policy pack such as `@veto/coding-agent`
105
+ - `configDir`: explicit local config directory
106
+ - `apiKey` / `endpoint`: cloud or self-hosted PDP
903
107
 
904
- Programmatic constraint definitions for cloud-managed deterministic policies.
108
+ ### Advanced: `Veto.init()` + `.wrap()`
905
109
 
906
- ```typescript
907
- import type {
908
- ArgumentConstraint,
909
- SessionConstraints,
910
- DeterministicPolicy,
911
- } from "veto-sdk";
110
+ `Veto.init()` remains supported for advanced/internal-facing integrations that need a reusable instance, direct `guard()` calls, event hooks, audit export, or explicit self-host/cloud configuration.
912
111
 
913
- const constraint: ArgumentConstraint = {
914
- argumentName: "amount",
915
- enabled: true,
916
- maximum: 1000,
917
- dynamicMaximum: "session.remaining * 0.15",
918
- };
112
+ ```ts
113
+ import { Veto } from "veto-sdk";
919
114
 
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
- };
115
+ const veto = await Veto.init({ configDir: "./veto", mode: "strict" });
116
+ const safeTools = veto.wrap(tools);
117
+ const decision = await veto.guard("transfer_funds", { amount: 1500 });
933
118
  ```
934
119
 
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";
120
+ ### `veto.guard(toolName, args, context?)`
943
121
 
944
- const entities = extractEntities(text, { patterns: ["email", "ssn"] });
945
- // { emails: [...], ssns: [...] }
122
+ ```ts
123
+ const result = await veto.guard("transfer_funds", { amount: 5000 });
124
+ // { decision: 'deny', reason: 'Amount exceeds limit', ruleId: 'block-large-transfers' }
946
125
  ```
947
126
 
948
- ---
949
-
950
- ## Rule YAML Reference
951
-
952
- ### Input rules
953
-
954
- ```yaml
955
- rules:
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
127
+ ### Decision history
962
128
 
963
- # Scope: which tools (omit for global rule)
964
- tools:
965
- - make_payment
966
-
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)
972
- conditions:
973
- - field: arguments.amount
974
- operator: greater_than
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
129
+ ```ts
130
+ const stats = veto.getHistoryStats();
131
+ const json = veto.exportDecisions("json");
132
+ const csv = veto.exportDecisions("csv");
1011
133
  ```
1012
134
 
1013
- ### Condition operators
135
+ Decision export is local to your process unless you explicitly configure a remote endpoint.
1014
136
 
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 |
137
+ ## Policy packs
1032
138
 
1033
- ### Output rules
139
+ Built-in packs ship in `packs/` and are referenced with `extends` or `protect(..., { pack })`:
1034
140
 
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'
1049
- ```
1050
-
1051
- ### Time window conditions
1052
-
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
141
+ - `@veto/safe-defaults` observe-mode zero-config defaults
142
+ - `@veto/coding-agent`
143
+ - `@veto/financial`
144
+ - `@veto/crypto-trading`
145
+ - `@veto/browser-automation`
146
+ - `@veto/data-access`
147
+ - `@veto/communication`
148
+ - `@veto/deployment`
149
+ - `@veto/economic-agent`
150
+ - `@veto/soc2-lite`, `@veto/hipaa-lite`, `@veto/eu-ai-act-starter` starter packs
1065
151
 
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
- ```
152
+ ## Self-host / BYOC boundary
1075
153
 
1076
- Full CLI reference: [`veto-cli`](../cli/README.md)
154
+ The SDK can point at a self-hosted PDP with `endpoint`, but customer-plane data stays in the customer environment unless you explicitly configure outbound integrations. Public BYOC artifacts in the repo state the boundary: customer policy, decision rows, tool args, agent IDs, user IDs, Slack content, prompts, environment variables, and secrets do not cross to Plaw.
1077
155
 
1078
156
  ## License
1079
157
 
1080
- Apache-2.0 (c) [Plaw, Inc.](https://plaw.io)
158
+ Apache-2.0