/** * @file policy-engine.ts * @notice Policy engine for route selection, compliance, and identity gating */ import { DestinationType } from './workflow-engine'; export interface PolicyRule { id: string; name: string; type: 'allowlist' | 'blocklist' | 'limit' | 'compliance' | 'identity'; conditions: PolicyCondition[]; action: 'allow' | 'deny' | 'review'; priority: number; } export interface PolicyCondition { field: string; operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'in' | 'contains'; value: any; } export interface IdentityCredential { did: string; vcType: string; issuer: string; claims: Record; expiration?: number; } export interface PolicyContext { depositor: string; token: string; amount: string; destinationType: DestinationType; destinationChainId: number; identity?: IdentityCredential; previousTransfers?: number; dailyVolume?: string; } export interface PolicyResult { allowed: boolean; reason?: string; requiresReview: boolean; appliedRules: string[]; } export class PolicyEngine { private rules: PolicyRule[] = []; private identityRegistry: Map = new Map(); constructor() { this.initializeDefaultRules(); } /** * Initialize default policy rules */ private initializeDefaultRules(): void { // Default: Allow all EVM routes for public access this.rules.push({ id: 'default-evm-allow', name: 'Default EVM Allow', type: 'allowlist', conditions: [ { field: 'destinationType', operator: 'eq', value: DestinationType.EVM } ], action: 'allow', priority: 100 }); // Default: XRPL requires Tier 1+ identity this.rules.push({ id: 'xrpl-tier-requirement', name: 'XRPL Tier Requirement', type: 'identity', conditions: [ { field: 'destinationType', operator: 'eq', value: DestinationType.XRPL }, { field: 'identity.vcType', operator: 'eq', value: 'KYCTier' }, { field: 'identity.claims.tier', operator: 'gte', value: 1 } ], action: 'allow', priority: 200 }); // Default: Fabric requires Tier 2+ identity this.rules.push({ id: 'fabric-tier-requirement', name: 'Fabric Tier Requirement', type: 'identity', conditions: [ { field: 'destinationType', operator: 'eq', value: DestinationType.FABRIC }, { field: 'identity.vcType', operator: 'eq', value: 'KYCTier' }, { field: 'identity.claims.tier', operator: 'gte', value: 2 } ], action: 'allow', priority: 200 }); // Default: Daily volume limit this.rules.push({ id: 'daily-volume-limit', name: 'Daily Volume Limit', type: 'limit', conditions: [ { field: 'dailyVolume', operator: 'gt', value: '1000000000000000000000' } // 1000 ETH ], action: 'review', priority: 150 }); } /** * Evaluate policy for a transfer request */ async evaluatePolicy(context: PolicyContext): Promise { // Sort rules by priority (higher first) const sortedRules = [...this.rules].sort((a, b) => b.priority - a.priority); const appliedRules: string[] = []; let finalResult: PolicyResult = { allowed: false, requiresReview: false, appliedRules: [] }; for (const rule of sortedRules) { if (this.matchesRule(rule, context)) { appliedRules.push(rule.id); if (rule.action === 'deny') { return { allowed: false, reason: `Blocked by rule: ${rule.name}`, requiresReview: false, appliedRules }; } else if (rule.action === 'review') { finalResult = { allowed: false, reason: `Requires review: ${rule.name}`, requiresReview: true, appliedRules }; } else if (rule.action === 'allow') { finalResult = { allowed: true, requiresReview: false, appliedRules }; // Continue to check for higher priority deny rules } } } // If no rules matched, default deny if (appliedRules.length === 0) { return { allowed: false, reason: 'No matching policy rules', requiresReview: false, appliedRules: [] }; } return finalResult; } /** * Check if context matches rule conditions */ private matchesRule(rule: PolicyRule, context: PolicyContext): boolean { return rule.conditions.every(condition => { const fieldValue = this.getFieldValue(context, condition.field); return this.evaluateCondition(fieldValue, condition.operator, condition.value); }); } /** * Get field value from context */ private getFieldValue(context: PolicyContext, field: string): any { const parts = field.split('.'); let value: any = context; for (const part of parts) { if (value && typeof value === 'object' && part in value) { value = value[part as keyof typeof value]; } else { return undefined; } } return value; } /** * Evaluate a single condition */ private evaluateCondition( fieldValue: any, operator: string, expectedValue: any ): boolean { switch (operator) { case 'eq': return fieldValue === expectedValue; case 'ne': return fieldValue !== expectedValue; case 'gt': return Number(fieldValue) > Number(expectedValue); case 'lt': return Number(fieldValue) < Number(expectedValue); case 'gte': return Number(fieldValue) >= Number(expectedValue); case 'lte': return Number(fieldValue) <= Number(expectedValue); case 'in': return Array.isArray(expectedValue) && expectedValue.includes(fieldValue); case 'contains': return String(fieldValue).includes(String(expectedValue)); default: return false; } } /** * Add a policy rule */ addRule(rule: PolicyRule): void { this.rules.push(rule); this.rules.sort((a, b) => b.priority - a.priority); } /** * Remove a policy rule */ removeRule(ruleId: string): boolean { const index = this.rules.findIndex(r => r.id === ruleId); if (index >= 0) { this.rules.splice(index, 1); return true; } return false; } /** * Register identity credential */ registerIdentity(address: string, credential: IdentityCredential): void { this.identityRegistry.set(address, credential); } /** * Get identity credential */ getIdentity(address: string): IdentityCredential | undefined { return this.identityRegistry.get(address); } /** * Get all rules */ getAllRules(): PolicyRule[] { return [...this.rules]; } }