@agentguard-run/spend 0.4.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +23 -0
  3. package/dist/index.d.ts +14 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +37 -3
  6. package/dist/index.js.map +1 -1
  7. package/dist/middleware/provenance.d.ts +11 -0
  8. package/dist/middleware/provenance.d.ts.map +1 -0
  9. package/dist/middleware/provenance.js +17 -0
  10. package/dist/middleware/provenance.js.map +1 -0
  11. package/dist/middleware/provider-registry.d.ts +34 -0
  12. package/dist/middleware/provider-registry.d.ts.map +1 -0
  13. package/dist/middleware/provider-registry.js +290 -0
  14. package/dist/middleware/provider-registry.js.map +1 -0
  15. package/dist/posture/enforce.d.ts +19 -0
  16. package/dist/posture/enforce.d.ts.map +1 -0
  17. package/dist/posture/enforce.js +109 -0
  18. package/dist/posture/enforce.js.map +1 -0
  19. package/dist/receipts/schema.d.ts +46 -0
  20. package/dist/receipts/schema.d.ts.map +1 -0
  21. package/dist/receipts/schema.js +23 -0
  22. package/dist/receipts/schema.js.map +1 -0
  23. package/dist/spend-guard.d.ts +20 -0
  24. package/dist/spend-guard.d.ts.map +1 -1
  25. package/dist/spend-guard.js +24 -2
  26. package/dist/spend-guard.js.map +1 -1
  27. package/dist/types.d.ts +10 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/workflow/chain-validator.d.ts +4 -0
  30. package/dist/workflow/chain-validator.d.ts.map +1 -0
  31. package/dist/workflow/chain-validator.js +37 -0
  32. package/dist/workflow/chain-validator.js.map +1 -0
  33. package/dist/workflow/context.d.ts +46 -0
  34. package/dist/workflow/context.d.ts.map +1 -0
  35. package/dist/workflow/context.js +360 -0
  36. package/dist/workflow/context.js.map +1 -0
  37. package/dist/workflow/errors.d.ts +43 -0
  38. package/dist/workflow/errors.d.ts.map +1 -0
  39. package/dist/workflow/errors.js +40 -0
  40. package/dist/workflow/errors.js.map +1 -0
  41. package/dist/workflow/index.d.ts +6 -0
  42. package/dist/workflow/index.d.ts.map +1 -0
  43. package/dist/workflow/index.js +20 -0
  44. package/dist/workflow/index.js.map +1 -0
  45. package/dist/workflow/receipt.d.ts +41 -0
  46. package/dist/workflow/receipt.d.ts.map +1 -0
  47. package/dist/workflow/receipt.js +76 -0
  48. package/dist/workflow/receipt.js.map +1 -0
  49. package/dist/workflow/types.d.ts +79 -0
  50. package/dist/workflow/types.d.ts.map +1 -0
  51. package/dist/workflow/types.js +3 -0
  52. package/dist/workflow/types.js.map +1 -0
  53. package/package.json +13 -3
  54. package/src/middleware/provenance.ts +31 -0
  55. package/src/middleware/provider-registry.ts +289 -0
  56. package/src/posture/enforce.ts +87 -0
  57. package/src/receipts/schema.ts +81 -0
  58. package/src/workflow/chain-validator.ts +35 -0
  59. package/src/workflow/context.ts +418 -0
  60. package/src/workflow/errors.ts +27 -0
  61. package/src/workflow/index.ts +18 -0
  62. package/src/workflow/receipt.ts +107 -0
  63. package/src/workflow/types.ts +95 -0
