import * as http from 'http'; import * as https from 'https'; import type { ProvenanceBlock } from '../receipts/schema'; export type GovernancePosture = 'velocity' | 'standard' | 'compliance'; export interface ComplianceEnforcementConfig { posture?: GovernancePosture; foreignOriginConsentReceiptId?: string | null; consentEndpointBaseUrl?: string; consentGetJson?: (url: string) => Promise; } export class AgentGuardComplianceError extends Error { code = 'AGENTGUARD_COMPLIANCE_BLOCK'; constructor(message: string) { super(message); this.name = 'AgentGuardComplianceError'; } } export class AgentGuardConsentRequiredError extends Error { code = 'AGENTGUARD_CONSENT_REQUIRED'; constructor(message: string) { super(message); this.name = 'AgentGuardConsentRequiredError'; } } const DEFAULT_CONSENT_BASE_URL = 'https://agentguard.run'; export async function enforceCompliance( provenance: ProvenanceBlock, config: ComplianceEnforcementConfig = {}, ): Promise { if (!provenance.compliance.foreign_origin_weight_flag) return; const posture = config.posture ?? 'standard'; if (posture === 'compliance') { throw new AgentGuardComplianceError( 'Foreign origin weights are blocked in compliance posture. Set posture to standard and provide foreign_origin_consent_receipt_id to use this model.', ); } if (posture !== 'standard') return; const consentId = config.foreignOriginConsentReceiptId || provenance.compliance.foreign_origin_consent_receipt_id; if (!consentId) { throw new AgentGuardConsentRequiredError( 'This call uses foreign origin weights. Generate a consent receipt at https://agentguard.run/dashboard/consent/foreign-origin-weights before using this model.', ); } const verified = await verifyConsentReceipt(consentId, config); if (!verified) { throw new AgentGuardConsentRequiredError(`Foreign origin consent receipt ${consentId} is invalid or expired.`); } } export async function verifyConsentReceipt( consentId: string, config: ComplianceEnforcementConfig = {}, ): Promise { if (!/^ag_consent_[A-Za-z0-9_-]+$/.test(consentId)) return false; const base = (config.consentEndpointBaseUrl || DEFAULT_CONSENT_BASE_URL).replace(/\/$/, ''); const raw = await (config.consentGetJson ?? getJson)(`${base}/api/consent/verify/${encodeURIComponent(consentId)}`); return Boolean(raw && typeof raw === 'object' && (raw as { valid?: unknown }).valid === true); } function getJson(url: string): Promise { if (url.startsWith('mock://')) return Promise.resolve({ valid: true }); return new Promise((resolve, reject) => { const parsed = new URL(url); const client = parsed.protocol === 'http:' ? http : https; const req = client.request( { method: 'GET', hostname: parsed.hostname, port: parsed.port, path: parsed.pathname + parsed.search, timeout: 5000 }, (res) => { const chunks: Buffer[] = []; res.on('data', (chunk: Buffer) => chunks.push(chunk)); res.on('end', () => { const text = Buffer.concat(chunks).toString('utf8'); if ((res.statusCode ?? 0) >= 400) return reject(new Error(`consent endpoint failed: ${res.statusCode}`)); try { resolve(text ? JSON.parse(text) : {}); } catch { resolve({}); } }); }, ); req.on('error', reject); req.on('timeout', () => req.destroy(new Error('consent endpoint timed out'))); req.end(); }); }