Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
at main 105 lines 2.9 kB view raw
1/** 2 * Cascade Engine — propagates evidence failures through the IU dependency graph. 3 * 4 * When IU-X fails, dependent IU-Y must re-run: 5 * - typecheck 6 * - boundary checks 7 * - relevant tagged tests 8 */ 9 10import type { ImplementationUnit } from './models/iu.js'; 11import type { PolicyEvaluation, CascadeEvent, CascadeAction } from './models/evidence.js'; 12 13/** 14 * Compute cascade effects from policy evaluation failures. 15 */ 16export function computeCascade( 17 evaluations: PolicyEvaluation[], 18 ius: ImplementationUnit[], 19): CascadeEvent[] { 20 const events: CascadeEvent[] = []; 21 const iuMap = new Map(ius.map(iu => [iu.iu_id, iu])); 22 23 // Build reverse dependency map: iu_id → IUs that depend on it 24 const dependents = new Map<string, ImplementationUnit[]>(); 25 for (const iu of ius) { 26 for (const depId of iu.dependencies) { 27 const list = dependents.get(depId) ?? []; 28 list.push(iu); 29 dependents.set(depId, list); 30 } 31 } 32 33 for (const eval_ of evaluations) { 34 if (eval_.verdict === 'PASS') continue; 35 36 const sourceIU = iuMap.get(eval_.iu_id); 37 if (!sourceIU) continue; 38 39 const affected = dependents.get(eval_.iu_id) ?? []; 40 if (affected.length === 0 && eval_.verdict !== 'FAIL') continue; 41 42 const actions: CascadeAction[] = []; 43 44 // Actions on the failed IU itself 45 if (eval_.verdict === 'FAIL') { 46 actions.push({ 47 iu_id: eval_.iu_id, 48 iu_name: eval_.iu_name, 49 action: 'BLOCK', 50 reason: `Evidence failed: ${eval_.failed.join(', ')}`, 51 }); 52 } 53 54 // Actions on dependents 55 for (const dep of affected) { 56 actions.push({ 57 iu_id: dep.iu_id, 58 iu_name: dep.name, 59 action: 'RE_VALIDATE', 60 reason: `Dependency ${eval_.iu_name} ${eval_.verdict === 'FAIL' ? 'failed' : 'incomplete'}; re-run typecheck + boundary + tagged tests`, 61 }); 62 } 63 64 events.push({ 65 source_iu_id: eval_.iu_id, 66 source_iu_name: eval_.iu_name, 67 failure_kind: eval_.verdict, 68 affected_iu_ids: affected.map(a => a.iu_id), 69 actions, 70 }); 71 } 72 73 return events; 74} 75 76/** 77 * Get all IU IDs that are transitively affected by a failure. 78 */ 79export function getTransitiveDependents( 80 iuId: string, 81 ius: ImplementationUnit[], 82): string[] { 83 const dependents = new Map<string, string[]>(); 84 for (const iu of ius) { 85 for (const depId of iu.dependencies) { 86 const list = dependents.get(depId) ?? []; 87 list.push(iu.iu_id); 88 dependents.set(depId, list); 89 } 90 } 91 92 const visited = new Set<string>(); 93 const queue = [iuId]; 94 while (queue.length > 0) { 95 const current = queue.pop()!; 96 if (visited.has(current)) continue; 97 visited.add(current); 98 for (const dep of (dependents.get(current) ?? [])) { 99 queue.push(dep); 100 } 101 } 102 103 visited.delete(iuId); // don't include the source 104 return [...visited]; 105}