#!/usr/bin/env python3 """Route-Repair Loop v1.1: Failure Flexure Expansion + full 13-dim v2 features.""" import hashlib, json, math, os, re, subprocess, sys, time, uuid from collections import Counter, defaultdict from pathlib import Path sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".")) from lean_trace_bridge_v2 import instrument_theorem, prove from failure_flexure_bank import FAILURE_THEOREMS WORKER_URL = os.environ.get("CANARY_WORKER_URL", "http://100.110.163.82:8787") PROOF_SERVER_TOKEN = os.environ.get("PROOF_SERVER_TOKEN", "") if not PROOF_SERVER_TOKEN: tf = os.environ.get("PROOF_SERVER_TOKEN_FILE", os.path.expanduser("~/.config/ene/language-proof-server.token")) try: PROOF_SERVER_TOKEN = Path(tf).read_text().strip() except: pass V2_FEATURES = ["matrix_size", "rank", "spectral_gap", "laplacian_zero_count", "density", "adjacency_eigenvalue_max", "adjacency_eigenvalue_second", "laplacian_eigenvalue_max", "laplacian_eigenvalue_min", "singular_value_max", "trace", "frobenius_norm"] OBSTRUCTION_MAP = {"rw_missing_dir": "missing_rewrite_direction","missing_assume": "missing_assumption_bridge", "arith_gap": "arithmetic_gap","case_split": "case_split_missing","induction_gap": "induction_incomplete", "simplifier_gap": "simplifier_gap","coercion_gap": "coercion_mismatch","order_gap": "order_inequality_gap"} def obstruction_type(name): for p, l in OBSTRUCTION_MAP.items(): if name.startswith(p): return l return "other" def classify_tactic(t): tl=t.lower() if "rw" in tl: return "rewrite" if "simp" in tl: return "normalization" if "omega" in tl or "nlinarith" in tl: return "arithmetic" if "induction" in tl: return "induction" if "cases" in tl or "constructor" in tl: return "case_analysis" if "apply" in tl or "exact" in tl: return "discharge" if "intro" in tl: return "introduction" if "have" in tl: return "lemma_introduction" if "rfl" in tl: return "reflexivity" return "unknown" def compute_spectral(matrix): n=len(matrix) if n==0: return {} sym=[[(matrix[i][j]+matrix[j][i])/2 for j in range(n)] for i in range(n)] lap=[[sum(sym[i]) if i==j else -sym[i][j] for j in range(n)] for i in range(n)] def pe(m): v=[1.0/math.sqrt(n)]*n for _ in range(100): vn=[sum(m[i][j]*v[j] for j in range(n)) for i in range(n)] nm=math.sqrt(sum(x*x for x in vn)) v=[x/nm for x in vn] if nm>0 else v num=sum(v[i]*sum(m[i][j]*v[j] for j in range(n)) for i in range(n)) return num/max(sum(v[i]*v[i] for i in range(n)),1e-12) sm=pe(sym); lm=pe(lap) sh=[[sym[i][j]-0.9*sm*(i==j) for j in range(n)] for i in range(n)] sm2=pe(sh); gap=sm-max(0,sm-sm2) ev_second=sm-max(0,sm-sm2) neg=[[-lap[i][j] for j in range(n)] for i in range(n)] nm=pe(neg) ata=[[sum(matrix[k][i]*matrix[k][j] for k in range(n)) for j in range(n)] for i in range(n)] sva=math.sqrt(max(0,pe(ata))) rank=sum(1 for row in matrix if sum(row)>0) total=sum(sum(r) for r in matrix) frob=math.sqrt(sum(cell*cell for row in matrix for cell in row)) lap0=sum(1 for i in range(n) if abs(sum(matrix[i])-matrix[i][i])<1e-9) return {"matrix_size":n,"rank":rank,"spectral_gap":round(gap,6),"density":round(total/max(n*n,1),6), "trace":sum(matrix[i][i] for i in range(n)),"frobenius_norm":round(frob,6), "laplacian_zero_count":lap0,"adjacency_eigenvalue_max":round(sm,6), "adjacency_eigenvalue_second":round(ev_second,6), "laplacian_eigenvalue_max":round(lm,6),"laplacian_eigenvalue_min":round(-nm,6), "singular_value_max":round(sva,6)} def build_trace(name, code): try: instr,tags=instrument_theorem(code) if not tags: return None resp=prove(instr,name+"_flex") stdout=resp.get("stdout","") or "" found=[l.split("@@PIST_TRACE_JSON@@")[1].strip() for l in stdout.split("\n") if "@@PIST_TRACE_JSON@@" in l] if not found: return None hs=[hashlib.sha256(t.encode()).hexdigest()[:16] for t in found] uniq=list(dict.fromkeys(hs)); n=len(uniq) mat=[[0]*n for _ in range(n)] hi={h:i for i,h in enumerate(uniq)} for i in range(len(hs)-1): if hs[i] in hi and hs[i+1] in hi: mat[hi[hs[i]]][hi[hs[i+1]]]+=1 sp=compute_spectral(mat) sp.update({"name":name,"status":"failed","obstruction":obstruction_type(name), "tactic":code.split("by")[-1].strip() if "by" in code else "unknown","code":code}) return sp except: return None def connect(): host=os.environ.get("RDS_HOST","database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com") port=os.environ.get("RDS_PORT","5432"); user=os.environ.get("RDS_USER","postgres") db=os.environ.get("RDS_DB","postgres") token=os.environ.get("RDS_IAM_TOKEN","") if not token: token=subprocess.check_output(["aws","rds","generate-db-auth-token","--region",os.environ.get("AWS_REGION","us-east-1"), "--hostname",host,"--port",port,"--username",user],text=True).strip() import psycopg2 return psycopg2.connect(host=host,port=port,user=user,password=token,dbname=db,sslmode="require") def ingest_failure_flexures(results): conn=connect(); cur=conn.cursor() sid=str(uuid.uuid4()) cur.execute("INSERT INTO ene.sessions(id,title,event_type,content,metadata) VALUES(%s,%s,'failure_flexure','V1.1 Failure',%s::jsonb)", (sid,"Failure Flexure v1.1",json.dumps({"source":"failure_flexure_bank","count":len(results)}))) for r in results: fid=str(uuid.uuid4()); tf=classify_tactic(r.get("tactic","")) sp={k:r.get(k,0) for k in V2_FEATURES} sig=json.dumps({"tactic":r.get("tactic","?"),"tactic_family":tf,"delta_score":abs(r.get("gap",r.get("spectral_gap",0)))*10, "joint_label":f"{tf}_failed_{r.get('obstruction','?')}","obstruction_type":r.get("obstruction","?"), "domain":"failure","proof_method":tf,"rrc_shape":"HoldForUnlawfulOrUnderspecifiedShape", "spectral":sp,"feature_version":"flexure-spectrum-v2"}) sd=int(hashlib.sha256(r.get("name","?").encode()).hexdigest()[:4],16)%255 cur.execute("INSERT INTO ene.flexures(id,session_id,step_index,pre_sidon_label,pre_residual,available_crossings,chosen_crossing,decision_signals,post_sidon_label,post_residual,converged) VALUES(%s,%s,%s,%s,%s,%s::jsonb,%s::jsonb,%s::jsonb,%s,%s,%s)", (fid,sid,0,sd,0.5,"[]",json.dumps({"name":r.get("name","?")}),sig,sd%255,0.5,False)) conn.commit(); cur.close(); conn.close() return sid def route_repair(name, code, library_session, failure_session, max_attempts=5): resp=prove(code,name+"_init") if resp.get("ok",False): return {"name":name,"initial_status":"verified","recovered":False,"notes":"already verified"} instr,tags=instrument_theorem(code) if tags: hs=[hashlib.sha256(t.encode()).hexdigest()[:16] for t in tags] uniq=list(dict.fromkeys(hs)); n=len(uniq) mat=[[0]*n for _ in range(n)] hi={h:i for i,h in enumerate(uniq)} for i in range(len(hs)-1): if hs[i] in hi and hs[i+1] in hi: mat[hi[hs[i]]][hi[hs[i+1]]]+=1 else: mat=[[1]] sp=compute_spectral(mat) vec=[sp.get(k,0) for k in V2_FEATURES] conn=connect(); cur=conn.cursor() cur.execute("SELECT decision_signals FROM ene.flexures WHERE session_id=%s OR session_id=%s",(library_session,failure_session)) library=[] for row in cur.fetchall(): sig=json.loads(row[0]) if isinstance(row[0],str) else row[0] sl=sig.get("spectral",{}); lv=[sl.get(k,0) for k in V2_FEATURES] library.append({"features":lv,"tf":sig.get("tactic_family","?"),"obs":sig.get("obstruction_type","?"),"jl":sig.get("joint_label","?")}) cur.close(); conn.close() scored=[(math.sqrt(sum((vec[i]-l["features"][i])**2 for i in range(len(vec)))),l) for l in library if len(l["features"])==len(vec)] scored.sort(key=lambda x:x[0]) top3=scored[:3] votes=Counter(lib["obs"] for _,lib in top3) predicted_obs=votes.most_common(1)[0][0] if votes else "other" # Map obstruction type to patch family obs_to_family = {"missing_rewrite_direction":"rewrite","missing_assumption_bridge":"discharge", "arithmetic_gap":"arithmetic","case_split_missing":"case_analysis", "induction_incomplete":"induction","simplifier_gap":"normalization", "coercion_mismatch":"normalization","order_inequality_gap":"arithmetic"} predicted_family = obs_to_family.get(predicted_obs, "normalization") # Extract actual hypothesis names from the theorem hyps = re.findall(r'\(([^)]+:\s*[^)]+)\)', code) hyp_names = [] for h in hyps: parts = h.split(":") names_part = parts[0].strip() for n in names_part.split(): if n.strip(): hyp_names.append(n.strip()) first_hyp = hyp_names[0] if hyp_names else "h" pc=[] if predicted_family=="rewrite": pc+=["rw ["+first_hyp+"]","rw [← "+first_hyp+"]"] elif predicted_family=="discharge": pc+=["exact "+first_hyp,"assumption","apply "+first_hyp] elif predicted_family=="normalization": pc+=["simp","norm_num"] elif predicted_family=="arithmetic": pc+=["omega"] elif predicted_family=="case_analysis": pc+=["cases h","constructor"] elif predicted_family=="induction": pc+=["induction n"] else: pc+=["simp","omega"] init_vars=code.count("("); init_ops=sum(1 for c in code if c in "+-*/^∧∨→¬∀∃≤≥") attempts=[]; best_delta=-999; best_attempt=None; recovered=False for i,patch in enumerate(pc[:max_attempts]): patched=code.split(":=")[0]+":=" if ":=" in code else code patched+="\n "+patch r=prove(patched,f"{name}_repair_{i}") ok=r.get("ok",False) av=patched.count("("); ao=sum(1 for c in patched if c in "+-*/^∧∨→¬∀∃≤≥") delta=(init_vars+init_ops)-(av+ao) if delta>best_delta: best_delta=delta; best_attempt={"patch":patch,"delta":delta,"ok":ok} if ok: recovered=True; best_attempt={"patch":patch,"delta":delta,"ok":ok}; break attempts.append({"attempt":i+1,"patch":patch,"family":predicted_family,"ok":ok,"delta":delta}) return {"name":name,"obstruction":obstruction_type(name),"initial_status":"failed", "predicted_family":predicted_family,"recovered":recovered,"best_delta":best_delta, "partial_improvement":best_delta>0,"attempts":attempts,"best_attempt":best_attempt} def main(): print("V1.1: Failure Flexure Expansion + 13-dim v2 features\n") traced=[] for i,(n,c) in enumerate(FAILURE_THEOREMS): print(f" [{i+1}/{len(FAILURE_THEOREMS)}] {n:35s} ... ",end="",flush=True) r=build_trace(n,c) if r is None: print("SKIP") else: traced.append(r) print(f"n={r.get('matrix_size','?'):2d} obs={r.get('obstruction','?')[:20]}") print(f"\n Traced: {len(traced)}/{len(FAILURE_THEOREMS)}") failure_session=ingest_failure_flexures(traced) print(f" Failure session: {failure_session}") combined_session="a4a0eb20-93fe-413e-8e0b-50334bb778d8" test_set=FAILURE_THEOREMS[:30] results=[] rec=part=worse=0; td=0; dc=0 for i,(n,c) in enumerate(test_set): print(f" [{i+1}/{len(test_set)}] {n:35s} ... ",end="",flush=True) r=route_repair(n,c,combined_session,failure_session) if r["initial_status"]=="verified": print("already verified"); continue s="RECOVERED" if r["recovered"] else "improved" if r.get("partial_improvement") else "no change" print(f"{s:15s} pred={r['predicted_family']:12s} delta={r['best_delta']:+d}") results.append(r) if r["recovered"]: rec+=1 if r.get("partial_improvement"): part+=1 if r["best_delta"]<0: worse+=1 td+=r["best_delta"]; dc+=1 n=len(results); ad=td/max(dc,1) print(f"\n{'='*60}\nROUTE-REPAIR V1.1\n{'='*60}") print(f"Test set: {n} failed | Recovered: {rec} ({rec/max(n,1):.0%}) | Partial: {part} ({part/max(n,1):.0%}) | Avg ΔC: {ad:.2f}") by_obs=defaultdict(lambda:{"t":0,"r":0,"p":0,"d":0}) for r in results: o=r.get("obstruction","?"); by_obs[o]["t"]+=1 if r["recovered"]: by_obs[o]["r"]+=1 if r.get("partial_improvement"): by_obs[o]["p"]+=1 by_obs[o]["d"]+=r["best_delta"] print(f"\nPer-obstruction:") for o,s in sorted(by_obs.items(),key=lambda x:-x[1]["t"]): print(f" {o:30s}: n={s['t']:2d} rec={s['r']/max(s['t'],1):.0%} part={s['p']/max(s['t'],1):.0%} ΔC={s['d']/max(s['t'],1):+.1f}") rp={"n":n,"recovered":rec,"partial_improvement":part,"avg_delta":round(ad,2), "failure_session_id":failure_session,"combined_session_id":combined_session,"results":results} rp_path=os.path.join(os.path.dirname(__file__),"../..","shared-data/pist_route_repair_v11_benchmark.json") with open(rp_path,"w") as f: json.dump(rp,f,indent=2) print(f"\nReport: {rp_path}") if __name__=="__main__": main()