#!/usr/bin/env python3 from __future__ import annotations import json import sys from pathlib import Path import yaml ROOT = Path(__file__).resolve().parent.parent def main() -> int: graph_path = ROOT / "spec" / "routing" / "decision_graph.yaml" if not graph_path.exists(): print(f"Decision graph spec missing: {graph_path}") return 1 try: graph_data = yaml.safe_load(graph_path.read_text(encoding="utf-8")) or {} except Exception as e: print(f"Failed to parse decision graph: {e}") return 1 nodes = graph_data.get("nodes", []) edges = graph_data.get("edges", []) # Build adjacency list adj = {} for node in nodes: nid = node.get("id") adj[nid] = [] for edge in edges: if len(edge) == 2: u, v = edge[0], edge[1] if u in adj and v in adj: adj[u].append(v) else: # If nodes are not declared, dynamically add them if u not in adj: adj[u] = [] if v not in adj: adj[v] = [] adj[u].append(v) errors = [] # Check topological sort order in_degree = {n: 0 for n in adj} for u in adj: for v in adj[u]: in_degree[v] += 1 # Find nodes with 0 in-degree queue = [n for n in adj if in_degree[n] == 0] topo_order = [] while queue: curr = queue.pop(0) topo_order.append(curr) for v in adj.get(curr, []): in_degree[v] -= 1 if in_degree[v] == 0: queue.append(v) # If topological sort is not successful (has cycle), fail if len(topo_order) != len(adj): errors.append("Decision graph contains a cycle") gate_passed = False else: anti_chase_idx = -1 if "anti_chase" in topo_order: anti_chase_idx = topo_order.index("anti_chase") else: errors.append("anti_chase node not found in graph") target_nodes = ["regime", "sector_beta", "style", "sizing", "execution"] if anti_chase_idx != -1: for t in target_nodes: if t in topo_order: t_idx = topo_order.index(t) if anti_chase_idx >= t_idx: errors.append(f"anti_chase (index {anti_chase_idx}) does not precede {t} (index {t_idx})") else: # Missing target node is a failure errors.append(f"Target node {t} not found in topological order") gate_passed = len(errors) == 0 result = { "formula_id": "DECISION_GRAPH_PRECEDENCE_VALIDATOR_V1", "topo_order": topo_order, "errors": errors, "gate": "PASS" if gate_passed else "FAIL" } # Write output to Temp out_dir = ROOT / "Temp" out_dir.mkdir(parents=True, exist_ok=True) out_path = out_dir / "decision_graph_precedence_validation_v1.json" out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(result, ensure_ascii=True, indent=2)) return 0 if gate_passed else 1 if __name__ == "__main__": sys.exit(main())