veto-sdk 2.0.0 → 2.2.1
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/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js.map +1 -1
- 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 +20 -1
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/browser/veto.d.ts +10 -0
- package/dist/browser/veto.d.ts.map +1 -1
- package/dist/browser/veto.js +77 -4
- package/dist/browser/veto.js.map +1 -1
- package/dist/cloud/types.d.ts +17 -2
- package/dist/cloud/types.d.ts.map +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 +13 -1
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/events.js +37 -4
- package/dist/core/events.js.map +1 -1
- package/dist/core/protect.d.ts +7 -1
- package/dist/core/protect.d.ts.map +1 -1
- package/dist/core/protect.js +23 -5
- package/dist/core/protect.js.map +1 -1
- package/dist/core/veto.d.ts +43 -1
- package/dist/core/veto.d.ts.map +1 -1
- package/dist/core/veto.js +256 -13
- package/dist/core/veto.js.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/deterministic/types.d.ts +103 -0
- package/dist/deterministic/types.d.ts.map +1 -1
- package/dist/economic/budget-engine.d.ts +29 -0
- package/dist/economic/budget-engine.d.ts.map +1 -0
- package/dist/economic/budget-engine.js +146 -0
- package/dist/economic/budget-engine.js.map +1 -0
- package/dist/economic/connectors/ap2.d.ts +51 -0
- package/dist/economic/connectors/ap2.d.ts.map +1 -0
- package/dist/economic/connectors/ap2.js +133 -0
- package/dist/economic/connectors/ap2.js.map +1 -0
- package/dist/economic/connectors/index.d.ts +8 -0
- package/dist/economic/connectors/index.d.ts.map +1 -0
- package/dist/economic/connectors/index.js +8 -0
- package/dist/economic/connectors/index.js.map +1 -0
- package/dist/economic/connectors/mpp.d.ts +41 -0
- package/dist/economic/connectors/mpp.d.ts.map +1 -0
- package/dist/economic/connectors/mpp.js +97 -0
- package/dist/economic/connectors/mpp.js.map +1 -0
- package/dist/economic/connectors/x402.d.ts +20 -0
- package/dist/economic/connectors/x402.d.ts.map +1 -0
- package/dist/economic/connectors/x402.js +142 -0
- package/dist/economic/connectors/x402.js.map +1 -0
- package/dist/economic/evaluator.d.ts +77 -0
- package/dist/economic/evaluator.d.ts.map +1 -0
- package/dist/economic/evaluator.js +231 -0
- package/dist/economic/evaluator.js.map +1 -0
- package/dist/economic/index.d.ts +13 -0
- package/dist/economic/index.d.ts.map +1 -0
- package/dist/economic/index.js +15 -0
- package/dist/economic/index.js.map +1 -0
- package/dist/economic/types.d.ts +188 -0
- package/dist/economic/types.d.ts.map +1 -0
- package/dist/economic/types.js +10 -0
- package/dist/economic/types.js.map +1 -0
- package/dist/extractors/content.d.ts +42 -0
- package/dist/extractors/content.d.ts.map +1 -0
- package/dist/extractors/content.js +171 -0
- package/dist/extractors/content.js.map +1 -0
- package/dist/extractors/index.d.ts +7 -0
- package/dist/extractors/index.d.ts.map +1 -0
- package/dist/extractors/index.js +7 -0
- package/dist/extractors/index.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/policy/generator.d.ts +110 -0
- package/dist/policy/generator.d.ts.map +1 -0
- package/dist/policy/generator.js +465 -0
- package/dist/policy/generator.js.map +1 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +7 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/providers/adapters.d.ts +27 -0
- package/dist/providers/adapters.d.ts.map +1 -1
- package/dist/providers/adapters.js +58 -0
- package/dist/providers/adapters.js.map +1 -1
- 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/index.d.ts +1 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +1 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/local-evaluator.d.ts +65 -0
- package/dist/rules/local-evaluator.d.ts.map +1 -0
- package/dist/rules/local-evaluator.js +250 -0
- package/dist/rules/local-evaluator.js.map +1 -0
- package/dist/rules/policy-ir-schema.d.ts +109 -0
- package/dist/rules/policy-ir-schema.d.ts.map +1 -1
- package/dist/rules/policy-ir-schema.js +90 -0
- package/dist/rules/policy-ir-schema.js.map +1 -1
- package/dist/rules/policy-packs.d.ts.map +1 -1
- package/dist/rules/policy-packs.js +1 -0
- package/dist/rules/policy-packs.js.map +1 -1
- package/dist/types/config.d.ts +2 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/dist/utils/logger.d.ts +38 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +231 -26
- package/dist/utils/logger.js.map +1 -1
- package/package.json +27 -12
- package/packs/economic-agent.yaml +62 -0
package/dist/core/veto.js
CHANGED
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module core/veto
|
|
8
8
|
*/
|
|
9
|
-
import { createLogger } from '../utils/logger.js';
|
|
9
|
+
import { createLogger, } from '../utils/logger.js';
|
|
10
10
|
import { generateId, generateToolCallId } from '../utils/id.js';
|
|
11
11
|
import { ValidationEngine } from './validator.js';
|
|
12
12
|
import { HistoryTracker } from './history.js';
|
|
13
13
|
import { BudgetTracker, BudgetExceededError } from './budget.js';
|
|
14
14
|
import { Interceptor, ToolCallDeniedError } from './interceptor.js';
|
|
15
|
+
import { EconomicEvaluator } from '../economic/evaluator.js';
|
|
16
|
+
import { LocalBudgetEngine } from '../economic/budget-engine.js';
|
|
15
17
|
import { compile, evaluate } from '../compiler/index.js';
|
|
16
18
|
import { evaluateConditionCollections } from '../rules/condition-evaluator.js';
|
|
17
19
|
import { VetoCloudClient, ApprovalTimeoutError } from '../cloud/client.js';
|
|
@@ -52,6 +54,8 @@ export class Veto {
|
|
|
52
54
|
validationEngine;
|
|
53
55
|
historyTracker;
|
|
54
56
|
budgetTracker;
|
|
57
|
+
economicEvaluator;
|
|
58
|
+
economicBudgetEngine;
|
|
55
59
|
interceptor;
|
|
56
60
|
outputValidator;
|
|
57
61
|
eventWebhookEmitter;
|
|
@@ -82,6 +86,7 @@ export class Veto {
|
|
|
82
86
|
approvalPollOptions;
|
|
83
87
|
localApprovalConfig;
|
|
84
88
|
onApprovalRequired;
|
|
89
|
+
onDecisionMade;
|
|
85
90
|
// Approval preference cache: tool name -> 'approve_all' | 'deny_all'
|
|
86
91
|
approvalPreferences = new Map();
|
|
87
92
|
// Client-side deterministic validation cache
|
|
@@ -107,10 +112,18 @@ export class Veto {
|
|
|
107
112
|
? undefined
|
|
108
113
|
: Veto.parseEnvMode(process.env.VETO_MODE);
|
|
109
114
|
this.mode = options.mode ?? config.mode ?? envMode ?? 'strict';
|
|
115
|
+
const envLogSetting = this.browserMode
|
|
116
|
+
? undefined
|
|
117
|
+
: Veto.parseEnvLogSetting(process.env.VETO_LOG);
|
|
110
118
|
const envLogLevel = this.browserMode
|
|
111
119
|
? undefined
|
|
112
120
|
: Veto.parseEnvLogLevel(process.env.VETO_LOG_LEVEL);
|
|
113
|
-
|
|
121
|
+
const explicitLogLevel = Veto.resolveExplicitLogLevel(options);
|
|
122
|
+
this.resolvedLogLevel = explicitLogLevel
|
|
123
|
+
?? envLogSetting?.level
|
|
124
|
+
?? envLogLevel
|
|
125
|
+
?? config.logging?.level
|
|
126
|
+
?? 'info';
|
|
114
127
|
const explicitValidationMode = config.validation?.mode;
|
|
115
128
|
const cloudApiKey = options.apiKey
|
|
116
129
|
?? config.cloud?.apiKey
|
|
@@ -238,8 +251,9 @@ export class Veto {
|
|
|
238
251
|
reasonField: config.approval?.responseSchema?.reasonField ?? 'reason',
|
|
239
252
|
},
|
|
240
253
|
};
|
|
241
|
-
//
|
|
254
|
+
// Hooks
|
|
242
255
|
this.onApprovalRequired = options.onApprovalRequired;
|
|
256
|
+
this.onDecisionMade = options.onDecisionMade;
|
|
243
257
|
this.eventWebhookEmitter = new EventWebhookEmitter(resolveEventWebhookConfig(config.events?.webhook, this.logger), this.logger);
|
|
244
258
|
// Resolve tracking options
|
|
245
259
|
const envSessionId = this.browserMode ? undefined : process.env.VETO_SESSION_ID;
|
|
@@ -327,6 +341,28 @@ export class Veto {
|
|
|
327
341
|
else {
|
|
328
342
|
this.budgetTracker = null;
|
|
329
343
|
}
|
|
344
|
+
// Initialize economic evaluator (if configured)
|
|
345
|
+
if (config.economic?.budgets?.length) {
|
|
346
|
+
const localBudgetEngine = new LocalBudgetEngine({
|
|
347
|
+
budgets: config.economic.budgets,
|
|
348
|
+
logger: this.logger,
|
|
349
|
+
});
|
|
350
|
+
this.economicBudgetEngine = localBudgetEngine;
|
|
351
|
+
this.economicEvaluator = new EconomicEvaluator({
|
|
352
|
+
policy: config.economic,
|
|
353
|
+
budgetEngine: localBudgetEngine,
|
|
354
|
+
logger: this.logger,
|
|
355
|
+
});
|
|
356
|
+
this.logger.info('Economic authorization enabled', {
|
|
357
|
+
budgets: config.economic.budgets.length,
|
|
358
|
+
protocols: ['x402', 'mpp', 'ap2'],
|
|
359
|
+
payer_required: config.economic.payer?.required ?? false,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
this.economicEvaluator = null;
|
|
364
|
+
this.economicBudgetEngine = null;
|
|
365
|
+
}
|
|
330
366
|
// Initialize interceptor
|
|
331
367
|
this.outputValidator = new OutputValidator({
|
|
332
368
|
logger: this.logger,
|
|
@@ -375,17 +411,28 @@ export class Veto {
|
|
|
375
411
|
const parseYaml = await Veto.loadYamlParser();
|
|
376
412
|
const configDir = pathModule.resolve(options.configDir ?? './veto');
|
|
377
413
|
// Determine log level
|
|
378
|
-
const
|
|
379
|
-
|
|
414
|
+
const envLogSetting = Veto.parseEnvLogSetting(process.env.VETO_LOG);
|
|
415
|
+
const envLogLevel = Veto.parseEnvLogLevel(process.env.VETO_LOG_LEVEL);
|
|
416
|
+
const explicitLogLevel = Veto.resolveExplicitLogLevel(options);
|
|
417
|
+
let logLevel = explicitLogLevel ?? envLogSetting?.level ?? envLogLevel ?? 'info';
|
|
418
|
+
let streamMode = options.streamMode ?? envLogSetting?.streamMode ?? 'compact';
|
|
380
419
|
// Load config file
|
|
381
420
|
const configPath = pathModule.join(configDir, 'veto.config.yaml');
|
|
382
421
|
let config = {};
|
|
383
422
|
if (fsModule.existsSync(configPath)) {
|
|
384
423
|
const configContent = fsModule.readFileSync(configPath, 'utf-8');
|
|
385
424
|
config = parseYaml(configContent);
|
|
386
|
-
logLevel =
|
|
387
|
-
|
|
388
|
-
|
|
425
|
+
logLevel = explicitLogLevel
|
|
426
|
+
?? envLogSetting?.level
|
|
427
|
+
?? envLogLevel
|
|
428
|
+
?? config.logging?.level
|
|
429
|
+
?? 'info';
|
|
430
|
+
streamMode = options.streamMode
|
|
431
|
+
?? envLogSetting?.streamMode
|
|
432
|
+
?? config.logging?.streamMode
|
|
433
|
+
?? 'compact';
|
|
434
|
+
}
|
|
435
|
+
const logger = createLogger(logLevel, streamMode);
|
|
389
436
|
if (!fsModule.existsSync(configPath)) {
|
|
390
437
|
logger.warn('Veto config not found. Run "npx veto init" to initialize.', {
|
|
391
438
|
expected: configPath,
|
|
@@ -402,8 +449,13 @@ export class Veto {
|
|
|
402
449
|
});
|
|
403
450
|
}
|
|
404
451
|
static fromRules(options) {
|
|
405
|
-
const
|
|
406
|
-
const
|
|
452
|
+
const envLogSetting = Veto.parseEnvLogSetting(typeof process === 'undefined' ? undefined : process.env.VETO_LOG);
|
|
453
|
+
const logLevel = Veto.resolveExplicitLogLevel(options)
|
|
454
|
+
?? envLogSetting?.level
|
|
455
|
+
?? Veto.parseEnvLogLevel(typeof process === 'undefined' ? undefined : process.env.VETO_LOG_LEVEL)
|
|
456
|
+
?? 'warn';
|
|
457
|
+
const streamMode = options.streamMode ?? envLogSetting?.streamMode ?? 'compact';
|
|
458
|
+
const logger = createLogger(logLevel, streamMode);
|
|
407
459
|
const envMode = Veto.parseEnvMode(typeof process === 'undefined' ? undefined : process.env.VETO_MODE);
|
|
408
460
|
const resolvedMode = options.mode ?? envMode;
|
|
409
461
|
const cloudClient = options.cloudClient ?? (options.apiKey
|
|
@@ -424,7 +476,7 @@ export class Veto {
|
|
|
424
476
|
baseUrl: options.endpoint,
|
|
425
477
|
}
|
|
426
478
|
: undefined,
|
|
427
|
-
logging: { level: logLevel },
|
|
479
|
+
logging: { level: logLevel, streamMode },
|
|
428
480
|
budget: options.budget,
|
|
429
481
|
costs: options.costs,
|
|
430
482
|
approval: options.approval,
|
|
@@ -434,6 +486,7 @@ export class Veto {
|
|
|
434
486
|
const vetoOptions = {
|
|
435
487
|
mode: resolvedMode,
|
|
436
488
|
logLevel,
|
|
489
|
+
streamMode,
|
|
437
490
|
sessionId: options.sessionId,
|
|
438
491
|
agentId: options.agentId,
|
|
439
492
|
userId: options.userId,
|
|
@@ -443,11 +496,17 @@ export class Veto {
|
|
|
443
496
|
endpoint: undefined,
|
|
444
497
|
cloudClient,
|
|
445
498
|
onApprovalRequired: options.onApprovalRequired,
|
|
499
|
+
onDecisionMade: options.onDecisionMade,
|
|
446
500
|
};
|
|
447
501
|
return new Veto(vetoOptions, config, rules, logger, true);
|
|
448
502
|
}
|
|
449
503
|
static async fromCloud(options) {
|
|
450
|
-
const
|
|
504
|
+
const envLogSetting = Veto.parseEnvLogSetting(typeof process === 'undefined' ? undefined : process.env.VETO_LOG);
|
|
505
|
+
const logLevel = envLogSetting?.level
|
|
506
|
+
?? Veto.parseEnvLogLevel(typeof process === 'undefined' ? undefined : process.env.VETO_LOG_LEVEL)
|
|
507
|
+
?? 'warn';
|
|
508
|
+
const streamMode = envLogSetting?.streamMode ?? 'compact';
|
|
509
|
+
const logger = createLogger(logLevel, streamMode);
|
|
451
510
|
const cloudClient = new VetoCloudClient({
|
|
452
511
|
config: {
|
|
453
512
|
apiKey: options.apiKey,
|
|
@@ -472,9 +531,29 @@ export class Veto {
|
|
|
472
531
|
}
|
|
473
532
|
return undefined;
|
|
474
533
|
}
|
|
534
|
+
static parseEnvLogSetting(value) {
|
|
535
|
+
if (!value) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
const normalized = value.trim().toLowerCase();
|
|
539
|
+
if (normalized === 'stream') {
|
|
540
|
+
return { level: 'stream', streamMode: 'compact' };
|
|
541
|
+
}
|
|
542
|
+
if (normalized === 'stream:verbose') {
|
|
543
|
+
return { level: 'stream', streamMode: 'verbose' };
|
|
544
|
+
}
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
static resolveExplicitLogLevel(options) {
|
|
548
|
+
if (options.stream) {
|
|
549
|
+
return 'stream';
|
|
550
|
+
}
|
|
551
|
+
return options.logLevel;
|
|
552
|
+
}
|
|
475
553
|
static parseEnvLogLevel(level) {
|
|
476
554
|
if (level === 'debug'
|
|
477
555
|
|| level === 'info'
|
|
556
|
+
|| level === 'stream'
|
|
478
557
|
|| level === 'warn'
|
|
479
558
|
|| level === 'error'
|
|
480
559
|
|| level === 'silent') {
|
|
@@ -1149,6 +1228,11 @@ export class Veto {
|
|
|
1149
1228
|
if (!ast) {
|
|
1150
1229
|
try {
|
|
1151
1230
|
ast = compile(expression);
|
|
1231
|
+
if (this.compiledExpressionCache.size >= 10_000) {
|
|
1232
|
+
const firstKey = this.compiledExpressionCache.keys().next().value;
|
|
1233
|
+
if (firstKey !== undefined)
|
|
1234
|
+
this.compiledExpressionCache.delete(firstKey);
|
|
1235
|
+
}
|
|
1152
1236
|
this.compiledExpressionCache.set(expression, ast);
|
|
1153
1237
|
}
|
|
1154
1238
|
catch (error) {
|
|
@@ -2026,6 +2110,61 @@ export class Veto {
|
|
|
2026
2110
|
};
|
|
2027
2111
|
this.eventWebhookEmitter.emit(event);
|
|
2028
2112
|
}
|
|
2113
|
+
notifyDecisionMade(result, toolName) {
|
|
2114
|
+
try {
|
|
2115
|
+
const maybePromise = this.onDecisionMade?.({ ...result, toolName });
|
|
2116
|
+
if (maybePromise && typeof maybePromise.catch === 'function') {
|
|
2117
|
+
maybePromise.catch(() => { });
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
catch {
|
|
2121
|
+
// swallow — callback errors must not break guard flow
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Emit a webhook event for economic authorization outcomes.
|
|
2126
|
+
*
|
|
2127
|
+
* Maps economic evaluation results to the appropriate webhook event type:
|
|
2128
|
+
* - budget_exceeded → 'budget_exceeded'
|
|
2129
|
+
* - approval_required → 'approval_triggered'
|
|
2130
|
+
* - budget_warning (>80% utilization on allow) → 'budget_warning'
|
|
2131
|
+
* - spend_committed (successful reservation) → 'spend_committed'
|
|
2132
|
+
*/
|
|
2133
|
+
emitEconomicEvent(toolName, args, econResult, economicContext, forceType) {
|
|
2134
|
+
let eventType;
|
|
2135
|
+
if (forceType) {
|
|
2136
|
+
eventType = forceType;
|
|
2137
|
+
}
|
|
2138
|
+
else if (econResult.denial?.reason === 'budget_exceeded') {
|
|
2139
|
+
eventType = 'budget_exceeded';
|
|
2140
|
+
}
|
|
2141
|
+
else if (econResult.denial?.reason === 'approval_required') {
|
|
2142
|
+
eventType = 'approval_triggered';
|
|
2143
|
+
}
|
|
2144
|
+
else {
|
|
2145
|
+
eventType = 'deny';
|
|
2146
|
+
}
|
|
2147
|
+
const event = {
|
|
2148
|
+
eventType,
|
|
2149
|
+
toolName,
|
|
2150
|
+
arguments: args,
|
|
2151
|
+
decision: econResult.decision,
|
|
2152
|
+
reason: econResult.denial?.reason,
|
|
2153
|
+
severity: eventType === 'budget_exceeded' ? 'high' : 'medium',
|
|
2154
|
+
timestamp: new Date().toISOString(),
|
|
2155
|
+
shadow: this.mode === 'shadow' ? true : undefined,
|
|
2156
|
+
economic: {
|
|
2157
|
+
cost: economicContext.cost,
|
|
2158
|
+
currency: economicContext.currency,
|
|
2159
|
+
protocol: economicContext.protocol,
|
|
2160
|
+
payer: economicContext.payer,
|
|
2161
|
+
budget_spent: econResult.denial?.budget_spent,
|
|
2162
|
+
budget_limit: econResult.denial?.budget_limit,
|
|
2163
|
+
budget_remaining: econResult.denial?.budget_remaining,
|
|
2164
|
+
},
|
|
2165
|
+
};
|
|
2166
|
+
this.eventWebhookEmitter.emit(event);
|
|
2167
|
+
}
|
|
2029
2168
|
toGuardResult(result) {
|
|
2030
2169
|
const metadata = result.metadata;
|
|
2031
2170
|
const ruleId = this.extractMetadataString(metadata, ['ruleId', 'rule_id']);
|
|
@@ -2376,6 +2515,67 @@ export class Veto {
|
|
|
2376
2515
|
* Unlike interceptor execution, this returns raw validation outcomes in log/shadow mode.
|
|
2377
2516
|
*/
|
|
2378
2517
|
async guard(toolName, args, context = {}) {
|
|
2518
|
+
// Economic pre-checks: payer validation and cost validation run BEFORE
|
|
2519
|
+
// behavioral rules. Budget reservation happens AFTER behavioral rules
|
|
2520
|
+
// to avoid the TOCTOU double-spend (check then reserve race).
|
|
2521
|
+
if (this.economicEvaluator && context.economic) {
|
|
2522
|
+
const econResult = this.economicEvaluator.evaluate(context.economic);
|
|
2523
|
+
if (econResult.decision !== 'allow') {
|
|
2524
|
+
this.logger.warn('Economic authorization denied', {
|
|
2525
|
+
toolName,
|
|
2526
|
+
decision: econResult.decision,
|
|
2527
|
+
reason: econResult.denial?.reason,
|
|
2528
|
+
cost: context.economic.cost,
|
|
2529
|
+
protocol: context.economic.protocol,
|
|
2530
|
+
});
|
|
2531
|
+
this.emitEconomicEvent(toolName, args, econResult, context.economic);
|
|
2532
|
+
const result = {
|
|
2533
|
+
decision: this.mode === 'shadow' ? 'allow' : econResult.decision,
|
|
2534
|
+
reason: econResult.denial
|
|
2535
|
+
? `Economic: ${econResult.denial.reason}`
|
|
2536
|
+
: 'Economic authorization denied',
|
|
2537
|
+
economicDenial: econResult.denial,
|
|
2538
|
+
shadow: this.mode === 'shadow' ? true : undefined,
|
|
2539
|
+
shadowDecision: this.mode === 'shadow' ? econResult.decision : undefined,
|
|
2540
|
+
};
|
|
2541
|
+
this.notifyDecisionMade(result, toolName);
|
|
2542
|
+
return result;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
// If economic evaluator is configured and can resolve cost from args
|
|
2546
|
+
// (no explicit EconomicContext provided, but cost_extraction is configured)
|
|
2547
|
+
let implicitEconomicContext;
|
|
2548
|
+
if (this.economicEvaluator && !context.economic) {
|
|
2549
|
+
const resolvedCost = this.economicEvaluator.resolveCost(toolName, args);
|
|
2550
|
+
if (resolvedCost !== undefined && resolvedCost > 0) {
|
|
2551
|
+
implicitEconomicContext = {
|
|
2552
|
+
cost: resolvedCost,
|
|
2553
|
+
currency: 'USD', // Default currency for implicit extraction
|
|
2554
|
+
protocol: 'custom',
|
|
2555
|
+
};
|
|
2556
|
+
const econResult = this.economicEvaluator.evaluate(implicitEconomicContext);
|
|
2557
|
+
if (econResult.decision !== 'allow') {
|
|
2558
|
+
this.logger.warn('Economic authorization denied (implicit cost)', {
|
|
2559
|
+
toolName,
|
|
2560
|
+
decision: econResult.decision,
|
|
2561
|
+
reason: econResult.denial?.reason,
|
|
2562
|
+
cost: resolvedCost,
|
|
2563
|
+
});
|
|
2564
|
+
this.emitEconomicEvent(toolName, args, econResult, implicitEconomicContext);
|
|
2565
|
+
const result = {
|
|
2566
|
+
decision: this.mode === 'shadow' ? 'allow' : econResult.decision,
|
|
2567
|
+
reason: econResult.denial
|
|
2568
|
+
? `Economic: ${econResult.denial.reason}`
|
|
2569
|
+
: 'Economic authorization denied',
|
|
2570
|
+
economicDenial: econResult.denial,
|
|
2571
|
+
shadow: this.mode === 'shadow' ? true : undefined,
|
|
2572
|
+
shadowDecision: this.mode === 'shadow' ? econResult.decision : undefined,
|
|
2573
|
+
};
|
|
2574
|
+
this.notifyDecisionMade(result, toolName);
|
|
2575
|
+
return result;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2379
2579
|
const customContext = {
|
|
2380
2580
|
...(context.custom ?? {}),
|
|
2381
2581
|
};
|
|
@@ -2415,7 +2615,34 @@ export class Veto {
|
|
|
2415
2615
|
this.historyTracker.record(toolName, args, validationResult, aggregatedResult.totalDurationMs);
|
|
2416
2616
|
this.emitDecisionEvent(validationContext, validationResult);
|
|
2417
2617
|
this.logClientDecision(validationContext, validationResult, aggregatedResult.totalDurationMs);
|
|
2418
|
-
|
|
2618
|
+
// If behavioral rules allow and economic context exists, reserve budget.
|
|
2619
|
+
// Skip reservation in shadow mode — shadow should never deduct real budget.
|
|
2620
|
+
const behavioralResult = this.toGuardResult(validationResult);
|
|
2621
|
+
const effectiveEconomic = context.economic ?? implicitEconomicContext;
|
|
2622
|
+
if (behavioralResult.decision === 'allow'
|
|
2623
|
+
&& this.economicEvaluator
|
|
2624
|
+
&& effectiveEconomic
|
|
2625
|
+
&& effectiveEconomic.cost > 0
|
|
2626
|
+
&& this.mode !== 'shadow') {
|
|
2627
|
+
const reserveResult = this.economicEvaluator.reserveBudget(effectiveEconomic.cost, effectiveEconomic.currency);
|
|
2628
|
+
if (reserveResult.decision !== 'allow') {
|
|
2629
|
+
this.emitEconomicEvent(toolName, args, reserveResult, effectiveEconomic);
|
|
2630
|
+
const result = {
|
|
2631
|
+
...behavioralResult,
|
|
2632
|
+
decision: reserveResult.decision,
|
|
2633
|
+
reason: reserveResult.denial
|
|
2634
|
+
? `Economic: ${reserveResult.denial.reason}`
|
|
2635
|
+
: 'Budget reservation failed',
|
|
2636
|
+
economicDenial: reserveResult.denial,
|
|
2637
|
+
};
|
|
2638
|
+
this.notifyDecisionMade(result, toolName);
|
|
2639
|
+
return result;
|
|
2640
|
+
}
|
|
2641
|
+
// Emit spend_committed event on successful reservation
|
|
2642
|
+
this.emitEconomicEvent(toolName, args, reserveResult, effectiveEconomic, 'spend_committed');
|
|
2643
|
+
}
|
|
2644
|
+
this.notifyDecisionMade(behavioralResult, toolName);
|
|
2645
|
+
return behavioralResult;
|
|
2419
2646
|
}
|
|
2420
2647
|
/**
|
|
2421
2648
|
* Cache an approval preference for a tool.
|
|
@@ -2512,6 +2739,9 @@ export class Veto {
|
|
|
2512
2739
|
});
|
|
2513
2740
|
});
|
|
2514
2741
|
}, refreshIntervalMs);
|
|
2742
|
+
if (typeof this.refreshIntervalId === 'object' && this.refreshIntervalId !== null && 'unref' in this.refreshIntervalId) {
|
|
2743
|
+
this.refreshIntervalId.unref();
|
|
2744
|
+
}
|
|
2515
2745
|
}
|
|
2516
2746
|
/**
|
|
2517
2747
|
* Get history statistics.
|
|
@@ -2537,6 +2767,19 @@ export class Veto {
|
|
|
2537
2767
|
resetBudget() {
|
|
2538
2768
|
this.budgetTracker?.reset();
|
|
2539
2769
|
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Get current economic budget status for a scope.
|
|
2772
|
+
* Returns null if no economic policy is configured.
|
|
2773
|
+
*/
|
|
2774
|
+
getEconomicBudgetStatus(scope = 'session') {
|
|
2775
|
+
return this.economicBudgetEngine?.getStatus(scope) ?? null;
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Reset economic budget for a scope.
|
|
2779
|
+
*/
|
|
2780
|
+
resetEconomicBudget(scope = 'session') {
|
|
2781
|
+
this.economicBudgetEngine?.reset(scope);
|
|
2782
|
+
}
|
|
2540
2783
|
dispose() {
|
|
2541
2784
|
if (this.refreshIntervalId) {
|
|
2542
2785
|
clearInterval(this.refreshIntervalId);
|