veto-sdk 2.2.0 → 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.
- package/README.md +933 -57
- package/dist/admin/client.d.ts +93 -0
- package/dist/admin/client.d.ts.map +1 -0
- package/dist/admin/client.js +365 -0
- package/dist/admin/client.js.map +1 -0
- package/dist/admin/types.d.ts +205 -0
- package/dist/admin/types.d.ts.map +1 -0
- package/dist/admin/types.js +2 -0
- package/dist/admin/types.js.map +1 -0
- package/dist/audit/chain.d.ts +13 -0
- package/dist/audit/chain.d.ts.map +1 -0
- package/dist/audit/chain.js +32 -0
- package/dist/audit/chain.js.map +1 -0
- package/dist/browser/protect.d.ts.map +1 -1
- package/dist/browser/protect.js +9 -1
- package/dist/browser/protect.js.map +1 -1
- package/dist/browser/types.d.ts +4 -1
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/browser/veto.d.ts.map +1 -1
- package/dist/browser/veto.js +33 -2
- package/dist/browser/veto.js.map +1 -1
- package/dist/cli/bin.js +0 -0
- package/dist/cli/compile.js +2 -2
- package/dist/cli/compile.js.map +1 -1
- package/dist/cli/repl-generate.js +1 -1
- package/dist/cli/repl-generate.js.map +1 -1
- package/dist/cli/runner.d.ts.map +1 -1
- package/dist/cli/runner.js +129 -8
- package/dist/cli/runner.js.map +1 -1
- package/dist/cli/templates.d.ts +1 -1
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +1 -1
- package/dist/compiler/evaluator.d.ts.map +1 -1
- package/dist/compiler/evaluator.js +6 -0
- package/dist/compiler/evaluator.js.map +1 -1
- package/dist/core/events.d.ts +2 -0
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/events.js +33 -4
- package/dist/core/events.js.map +1 -1
- package/dist/core/history.d.ts +14 -0
- package/dist/core/history.d.ts.map +1 -1
- package/dist/core/history.js +73 -13
- package/dist/core/history.js.map +1 -1
- package/dist/core/protect.d.ts +4 -0
- package/dist/core/protect.d.ts.map +1 -1
- package/dist/core/protect.js +9 -1
- package/dist/core/protect.js.map +1 -1
- package/dist/core/validator.d.ts +4 -0
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +119 -97
- package/dist/core/validator.js.map +1 -1
- package/dist/core/veto.d.ts +35 -1
- package/dist/core/veto.d.ts.map +1 -1
- package/dist/core/veto.js +106 -6
- package/dist/core/veto.js.map +1 -1
- package/dist/custom/types.d.ts +1 -1
- package/dist/custom/types.d.ts.map +1 -1
- package/dist/deterministic/regex-safety.d.ts.map +1 -1
- package/dist/deterministic/regex-safety.js +42 -1
- package/dist/deterministic/regex-safety.js.map +1 -1
- package/dist/extractors/content.d.ts.map +1 -1
- package/dist/extractors/content.js +40 -23
- package/dist/extractors/content.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/langchain/middleware.d.ts +1 -1
- package/dist/integrations/langchain/middleware.js +1 -1
- package/dist/integrations/vercel-ai/middleware.d.ts +1 -1
- package/dist/integrations/vercel-ai/middleware.js +1 -1
- package/dist/observability/otel.d.ts +29 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +43 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/policy/generator.d.ts.map +1 -1
- package/dist/policy/generator.js +4 -2
- package/dist/policy/generator.js.map +1 -1
- package/dist/proxy/anthropic-interceptor.d.ts +51 -0
- package/dist/proxy/anthropic-interceptor.d.ts.map +1 -0
- package/dist/proxy/anthropic-interceptor.js +132 -0
- package/dist/proxy/anthropic-interceptor.js.map +1 -0
- package/dist/proxy/interceptor.d.ts +55 -0
- package/dist/proxy/interceptor.d.ts.map +1 -0
- package/dist/proxy/interceptor.js +111 -0
- package/dist/proxy/interceptor.js.map +1 -0
- package/dist/proxy/server.d.ts +21 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/server.js +545 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/proxy/types.d.ts +18 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +7 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/rate-limiting/evaluator.d.ts +17 -0
- package/dist/rate-limiting/evaluator.d.ts.map +1 -0
- package/dist/rate-limiting/evaluator.js +48 -0
- package/dist/rate-limiting/evaluator.js.map +1 -0
- package/dist/rate-limiting/redis-store.d.ts +36 -0
- package/dist/rate-limiting/redis-store.d.ts.map +1 -0
- package/dist/rate-limiting/redis-store.js +68 -0
- package/dist/rate-limiting/redis-store.js.map +1 -0
- package/dist/rate-limiting/store.d.ts +8 -0
- package/dist/rate-limiting/store.d.ts.map +1 -0
- package/dist/rate-limiting/store.js +60 -0
- package/dist/rate-limiting/store.js.map +1 -0
- package/dist/rate-limiting/types.d.ts +9 -0
- package/dist/rate-limiting/types.d.ts.map +1 -0
- package/dist/rate-limiting/types.js +2 -0
- package/dist/rate-limiting/types.js.map +1 -0
- package/dist/rules/condition-evaluator.d.ts +6 -0
- package/dist/rules/condition-evaluator.d.ts.map +1 -1
- package/dist/rules/condition-evaluator.js +60 -18
- package/dist/rules/condition-evaluator.js.map +1 -1
- package/dist/rules/expression-validator.d.ts.map +1 -1
- package/dist/rules/expression-validator.js +5 -0
- package/dist/rules/expression-validator.js.map +1 -1
- package/dist/rules/loader.d.ts.map +1 -1
- package/dist/rules/loader.js +2 -0
- package/dist/rules/loader.js.map +1 -1
- package/dist/rules/local-evaluator.d.ts +11 -15
- package/dist/rules/local-evaluator.d.ts.map +1 -1
- package/dist/rules/local-evaluator.js +62 -29
- package/dist/rules/local-evaluator.js.map +1 -1
- package/dist/rules/policy-ir-schema.d.ts +43 -1
- package/dist/rules/policy-ir-schema.d.ts.map +1 -1
- package/dist/rules/policy-ir-schema.js +27 -1
- package/dist/rules/policy-ir-schema.js.map +1 -1
- package/dist/rules/types.d.ts +20 -1
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/testing/runner.d.ts +21 -0
- package/dist/testing/runner.d.ts.map +1 -0
- package/dist/testing/runner.js +239 -0
- package/dist/testing/runner.js.map +1 -0
- package/dist/testing/types.d.ts +39 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing/types.js +7 -0
- package/dist/testing/types.js.map +1 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/package.json +30 -17
package/README.md
CHANGED
|
@@ -3,21 +3,19 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/veto-sdk)
|
|
4
4
|
[](../../LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
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
|
|
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**
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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"
|
|
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:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
163
|
-
npx veto-cli
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
948
|
+
---
|
|
169
949
|
|
|
170
|
-
## Rule YAML
|
|
950
|
+
## Rule YAML Reference
|
|
951
|
+
|
|
952
|
+
### Input rules
|
|
171
953
|
|
|
172
954
|
```yaml
|
|
173
955
|
rules:
|
|
174
|
-
- id: unique-rule-id
|
|
175
|
-
name: Human readable name
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
#
|
|
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
|
-
#
|
|
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
|
|
188
|
-
operator: greater_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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
1051
|
+
### Time window conditions
|
|
196
1052
|
|
|
197
|
-
|
|
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
|
-
|
|
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
|
|
1080
|
+
Apache-2.0 (c) [Plaw, Inc.](https://plaw.io)
|