Reference implementation for the Phoenix Architecture. Work in progress.
aicoding.leaflet.pub/
ai
coding
crazy
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}