@@ -0,0 +1,79 @@
1
+ import type { ProvenanceBlock } from '../receipts/schema';
2
+ export type WorkflowState = 'active' | 'paused' | 'cancelled' | 'completed' | 'budget_capped' | 'duration_capped';
3
+ export type ReviewerVerdict = 'approve' | 'block' | 'revise' | null;
4
+ export interface WorkflowConfig {
5
+ name: string;
6
+ budget_cap_usd: number;
7
+ duration_cap_hours: number;
8
+ checkpoint_every_outcomes?: number;
9
+ parent_outcomes?: string[];
10
+ resume_if_exists?: boolean;
11
+ metadata?: Record<string, string | number | boolean>;
12
+ user_id?: string;
13
+ api_base_url?: string;
14
+ license_key?: string;
15
+ post_json?: (url: string, body: unknown, headers: Record<string, string>) => Promise<unknown>;
16
+ get_json?: (url: string, headers: Record<string, string>) => Promise<unknown>;
17
+ }
18
+ export interface ReviewerCascadeEvidence {
19
+ triggered: boolean;
20
+ drafter_model: string | null;
21
+ reviewer_model: string | null;
22
+ drafter_output_hash: string | null;
23
+ reviewer_verdict: ReviewerVerdict;
24
+ reviewer_reasoning_hash: string | null;
25
+ review_started_at: string | null;
26
+ review_completed_at: string | null;
27
+ }
28
+ export interface ReceiptV2 {
29
+ receipt_id: string;
30
+ schema_version: 2;
31
+ outcome_name: string;
32
+ user_id: string;
33
+ cost_usd: number;
34
+ signed_at: string;
35
+ signature: string;
36
+ public_key_fingerprint: string;
37
+ workflow_id: string | null;
38
+ parent_receipt_id: string | null;
39
+ chain_validation_hash: string | null;
40
+ workflow_checkpoint_idx: number | null;
41
+ workflow_total_spend_usd_to_date: number | null;
42
+ is_checkpoint: boolean;
43
+ checkpoint_label: string | null;
44
+ reviewer_cascade: ReviewerCascadeEvidence | null;
45
+ receipt_type?: 'outcome' | 'checkpoint' | 'cancel' | 'cap_hit' | 'completed';
46
+ cancelled_reason?: string | null;
47
+ }
48
+ export interface CheckpointReceipt extends ReceiptV2 {
49
+ is_checkpoint: true;
50
+ }
51
+ export interface WorkflowStatus {
52
+ workflow_id: string;
53
+ state: WorkflowState;
54
+ total_spend_usd: number;
55
+ budget_cap_usd: number;
56
+ budget_remaining_usd: number;
57
+ budget_pct_used: number;
58
+ outcome_count: number;
59
+ last_receipt_id: string | null;
60
+ last_checkpoint_id: string | null;
61
+ last_chain_hash: string | null;
62
+ duration_elapsed_ms: number;
63
+ duration_cap_ms: number;
64
+ duration_remaining_ms: number;
65
+ }
66
+ export interface ValidationResult {
67
+ ok: true;
68
+ }
69
+ export interface ValidationFailure {
70
+ ok: false;
71
+ broken_at: number;
72
+ reason: 'signature_invalid' | 'chain_hash_mismatch' | 'parent_reference_mismatch';
73
+ }
74
+ export type ChainValidationResult = ValidationResult | ValidationFailure;
75
+ export interface ReceiptV3 extends Omit<ReceiptV2, 'schema_version'> {
76
+ schema_version: 3;
77
+ provenance: ProvenanceBlock;
78
+ }
79
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentguard-run/spend",
3
- "version": "0.4.4",
3
+ "version": "0.6.0",
4
4
  "description": "All terminology and labels used in AgentGuard materials are descriptive of software functionality only, not legal definitions or guarantees of compliance. Terms like receipt, audit log, evidence, audit trail, and attestation refer solely to cryptographically-signed records produced by the software. Full functional-use disclaimer in README.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -44,6 +44,11 @@
44
44
  "types": "./dist/advisor/conversation.d.ts",
45
45
  "require": "./dist/advisor/conversation.js",
46
46
  "default": "./dist/advisor/conversation.js"
