@agentguard-run/spend 0.5.0 → 0.6.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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/license.d.ts +5 -1
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +19 -3
- package/dist/license.js.map +1 -1
- package/dist/middleware/provenance.d.ts +11 -0
- package/dist/middleware/provenance.d.ts.map +1 -0
- package/dist/middleware/provenance.js +17 -0
- package/dist/middleware/provenance.js.map +1 -0
- package/dist/middleware/provider-registry.d.ts +36 -0
- package/dist/middleware/provider-registry.d.ts.map +1 -0
- package/dist/middleware/provider-registry.js +292 -0
- package/dist/middleware/provider-registry.js.map +1 -0
- package/dist/openrouter/key-fetch.d.ts +28 -0
- package/dist/openrouter/key-fetch.d.ts.map +1 -0
- package/dist/openrouter/key-fetch.js +146 -0
- package/dist/openrouter/key-fetch.js.map +1 -0
- package/dist/posture/enforce.d.ts +19 -0
- package/dist/posture/enforce.d.ts.map +1 -0
- package/dist/posture/enforce.js +109 -0
- package/dist/posture/enforce.js.map +1 -0
- package/dist/receipts/schema.d.ts +55 -0
- package/dist/receipts/schema.d.ts.map +1 -0
- package/dist/receipts/schema.js +23 -0
- package/dist/receipts/schema.js.map +1 -0
- package/dist/spend-guard.d.ts +34 -0
- package/dist/spend-guard.d.ts.map +1 -1
- package/dist/spend-guard.js +69 -2
- package/dist/spend-guard.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow/receipt.d.ts +20 -2
- package/dist/workflow/receipt.d.ts.map +1 -1
- package/dist/workflow/receipt.js +16 -0
- package/dist/workflow/receipt.js.map +1 -1
- package/dist/workflow/types.d.ts +5 -0
- package/dist/workflow/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/middleware/provenance.ts +31 -0
- package/src/middleware/provider-registry.ts +293 -0
- package/src/posture/enforce.ts +87 -0
- package/src/receipts/schema.ts +91 -0
- package/src/workflow/receipt.ts +36 -2
- package/src/workflow/types.ts +7 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComplianceProvenance,
|
|
3
|
+
HostingProvenance,
|
|
4
|
+
JurisdictionCountry,
|
|
5
|
+
ModelIdentity,
|
|
6
|
+
ProvenanceProvider,
|
|
7
|
+
WeightsOriginCountry,
|
|
8
|
+
} from '../receipts/schema';
|
|
9
|
+
|
|
10
|
+
export interface ProviderRouteInfo {
|
|
11
|
+
provider: ProvenanceProvider;
|
|
12
|
+
weights_origin_country: WeightsOriginCountry;
|
|
13
|
+
available_jurisdictions: string[];
|
|
14
|
+
baa_path: string | null;
|
|
15
|
+
hipaa_eligible: boolean;
|
|
16
|
+
zdr_available: boolean;
|
|
17
|
+
default_retention_days: number | null;
|
|
18
|
+
foreign_origin?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ProviderRegistryRequest {
|
|
22
|
+
url?: string;
|
|
23
|
+
provider?: string;
|
|
24
|
+
providerRoute?: string;
|
|
25
|
+
model?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ProviderRegistryConfig {
|
|
29
|
+
providerRoute?: string;
|
|
30
|
+
jurisdictionCountry?: JurisdictionCountry;
|
|
31
|
+
jurisdictionRegion?: string;
|
|
32
|
+
baaInForce?: boolean;
|
|
33
|
+
baaVendor?: string | null;
|
|
34
|
+
dataRetentionDays?: number | null;
|
|
35
|
+
dataResidencyAttested?: boolean;
|
|
36
|
+
foreignOriginConsentReceiptId?: string | null;
|
|
37
|
+
inferenceBilling?: 'agentguard_managed' | 'customer_managed';
|
|
38
|
+
inferenceBillingDetail?: ComplianceProvenance['inference_billing_detail'];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const PROVIDER_REGISTRY: Record<string, ProviderRouteInfo> = {
|
|
42
|
+
'anthropic-direct': {
|
|
43
|
+
provider: 'anthropic',
|
|
44
|
+
weights_origin_country: 'US',
|
|
45
|
+
available_jurisdictions: ['US'],
|
|
46
|
+
baa_path: 'Anthropic Enterprise Messages API with BAA eligible routing',
|
|
47
|
+
hipaa_eligible: true,
|
|
48
|
+
zdr_available: true,
|
|
49
|
+
default_retention_days: 0,
|
|
50
|
+
},
|
|
51
|
+
'aws-bedrock': {
|
|
52
|
+
provider: 'anthropic',
|
|
53
|
+
weights_origin_country: 'US',
|
|
54
|
+
available_jurisdictions: ['us-east-1', 'us-east-2', 'us-west-2', 'eu-west-1', 'eu-central-1', 'ap-northeast-1'],
|
|
55
|
+
baa_path: 'AWS BAA for Bedrock HIPAA eligible services',
|
|
56
|
+
hipaa_eligible: true,
|
|
57
|
+
zdr_available: true,
|
|
58
|
+
default_retention_days: 0,
|
|
59
|
+
},
|
|
60
|
+
'azure-openai': {
|
|
61
|
+
provider: 'openai',
|
|
62
|
+
weights_origin_country: 'US',
|
|
63
|
+
available_jurisdictions: ['eastus', 'westus', 'westeurope', 'francecentral', 'switzerlandnorth'],
|
|
64
|
+
baa_path: 'Microsoft Product Terms DPA',
|
|
65
|
+
hipaa_eligible: true,
|
|
66
|
+
zdr_available: true,
|
|
67
|
+
default_retention_days: 0,
|
|
68
|
+
},
|
|
69
|
+
'openai-direct': {
|
|
70
|
+
provider: 'openai',
|
|
71
|
+
weights_origin_country: 'US',
|
|
72
|
+
available_jurisdictions: ['US'],
|
|
73
|
+
baa_path: 'OpenAI Enterprise BAA with eligible API configuration',
|
|
74
|
+
hipaa_eligible: true,
|
|
75
|
+
zdr_available: true,
|
|
76
|
+
default_retention_days: 30,
|
|
77
|
+
},
|
|
78
|
+
'vertex-ai': {
|
|
79
|
+
provider: 'google',
|
|
80
|
+
weights_origin_country: 'US',
|
|
81
|
+
available_jurisdictions: ['us-central1', 'us-east4', 'europe-west4', 'europe-west1', 'asia-northeast1'],
|
|
82
|
+
baa_path: 'Google Cloud BAA',
|
|
83
|
+
hipaa_eligible: true,
|
|
84
|
+
zdr_available: true,
|
|
85
|
+
default_retention_days: 0,
|
|
86
|
+
},
|
|
87
|
+
fireworks: {
|
|
88
|
+
provider: 'fireworks',
|
|
89
|
+
weights_origin_country: 'unknown',
|
|
90
|
+
available_jurisdictions: ['US'],
|
|
91
|
+
baa_path: null,
|
|
92
|
+
hipaa_eligible: false,
|
|
93
|
+
zdr_available: false,
|
|
94
|
+
default_retention_days: 30,
|
|
95
|
+
},
|
|
96
|
+
baseten: {
|
|
97
|
+
provider: 'baseten',
|
|
98
|
+
weights_origin_country: 'unknown',
|
|
99
|
+
available_jurisdictions: ['US'],
|
|
100
|
+
baa_path: null,
|
|
101
|
+
hipaa_eligible: false,
|
|
102
|
+
zdr_available: false,
|
|
103
|
+
default_retention_days: 30,
|
|
104
|
+
},
|
|
105
|
+
together: {
|
|
106
|
+
provider: 'together',
|
|
107
|
+
weights_origin_country: 'unknown',
|
|
108
|
+
available_jurisdictions: ['US'],
|
|
109
|
+
baa_path: null,
|
|
110
|
+
hipaa_eligible: false,
|
|
111
|
+
zdr_available: false,
|
|
112
|
+
default_retention_days: 30,
|
|
113
|
+
},
|
|
114
|
+
'moonshot-direct': {
|
|
115
|
+
provider: 'moonshot',
|
|
116
|
+
weights_origin_country: 'CN',
|
|
117
|
+
available_jurisdictions: ['CN'],
|
|
118
|
+
baa_path: null,
|
|
119
|
+
hipaa_eligible: false,
|
|
120
|
+
zdr_available: false,
|
|
121
|
+
default_retention_days: 30,
|
|
122
|
+
foreign_origin: true,
|
|
123
|
+
},
|
|
124
|
+
'deepseek-direct': {
|
|
125
|
+
provider: 'deepseek',
|
|
126
|
+
weights_origin_country: 'CN',
|
|
127
|
+
available_jurisdictions: ['CN'],
|
|
128
|
+
baa_path: null,
|
|
129
|
+
hipaa_eligible: false,
|
|
130
|
+
zdr_available: false,
|
|
131
|
+
default_retention_days: 30,
|
|
132
|
+
foreign_origin: true,
|
|
133
|
+
},
|
|
134
|
+
'alibaba-direct': {
|
|
135
|
+
provider: 'alibaba',
|
|
136
|
+
weights_origin_country: 'CN',
|
|
137
|
+
available_jurisdictions: ['CN'],
|
|
138
|
+
baa_path: null,
|
|
139
|
+
hipaa_eligible: false,
|
|
140
|
+
zdr_available: false,
|
|
141
|
+
default_retention_days: 30,
|
|
142
|
+
foreign_origin: true,
|
|
143
|
+
},
|
|
144
|
+
'kimi-k2-on-baseten': {
|
|
145
|
+
provider: 'baseten',
|
|
146
|
+
weights_origin_country: 'CN',
|
|
147
|
+
available_jurisdictions: ['US'],
|
|
148
|
+
baa_path: null,
|
|
149
|
+
hipaa_eligible: false,
|
|
150
|
+
zdr_available: false,
|
|
151
|
+
default_retention_days: 30,
|
|
152
|
+
foreign_origin: true,
|
|
153
|
+
},
|
|
154
|
+
'deepseek-on-together': {
|
|
155
|
+
provider: 'together',
|
|
156
|
+
weights_origin_country: 'CN',
|
|
157
|
+
available_jurisdictions: ['US'],
|
|
158
|
+
baa_path: null,
|
|
159
|
+
hipaa_eligible: false,
|
|
160
|
+
zdr_available: false,
|
|
161
|
+
default_retention_days: 30,
|
|
162
|
+
foreign_origin: true,
|
|
163
|
+
},
|
|
164
|
+
'qwen-on-together': {
|
|
165
|
+
provider: 'together',
|
|
166
|
+
weights_origin_country: 'CN',
|
|
167
|
+
available_jurisdictions: ['US'],
|
|
168
|
+
baa_path: null,
|
|
169
|
+
hipaa_eligible: false,
|
|
170
|
+
zdr_available: false,
|
|
171
|
+
default_retention_days: 30,
|
|
172
|
+
foreign_origin: true,
|
|
173
|
+
},
|
|
174
|
+
'self-hosted': {
|
|
175
|
+
provider: 'self_hosted',
|
|
176
|
+
weights_origin_country: 'unknown',
|
|
177
|
+
available_jurisdictions: ['self-hosted'],
|
|
178
|
+
baa_path: null,
|
|
179
|
+
hipaa_eligible: false,
|
|
180
|
+
zdr_available: true,
|
|
181
|
+
default_retention_days: 0,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const FOREIGN_ORIGIN_PATTERNS = [/kimi/i, /deepseek/i, /qwen/i, /yi[-_ ]/i, /baichuan/i, /glm/i, /moonshot/i];
|
|
186
|
+
|
|
187
|
+
export function isForeignOriginModel(model: string): boolean {
|
|
188
|
+
return FOREIGN_ORIGIN_PATTERNS.some((pattern) => pattern.test(model));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function inferProviderRoute(req: ProviderRegistryRequest): string {
|
|
192
|
+
const model = String(req.model || '').toLowerCase();
|
|
193
|
+
const url = String(req.url || '').toLowerCase();
|
|
194
|
+
if ((req.providerRoute === 'baseten' || url.includes('baseten')) && /kimi/.test(model)) return 'kimi-k2-on-baseten';
|
|
195
|
+
if ((req.providerRoute === 'together' || url.includes('together')) && /deepseek/.test(model)) return 'deepseek-on-together';
|
|
196
|
+
if ((req.providerRoute === 'together' || url.includes('together')) && /qwen/.test(model)) return 'qwen-on-together';
|
|
197
|
+
if (req.providerRoute && PROVIDER_REGISTRY[req.providerRoute]) return req.providerRoute;
|
|
198
|
+
if (url.includes('bedrock') || req.provider === 'bedrock') return 'aws-bedrock';
|
|
199
|
+
if (url.includes('anthropic') || req.provider === 'anthropic') return 'anthropic-direct';
|
|
200
|
+
if (url.includes('azure') || url.includes('openai.azure')) return 'azure-openai';
|
|
201
|
+
if (url.includes('aiplatform.googleapis') || url.includes('vertex')) return 'vertex-ai';
|
|
202
|
+
if (url.includes('fireworks') || req.providerRoute === 'fireworks') return 'fireworks';
|
|
203
|
+
if (url.includes('baseten') || req.providerRoute === 'baseten') return 'baseten';
|
|
204
|
+
if (url.includes('together') || req.providerRoute === 'together') return 'together';
|
|
205
|
+
if (url.includes('moonshot') || /kimi/.test(model)) return 'moonshot-direct';
|
|
206
|
+
if (url.includes('deepseek') || /deepseek/.test(model)) return 'deepseek-direct';
|
|
207
|
+
if (url.includes('dashscope') || url.includes('alibaba') || /qwen/.test(model)) return 'alibaba-direct';
|
|
208
|
+
if (req.provider === 'openai') return 'openai-direct';
|
|
209
|
+
if (req.provider === 'gemini') return 'vertex-ai';
|
|
210
|
+
return 'self-hosted';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function inferModelIdentity(model: string, route: string): ModelIdentity {
|
|
214
|
+
const info = PROVIDER_REGISTRY[route] ?? PROVIDER_REGISTRY['self-hosted'];
|
|
215
|
+
const modelId = model || 'unknown';
|
|
216
|
+
return {
|
|
217
|
+
provider: modelProvider(modelId, info.provider),
|
|
218
|
+
model_id: modelId,
|
|
219
|
+
model_version: modelVersion(modelId),
|
|
220
|
+
model_family: modelFamily(modelId),
|
|
221
|
+
weights_origin_country: isForeignOriginModel(modelId) || info.foreign_origin ? 'CN' : info.weights_origin_country,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function inferHosting(route: string, config: ProviderRegistryConfig = {}): HostingProvenance {
|
|
226
|
+
const info = PROVIDER_REGISTRY[route] ?? PROVIDER_REGISTRY['self-hosted'];
|
|
227
|
+
const region = config.jurisdictionRegion || info.available_jurisdictions[0] || 'unknown';
|
|
228
|
+
return {
|
|
229
|
+
provider_route: route,
|
|
230
|
+
jurisdiction_country: config.jurisdictionCountry || countryFromRegion(region),
|
|
231
|
+
jurisdiction_region: region,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function inferCompliance(route: string, model: ModelIdentity, config: ProviderRegistryConfig = {}): ComplianceProvenance {
|
|
236
|
+
const info = PROVIDER_REGISTRY[route] ?? PROVIDER_REGISTRY['self-hosted'];
|
|
237
|
+
const retention = config.dataRetentionDays !== undefined ? config.dataRetentionDays : info.default_retention_days;
|
|
238
|
+
return {
|
|
239
|
+
baa_in_force: config.baaInForce ?? false,
|
|
240
|
+
baa_vendor: config.baaVendor ?? info.baa_path,
|
|
241
|
+
hipaa_eligible: info.hipaa_eligible,
|
|
242
|
+
data_retention_days: retention,
|
|
243
|
+
data_residency_attested: config.dataResidencyAttested ?? false,
|
|
244
|
+
foreign_origin_weight_flag: model.weights_origin_country === 'CN' || info.foreign_origin === true,
|
|
245
|
+
foreign_origin_consent_receipt_id: config.foreignOriginConsentReceiptId ?? null,
|
|
246
|
+
inference_billing: config.inferenceBilling ?? 'customer_managed',
|
|
247
|
+
inference_billing_detail: config.inferenceBilling === 'agentguard_managed' ? config.inferenceBillingDetail ?? null : null,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function modelProvider(model: string, fallback: ProvenanceProvider): ProvenanceProvider {
|
|
252
|
+
const lower = model.toLowerCase();
|
|
253
|
+
if (lower.includes('claude')) return 'anthropic';
|
|
254
|
+
if (lower.includes('gpt') || lower.includes('openai')) return 'openai';
|
|
255
|
+
if (lower.includes('gemini')) return 'google';
|
|
256
|
+
if (lower.includes('mistral')) return 'mistral';
|
|
257
|
+
if (lower.includes('llama')) return 'meta';
|
|
258
|
+
if (lower.includes('command')) return 'cohere';
|
|
259
|
+
if (lower.includes('deepseek')) return 'deepseek';
|
|
260
|
+
if (lower.includes('kimi') || lower.includes('moonshot')) return 'moonshot';
|
|
261
|
+
if (lower.includes('qwen') || lower.includes('yi') || lower.includes('baichuan') || lower.includes('glm')) return 'alibaba';
|
|
262
|
+
return fallback;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function modelVersion(model: string): string {
|
|
266
|
+
const match = model.match(/(?:^|[-_])(20\d{6}|\d{8})(?:$|[-_])/);
|
|
267
|
+
return match?.[1] ?? 'unknown';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function modelFamily(model: string): string {
|
|
271
|
+
const lower = model.toLowerCase();
|
|
272
|
+
if (lower.includes('claude')) return 'claude';
|
|
273
|
+
if (lower.includes('gpt')) return 'gpt';
|
|
274
|
+
if (lower.includes('gemini')) return 'gemini';
|
|
275
|
+
if (lower.includes('llama-4') || lower.includes('llama4')) return 'llama-4';
|
|
276
|
+
if (lower.includes('mistral')) return 'mistral';
|
|
277
|
+
if (lower.includes('kimi')) return 'kimi';
|
|
278
|
+
if (lower.includes('deepseek')) return 'deepseek';
|
|
279
|
+
if (lower.includes('qwen')) return 'qwen';
|
|
280
|
+
return lower.split(/[/:_]/).pop()?.split('-').slice(0, 2).join('-') || 'unknown';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function countryFromRegion(region: string): JurisdictionCountry {
|
|
284
|
+
const lower = region.toLowerCase();
|
|
285
|
+
if (lower.startsWith('us') || lower.includes('eastus') || lower.includes('westus')) return 'US';
|
|
286
|
+
if (lower.startsWith('eu') || lower.includes('europe') || lower.includes('france') || lower.includes('switzerland')) return 'EU';
|
|
287
|
+
if (lower.startsWith('uk')) return 'UK';
|
|
288
|
+
if (lower.startsWith('ca')) return 'CA';
|
|
289
|
+
if (lower.startsWith('cn')) return 'CN';
|
|
290
|
+
if (lower.startsWith('ap-northeast') || lower.includes('tokyo') || lower.includes('japan')) return 'JP';
|
|
291
|
+
if (lower.startsWith('ap-southeast-2') || lower.includes('australia')) return 'AU';
|
|
292
|
+
return 'unknown';
|
|
293
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as https from 'https';
|
|
3
|
+
import type { ProvenanceBlock } from '../receipts/schema';
|
|
4
|
+
|
|
5
|
+
export type GovernancePosture = 'velocity' | 'standard' | 'compliance';
|
|
6
|
+
|
|
7
|
+
export interface ComplianceEnforcementConfig {
|
|
8
|
+
posture?: GovernancePosture;
|
|
9
|
+
foreignOriginConsentReceiptId?: string | null;
|
|
10
|
+
consentEndpointBaseUrl?: string;
|
|
11
|
+
consentGetJson?: (url: string) => Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class AgentGuardComplianceError extends Error {
|
|
15
|
+
code = 'AGENTGUARD_COMPLIANCE_BLOCK';
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'AgentGuardComplianceError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class AgentGuardConsentRequiredError extends Error {
|
|
23
|
+
code = 'AGENTGUARD_CONSENT_REQUIRED';
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'AgentGuardConsentRequiredError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_CONSENT_BASE_URL = 'https://agentguard.run';
|
|
31
|
+
|
|
32
|
+
export async function enforceCompliance(
|
|
33
|
+
provenance: ProvenanceBlock,
|
|
34
|
+
config: ComplianceEnforcementConfig = {},
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
if (!provenance.compliance.foreign_origin_weight_flag) return;
|
|
37
|
+
const posture = config.posture ?? 'standard';
|
|
38
|
+
if (posture === 'compliance') {
|
|
39
|
+
throw new AgentGuardComplianceError(
|
|
40
|
+
'Foreign origin weights are blocked in compliance posture. Set posture to standard and provide foreign_origin_consent_receipt_id to use this model.',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (posture !== 'standard') return;
|
|
44
|
+
const consentId = config.foreignOriginConsentReceiptId || provenance.compliance.foreign_origin_consent_receipt_id;
|
|
45
|
+
if (!consentId) {
|
|
46
|
+
throw new AgentGuardConsentRequiredError(
|
|
47
|
+
'This call uses foreign origin weights. Generate a consent receipt at https://agentguard.run/dashboard/consent/foreign-origin-weights before using this model.',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const verified = await verifyConsentReceipt(consentId, config);
|
|
51
|
+
if (!verified) {
|
|
52
|
+
throw new AgentGuardConsentRequiredError(`Foreign origin consent receipt ${consentId} is invalid or expired.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function verifyConsentReceipt(
|
|
57
|
+
consentId: string,
|
|
58
|
+
config: ComplianceEnforcementConfig = {},
|
|
59
|
+
): Promise<boolean> {
|
|
60
|
+
if (!/^ag_consent_[A-Za-z0-9_-]+$/.test(consentId)) return false;
|
|
61
|
+
const base = (config.consentEndpointBaseUrl || DEFAULT_CONSENT_BASE_URL).replace(/\/$/, '');
|
|
62
|
+
const raw = await (config.consentGetJson ?? getJson)(`${base}/api/consent/verify/${encodeURIComponent(consentId)}`);
|
|
63
|
+
return Boolean(raw && typeof raw === 'object' && (raw as { valid?: unknown }).valid === true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getJson(url: string): Promise<unknown> {
|
|
67
|
+
if (url.startsWith('mock://')) return Promise.resolve({ valid: true });
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const parsed = new URL(url);
|
|
70
|
+
const client = parsed.protocol === 'http:' ? http : https;
|
|
71
|
+
const req = client.request(
|
|
72
|
+
{ method: 'GET', hostname: parsed.hostname, port: parsed.port, path: parsed.pathname + parsed.search, timeout: 5000 },
|
|
73
|
+
(res) => {
|
|
74
|
+
const chunks: Buffer[] = [];
|
|
75
|
+
res.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
76
|
+
res.on('end', () => {
|
|
77
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
78
|
+
if ((res.statusCode ?? 0) >= 400) return reject(new Error(`consent endpoint failed: ${res.statusCode}`));
|
|
79
|
+
try { resolve(text ? JSON.parse(text) : {}); } catch { resolve({}); }
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
req.on('error', reject);
|
|
84
|
+
req.on('timeout', () => req.destroy(new Error('consent endpoint timed out')));
|
|
85
|
+
req.end();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ReceiptV2 } from '../workflow/types';
|
|
2
|
+
|
|
3
|
+
export type ProvenanceProvider =
|
|
4
|
+
| 'anthropic'
|
|
5
|
+
| 'openai'
|
|
6
|
+
| 'google'
|
|
7
|
+
| 'mistral'
|
|
8
|
+
| 'meta'
|
|
9
|
+
| 'cohere'
|
|
10
|
+
| 'fireworks'
|
|
11
|
+
| 'baseten'
|
|
12
|
+
| 'together'
|
|
13
|
+
| 'deepseek'
|
|
14
|
+
| 'moonshot'
|
|
15
|
+
| 'alibaba'
|
|
16
|
+
| 'self_hosted'
|
|
17
|
+
| 'unknown';
|
|
18
|
+
|
|
19
|
+
export type WeightsOriginCountry = 'US' | 'EU' | 'UK' | 'CA' | 'CN' | 'unknown';
|
|
20
|
+
export type JurisdictionCountry = 'US' | 'EU' | 'UK' | 'CA' | 'CN' | 'AU' | 'JP' | 'unknown';
|
|
21
|
+
|
|
22
|
+
export interface ModelIdentity {
|
|
23
|
+
provider: ProvenanceProvider;
|
|
24
|
+
model_id: string;
|
|
25
|
+
model_version: string;
|
|
26
|
+
model_family: string;
|
|
27
|
+
weights_origin_country: WeightsOriginCountry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface HostingProvenance {
|
|
31
|
+
provider_route: string;
|
|
32
|
+
jurisdiction_country: JurisdictionCountry;
|
|
33
|
+
jurisdiction_region: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ComplianceInferenceBillingDetail {
|
|
37
|
+
via: 'openrouter_key' | 'openrouter_workspace';
|
|
38
|
+
workspace_id?: string | null;
|
|
39
|
+
key_hash?: string | null;
|
|
40
|
+
tier: 'solo_pro' | 'startup_pro' | 'growth_pro';
|
|
41
|
+
guardrails_active: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ComplianceProvenance {
|
|
45
|
+
baa_in_force: boolean;
|
|
46
|
+
baa_vendor: string | null;
|
|
47
|
+
hipaa_eligible: boolean;
|
|
48
|
+
data_retention_days: number | null;
|
|
49
|
+
data_residency_attested: boolean;
|
|
50
|
+
foreign_origin_weight_flag: boolean;
|
|
51
|
+
foreign_origin_consent_receipt_id: string | null;
|
|
52
|
+
inference_billing: 'agentguard_managed' | 'customer_managed';
|
|
53
|
+
inference_billing_detail?: ComplianceInferenceBillingDetail | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ProvenanceBlock {
|
|
57
|
+
model_identity: ModelIdentity;
|
|
58
|
+
hosting: HostingProvenance;
|
|
59
|
+
compliance: ComplianceProvenance;
|
|
60
|
+
captured_at: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ReceiptV1 {
|
|
64
|
+
receipt_id: string;
|
|
65
|
+
schema_version?: 1;
|
|
66
|
+
signature?: string;
|
|
67
|
+
[key: string]: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type ReceiptV3 = Omit<ReceiptV2, 'schema_version'> & {
|
|
71
|
+
schema_version: 3;
|
|
72
|
+
provenance: ProvenanceBlock;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type AnyReceipt = ReceiptV1 | ReceiptV2 | ReceiptV3;
|
|
76
|
+
|
|
77
|
+
export function receiptSchemaVersion(receipt: unknown): 1 | 2 | 3 {
|
|
78
|
+
if (!receipt || typeof receipt !== 'object') throw new Error('Receipt must be an object');
|
|
79
|
+
const raw = (receipt as { schema_version?: unknown }).schema_version;
|
|
80
|
+
if (raw === undefined || raw === 1) return 1;
|
|
81
|
+
if (raw === 2 || raw === 3) return raw;
|
|
82
|
+
throw new Error(`Unsupported receipt schema_version: ${String(raw)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isReceiptV3(receipt: unknown): receipt is ReceiptV3 {
|
|
86
|
+
return receiptSchemaVersion(receipt) === 3 && Boolean((receipt as { provenance?: unknown }).provenance);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function assertReceiptV3(receipt: unknown): asserts receipt is ReceiptV3 {
|
|
90
|
+
if (!isReceiptV3(receipt)) throw new Error('Receipt schema v3 requires a provenance block');
|
|
91
|
+
}
|
package/src/workflow/receipt.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { canonicalJson, sha256Hex } from '../decision-log';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ProvenanceBlock } from '../receipts/schema';
|
|
4
|
+
import type { ReceiptV2, ReceiptV3, ReviewerCascadeEvidence } from './types';
|
|
4
5
|
|
|
5
6
|
export function canonicalJSONStringify(value: unknown): string {
|
|
6
7
|
return canonicalJson(value);
|
|
@@ -14,7 +15,7 @@ export function workflowReceiptId(prefix = 'ag_r'): string {
|
|
|
14
15
|
return `${prefix}_${randomUUID().replace(/-/g, '').slice(0, 16)}`;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export function signReceiptPayload(payload:
|
|
18
|
+
export function signReceiptPayload(payload: unknown): string {
|
|
18
19
|
return sha256Hex(canonicalJson(payload));
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -63,6 +64,39 @@ export function buildReceipt(args: {
|
|
|
63
64
|
return { ...unsigned, signature: signReceiptPayload(unsigned) };
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export function buildReceiptV3(args: {
|
|
70
|
+
workflow_id: string | null;
|
|
71
|
+
outcome_name: string;
|
|
72
|
+
user_id: string;
|
|
73
|
+
cost_usd: number;
|
|
74
|
+
parent_receipt_id: string | null;
|
|
75
|
+
chain_validation_hash: string | null;
|
|
76
|
+
workflow_checkpoint_idx: number | null;
|
|
77
|
+
workflow_total_spend_usd_to_date: number | null;
|
|
78
|
+
provenance: ProvenanceBlock;
|
|
79
|
+
is_checkpoint?: boolean;
|
|
80
|
+
checkpoint_label?: string | null;
|
|
81
|
+
reviewer_cascade?: ReviewerCascadeEvidence | null;
|
|
82
|
+
receipt_type?: ReceiptV2['receipt_type'];
|
|
83
|
+
cancelled_reason?: string | null;
|
|
84
|
+
}): ReceiptV3 {
|
|
85
|
+
const v2 = buildReceipt(args);
|
|
86
|
+
const unsigned: Omit<ReceiptV3, 'signature'> = {
|
|
87
|
+
...v2,
|
|
88
|
+
schema_version: 3,
|
|
89
|
+
provenance: args.provenance,
|
|
90
|
+
};
|
|
91
|
+
const { signature: _oldSignature, ...withoutSignature } = unsigned as Omit<ReceiptV3, 'signature'> & { signature?: string };
|
|
92
|
+
return { ...withoutSignature, signature: signReceiptPayload(withoutSignature) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function verifyAnyReceipt(receipt: ReceiptV2 | ReceiptV3): boolean {
|
|
96
|
+
const { signature: _signature, ...unsigned } = receipt;
|
|
97
|
+
return signReceiptPayload(unsigned) === receipt.signature;
|
|
98
|
+
}
|
|
99
|
+
|
|
66
100
|
export function verifyReceipt(receipt: ReceiptV2): boolean {
|
|
67
101
|
const { signature: _signature, ...unsigned } = receipt;
|
|
68
102
|
return signReceiptPayload(unsigned) === receipt.signature;
|
package/src/workflow/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProvenanceBlock } from '../receipts/schema';
|
|
1
2
|
export type WorkflowState =
|
|
2
3
|
| 'active'
|
|
3
4
|
| 'paused'
|
|
@@ -86,3 +87,9 @@ export interface ValidationFailure {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export type ChainValidationResult = ValidationResult | ValidationFailure;
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
export interface ReceiptV3 extends Omit<ReceiptV2, 'schema_version'> {
|
|
93
|
+
schema_version: 3;
|
|
94
|
+
provenance: ProvenanceBlock;
|
|
95
|
+
}
|