47
+ },
48
+ "./workflow": {
49
+ "types": "./dist/workflow/index.d.ts",
50
+ "require": "./dist/workflow/index.js",
51
+ "default": "./dist/workflow/index.js"
47
52
  }
48
53
  },
49
54
  "bin": {
@@ -65,7 +70,11 @@
65
70
  "src/cli/wizard.ts",
66
71
  "src/bindings",
67
72
  "src/advisor",
68
- "src/cli/advisor.ts"
73
+ "src/cli/advisor.ts",
74
+ "src/workflow",
75
+ "src/receipts",
76
+ "src/middleware",
77
+ "src/posture"
69
78
  ],
70
79
  "scripts": {
71
80
  "build": "tsc",
@@ -103,7 +112,8 @@
103
112
  "url": "https://agentguard.run/contact"
104
113
  },
105
114
  "dependencies": {
106
- "@noble/ed25519": ">=2.3.0 <4.0.0"
115
+ "@noble/ed25519": "^3.1.0",
116
+ "@noble/hashes": "^2.2.0"
107
117
  },
108
118
  "peerDependencies": {
109
119
  "@anthropic-ai/sdk": ">=0.30.0",
@@ -0,0 +1,31 @@
1
+ import type { ProvenanceBlock } from '../receipts/schema';
2
+ import {
3
+ inferCompliance,
4
+ inferHosting,
5
+ inferModelIdentity,
6
+ inferProviderRoute,
7
+ type ProviderRegistryConfig,
8
+ type ProviderRegistryRequest,
9
+ } from './provider-registry';
10
+
11
+ export interface AgentRequest extends ProviderRegistryRequest {
12
+ headers?: Record<string, string | undefined>;
13
+ }
14
+
15
+ export interface AgentGuardContext {
16
+ config?: ProviderRegistryConfig;
17
+ now?: () => Date;
18
+ }
19
+
20
+ export function captureProvenance(req: AgentRequest, ctx: AgentGuardContext = {}): ProvenanceBlock {
21
+ const route = inferProviderRoute({ ...req, providerRoute: ctx.config?.providerRoute ?? req.providerRoute });
22
+ const model = inferModelIdentity(String(req.model || 'unknown'), route);
23
+ const hosting = inferHosting(route, ctx.config);
24
+ const compliance = inferCompliance(route, model, ctx.config);
25
+ return {
26
+ model_identity: model,
27
+ hosting,
28
+ compliance,
29
+ captured_at: (ctx.now ? ctx.now() : new Date()).toISOString(),
30
+ };
31
+ }
@@ -0,0 +1,289 @@
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
+ }
38
+
39
+ export const PROVIDER_REGISTRY: Record<string, ProviderRouteInfo> = {
40
+ 'anthropic-direct': {
41
+ provider: 'anthropic',
42
+ weights_origin_country: 'US',
43
+ available_jurisdictions: ['US'],
44
+ baa_path: 'Anthropic Enterprise Messages API with BAA eligible routing',
45
+ hipaa_eligible: true,
46
+ zdr_available: true,
47
+ default_retention_days: 0,
48
+ },
49
+ 'aws-bedrock': {
50
+ provider: 'anthropic',
51
+ weights_origin_country: 'US',
52
+ available_jurisdictions: ['us-east-1', 'us-east-2', 'us-west-2', 'eu-west-1', 'eu-central-1', 'ap-northeast-1'],
53
+ baa_path: 'AWS BAA for Bedrock HIPAA eligible services',
54
+ hipaa_eligible: true,
55
+ zdr_available: true,
56
+ default_retention_days: 0,
57
+ },
58
+ 'azure-openai': {
59
+ provider: 'openai',
60
+ weights_origin_country: 'US',
61
+ available_jurisdictions: ['eastus', 'westus', 'westeurope', 'francecentral', 'switzerlandnorth'],
62
+ baa_path: 'Microsoft Product Terms DPA',
63
+ hipaa_eligible: true,
64
+ zdr_available: true,
65
+ default_retention_days: 0,
66
+ },
67
+ 'openai-direct': {
68
+ provider: 'openai',
69
+ weights_origin_country: 'US',
70
+ available_jurisdictions: ['US'],
71
+ baa_path: 'OpenAI Enterprise BAA with eligible API configuration',
72
+ hipaa_eligible: true,
73
+ zdr_available: true,
74
+ default_retention_days: 30,
75
+ },
76
+ 'vertex-ai': {
77
+ provider: 'google',
78
+ weights_origin_country: 'US',
79
+ available_jurisdictions: ['us-central1', 'us-east4', 'europe-west4', 'europe-west1', 'asia-northeast1'],
80
+ baa_path: 'Google Cloud BAA',
81
+ hipaa_eligible: true,
82
+ zdr_available: true,
83
+ default_retention_days: 0,
84
+ },
85
+ fireworks: {
86
+ provider: 'fireworks',
87
+ weights_origin_country: 'unknown',
88
+ available_jurisdictions: ['US'],
89
+ baa_path: null,
90
+ hipaa_eligible: false,
91
+ zdr_available: false,
92
+ default_retention_days: 30,
93
+ },
94
+ baseten: {
95
+ provider: 'baseten',
96
+ weights_origin_country: 'unknown',
97
+ available_jurisdictions: ['US'],
98
+ baa_path: null,
99
+ hipaa_eligible: false,
100
+ zdr_available: false,
101
+ default_retention_days: 30,
102
+ },
103
+ together: {
104
+ provider: 'together',
105
+ weights_origin_country: 'unknown',
106
+ available_jurisdictions: ['US'],
107
+ baa_path: null,
108
+ hipaa_eligible: false,
109
+ zdr_available: false,
110
+ default_retention_days: 30,
111
+ },
112
+ 'moonshot-direct': {
113
+ provider: 'moonshot',
114
+ weights_origin_country: 'CN',
115
+ available_jurisdictions: ['CN'],
116
+ baa_path: null,
117
+ hipaa_eligible: false,
118
+ zdr_available: false,
119
+ default_retention_days: 30,
120
+ foreign_origin: true,
121
+ },
122
+ 'deepseek-direct': {
123
+ provider: 'deepseek',
124
+ weights_origin_country: 'CN',
125
+ available_jurisdictions: ['CN'],
126
+ baa_path: null,
127
+ hipaa_eligible: false,
128
+ zdr_available: false,
129
+ default_retention_days: 30,
130
+ foreign_origin: true,
131
+ },
132
+ 'alibaba-direct': {
133
+ provider: 'alibaba',
134
+ weights_origin_country: 'CN',
135
+ available_jurisdictions: ['CN'],
136
+ baa_path: null,
137
+ hipaa_eligible: false,
138
+ zdr_available: false,
139
+ default_retention_days: 30,
140
+ foreign_origin: true,
141
+ },
142
+ 'kimi-k2-on-baseten': {
143
+ provider: 'baseten',
144
+ weights_origin_country: 'CN',
145
+ available_jurisdictions: ['US'],
146
+ baa_path: null,
147
+ hipaa_eligible: false,
148
+ zdr_available: false,
149
+ default_retention_days: 30,
150
+ foreign_origin: true,
151
+ },
152
+ 'deepseek-on-together': {
153
+ provider: 'together',
154
+ weights_origin_country: 'CN',
155
+ available_jurisdictions: ['US'],
156
+ baa_path: null,
157
+ hipaa_eligible: false,
158
+ zdr_available: false,
159
+ default_retention_days: 30,
160
+ foreign_origin: true,
161
+ },
162
+ 'qwen-on-together': {
163
+ provider: 'together',
164
+ weights_origin_country: 'CN',
165
+ available_jurisdictions: ['US'],
166
+ baa_path: null,
167
+ hipaa_eligible: false,
168
+ zdr_available: false,
169
+ default_retention_days: 30,
170
+ foreign_origin: true,
171
+ },
172
+ 'self-hosted': {
173
+ provider: 'self_hosted',
174
+ weights_origin_country: 'unknown',
175
+ available_jurisdictions: ['self-hosted'],
176
+ baa_path: null,
177
+ hipaa_eligible: false,
178
+ zdr_available: true,
179
+ default_retention_days: 0,
180
+ },
181
+ };
182
+
183
+ const FOREIGN_ORIGIN_PATTERNS = [/kimi/i, /deepseek/i, /qwen/i, /yi[-_ ]/i, /baichuan/i, /glm/i, /moonshot/i];
184
+
185
+ export function isForeignOriginModel(model: string): boolean {
186
+ return FOREIGN_ORIGIN_PATTERNS.some((pattern) => pattern.test(model));
187
+ }
188
+
189
+ export function inferProviderRoute(req: ProviderRegistryRequest): string {
190
+ const model = String(req.model || '').toLowerCase();
191
+ const url = String(req.url || '').toLowerCase();
192
+ if ((req.providerRoute === 'baseten' || url.includes('baseten')) && /kimi/.test(model)) return 'kimi-k2-on-baseten';
193
+ if ((req.providerRoute === 'together' || url.includes('together')) && /deepseek/.test(model)) return 'deepseek-on-together';
194
+ if ((req.providerRoute === 'together' || url.includes('together')) && /qwen/.test(model)) return 'qwen-on-together';
195
+ if (req.providerRoute && PROVIDER_REGISTRY[req.providerRoute]) return req.providerRoute;
196
+ if (url.includes('bedrock') || req.provider === 'bedrock') return 'aws-bedrock';
197
+ if (url.includes('anthropic') || req.provider === 'anthropic') return 'anthropic-direct';
198
+ if (url.includes('azure') || url.includes('openai.azure')) return 'azure-openai';
199
+ if (url.includes('aiplatform.googleapis') || url.includes('vertex')) return 'vertex-ai';
200
+ if (url.includes('fireworks') || req.providerRoute === 'fireworks') return 'fireworks';
201
+ if (url.includes('baseten') || req.providerRoute === 'baseten') return 'baseten';
202
+ if (url.includes('together') || req.providerRoute === 'together') return 'together';
203
+ if (url.includes('moonshot') || /kimi/.test(model)) return 'moonshot-direct';
204
+ if (url.includes('deepseek') || /deepseek/.test(model)) return 'deepseek-direct';
205
+ if (url.includes('dashscope') || url.includes('alibaba') || /qwen/.test(model)) return 'alibaba-direct';
206
+ if (req.provider === 'openai') return 'openai-direct';
207
+ if (req.provider === 'gemini') return 'vertex-ai';
208
+ return 'self-hosted';
209
+ }
210
+
211
+ export function inferModelIdentity(model: string, route: string): ModelIdentity {
212
+ const info = PROVIDER_REGISTRY[route] ?? PROVIDER_REGISTRY['self-hosted'];
213
+ const modelId = model || 'unknown';
214
+ return {
215
+ provider: modelProvider(modelId, info.provider),
216
+ model_id: modelId,
217
+ model_version: modelVersion(modelId),
218
+ model_family: modelFamily(modelId),
219
+ weights_origin_country: isForeignOriginModel(modelId) || info.foreign_origin ? 'CN' : info.weights_origin_country,
220
+ };
221
+ }
222
+
223
+ export function inferHosting(route: string, config: ProviderRegistryConfig = {}): HostingProvenance {
224
+ const info = PROVIDER_REGISTRY[route] ?? PROVIDER_REGISTRY['self-hosted'];
225
+ const region = config.jurisdictionRegion || info.available_jurisdictions[0] || 'unknown';
226
+ return {
227
+ provider_route: route,
228
+ jurisdiction_country: config.jurisdictionCountry || countryFromRegion(region),
229
+ jurisdiction_region: region,
230
+ };
231
+ }
232
+
233
+ export function inferCompliance(route: string, model: ModelIdentity, config: ProviderRegistryConfig = {}): ComplianceProvenance {
234
+ const info = PROVIDER_REGISTRY[route] ?? PROVIDER_REGISTRY['self-hosted'];
235
+ const retention = config.dataRetentionDays !== undefined ? config.dataRetentionDays : info.default_retention_days;
236
+ return {
237
+ baa_in_force: config.baaInForce ?? false,
238
+ baa_vendor: config.baaVendor ?? info.baa_path,
239
+ hipaa_eligible: info.hipaa_eligible,
240
+ data_retention_days: retention,
241
+ data_residency_attested: config.dataResidencyAttested ?? false,
242
+ foreign_origin_weight_flag: model.weights_origin_country === 'CN' || info.foreign_origin === true,
243
+ foreign_origin_consent_receipt_id: config.foreignOriginConsentReceiptId ?? null,
244
+ };
245
+ }
246
+
247
+ function modelProvider(model: string, fallback: ProvenanceProvider): ProvenanceProvider {
248
+ const lower = model.toLowerCase();
249
+ if (lower.includes('claude')) return 'anthropic';
250
+ if (lower.includes('gpt') || lower.includes('openai')) return 'openai';
251
+ if (lower.includes('gemini')) return 'google';
252
+ if (lower.includes('mistral')) return 'mistral';
253
+ if (lower.includes('llama')) return 'meta';
254
+ if (lower.includes('command')) return 'cohere';
255
+ if (lower.includes('deepseek')) return 'deepseek';
256
+ if (lower.includes('kimi') || lower.includes('moonshot')) return 'moonshot';
257
+ if (lower.includes('qwen') || lower.includes('yi') || lower.includes('baichuan') || lower.includes('glm')) return 'alibaba';
258
+ return fallback;
259
+ }
260
+
261
+ function modelVersion(model: string): string {
262
+ const match = model.match(/(?:^|[-_])(20\d{6}|\d{8})(?:$|[-_])/);
263
+ return match?.[1] ?? 'unknown';
264
+ }
265
+
266
+ function modelFamily(model: string): string {
267
+ const lower = model.toLowerCase();
268
+ if (lower.includes('claude')) return 'claude';
269
+ if (lower.includes('gpt')) return 'gpt';
270
+ if (lower.includes('gemini')) return 'gemini';
271
+ if (lower.includes('llama-4') || lower.includes('llama4')) return 'llama-4';
272
+ if (lower.includes('mistral')) return 'mistral';
273
+ if (lower.includes('kimi')) return 'kimi';
274
+ if (lower.includes('deepseek')) return 'deepseek';
275
+ if (lower.includes('qwen')) return 'qwen';
276
+ return lower.split(/[/:_]/).pop()?.split('-').slice(0, 2).join('-') || 'unknown';
277
+ }
278
+
279
+ function countryFromRegion(region: string): JurisdictionCountry {
280
+ const lower = region.toLowerCase();
281
+ if (lower.startsWith('us') || lower.includes('eastus') || lower.includes('westus')) return 'US';
282
+ if (lower.startsWith('eu') || lower.includes('europe') || lower.includes('france') || lower.includes('switzerland')) return 'EU';
283
+ if (lower.startsWith('uk')) return 'UK';
284
+ if (lower.startsWith('ca')) return 'CA';
285
+ if (lower.startsWith('cn')) return 'CN';
286
+ if (lower.startsWith('ap-northeast') || lower.includes('tokyo') || lower.includes('japan')) return 'JP';
287
+ if (lower.startsWith('ap-southeast-2') || lower.includes('australia')) return 'AU';
288
+ return 'unknown';
289
+ }
@@ -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,81 @@
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 ComplianceProvenance {
37
+ baa_in_force: boolean;
38
+ baa_vendor: string | null;
39
+ hipaa_eligible: boolean;
40
+ data_retention_days: number | null;
41
+ data_residency_attested: boolean;
42
+ foreign_origin_weight_flag: boolean;
43
+ foreign_origin_consent_receipt_id: string | null;
44
+ }
45
+
46
+ export interface ProvenanceBlock {
47
+ model_identity: ModelIdentity;
48
+ hosting: HostingProvenance;
49
+ compliance: ComplianceProvenance;
50
+ captured_at: string;
51
+ }
52
+
53
+ export interface ReceiptV1 {
54
+ receipt_id: string;
55
+ schema_version?: 1;
56
+ signature?: string;
57
+ [key: string]: unknown;
58
+ }
59
+
60
+ export type ReceiptV3 = Omit<ReceiptV2, 'schema_version'> & {
61
+ schema_version: 3;
62
+ provenance: ProvenanceBlock;
63
+ };
64
+
65
+ export type AnyReceipt = ReceiptV1 | ReceiptV2 | ReceiptV3;
66
+
67
+ export function receiptSchemaVersion(receipt: unknown): 1 | 2 | 3 {
68
+ if (!receipt || typeof receipt !== 'object') throw new Error('Receipt must be an object');
69
+ const raw = (receipt as { schema_version?: unknown }).schema_version;
70
+ if (raw === undefined || raw === 1) return 1;
71
+ if (raw === 2 || raw === 3) return raw;
72
+ throw new Error(`Unsupported receipt schema_version: ${String(raw)}`);
73
+ }
74
+
75
+ export function isReceiptV3(receipt: unknown): receipt is ReceiptV3 {
76
+ return receiptSchemaVersion(receipt) === 3 && Boolean((receipt as { provenance?: unknown }).provenance);
77
+ }
78
+
79
+ export function assertReceiptV3(receipt: unknown): asserts receipt is ReceiptV3 {
80
+ if (!isReceiptV3(receipt)) throw new Error('Receipt schema v3 requires a provenance block');
81
+ }
@@ -0,0 +1,35 @@
1
+ import { canonicalJSONStringify, sha256, verifyReceipt } from './receipt';
2
+ import type { ChainValidationResult, ReceiptV2 } from './types';
3
+
4
+ export async function validateReceiptChain(receipts: ReceiptV2[]): Promise<ChainValidationResult> {
5
+ if (receipts.length === 0) return { ok: true };
6
+
7
+ for (let i = 0; i < receipts.length; i++) {
8
+ if (!verifyReceipt(receipts[i]!)) {
9
+ return { ok: false, broken_at: i, reason: 'signature_invalid' };
10
+ }
11
+ if (i === 0) continue;
12
+ const prev = receipts[i - 1]!;
13
+ const curr = receipts[i]!;
14
+ const expectedHash = await computeChainHash(prev);
15
+ if (curr.chain_validation_hash !== expectedHash) {
16
+ return { ok: false, broken_at: i, reason: 'chain_hash_mismatch' };
17
+ }
18
+ if (curr.parent_receipt_id !== prev.receipt_id) {
19
+ return { ok: false, broken_at: i, reason: 'parent_reference_mismatch' };
20
+ }
21
+ }
22
+
23
+ return { ok: true };
24
+ }
25
+
26
+ export async function computeChainHash(receipt: ReceiptV2): Promise<string> {
27
+ const canonical = canonicalJSONStringify({
28
+ receipt_id: receipt.receipt_id,
29
+ workflow_id: receipt.workflow_id,
30
+ cost_usd: receipt.cost_usd,
31
+ signed_at: receipt.signed_at,
32
+ signature: receipt.signature,
33
+ });
34
+ return sha256(canonical);
35
+ }