#!/usr/bin/env python3
import hashlib,json,re,sys
from pathlib import Path
PROJECT='gacmai_20260716_233054'

def fail(es):
 print(json.dumps({'status':'failed','errors':es},ensure_ascii=False,indent=2));raise SystemExit(1)
def main(story_path,outline_path):
 sp,op=Path(story_path),Path(outline_path);s=json.loads(sp.read_text());o=json.loads(op.read_text());es=[]
 if s.get('project_id')!=PROJECT:es.append('project mismatch')
 if s.get('outline_sha256')!=hashlib.sha256(op.read_bytes()).hexdigest():es.append('outline checksum mismatch')
 parts={x['part']:'\n'.join(x['paragraphs']) for x in s.get('parts',[])}
 if set(parts)!=set(range(1,13)):es.append('parts mismatch')
 evidence=[]
 for e in o['evidence']:
  eid=e['id'];setup=e['setup_part'];pres=e['preservation_part'];pay=e['payoff_part']
  # Writer ledgers are the deterministic occurrence inventory; narration semantics remain independently audited.
  ledgers=[]
  for src in s.get('sources',[]):
   bp=Path(src['path'])
   if bp.exists():
    bd=json.loads(bp.read_text());ledgers.append(bd.get('evidence_ledger',{}))
  merged={}
  for l in ledgers:
   if eid in l:merged.setdefault(eid,[]).append(l[eid])
  if not merged.get(eid):es.append(f'{eid} absent from batch evidence ledgers')
  evidence.append({'id':eid,'setup_part':setup,'preservation_part':pres,'payoff_part':pay,'ledger_entries':merged.get(eid,[])})
 text='\n'.join(parts.values()).lower()
 overclaim_patterns=[r'khẳng định chắc chắn .* (tráo|gây)',r'chứng minh duy nhất .* thủ phạm',r'không thể là ai khác']
 hits=[]
 for pat in overclaim_patterns:
  for m in re.finditer(pat,text):hits.append({'pattern':pat,'excerpt':text[max(0,m.start()-80):m.end()+120]})
 if hits:es.append(f'direct overclaim patterns: {len(hits)}')
 # Authority names/scopes must all remain represented in P10-P12, without collapsing to one omnipotent body.
 tail='\n'.join(parts.get(i,'') for i in (10,11,12)).lower()
 authority_presence=[]
 for a in o['authority_scopes']:
  name=a['authority'].lower()
  aliases=[name]+[str(x).lower() for x in a.get('aliases',[]) if str(x).strip()]
  matched=[x for x in dict.fromkeys(aliases) if x in tail]
  present=bool(matched);authority_presence.append({'authority':a['authority'],'present':present,'matched_aliases':matched})
  if not present:es.append(f'authority absent from P10-P12: {a["authority"]}')
 out={'status':'passed' if not es else 'failed','story_sha256':hashlib.sha256(sp.read_bytes()).hexdigest(),'evidence':evidence,'direct_overclaim_hits':hits,'authority_presence':authority_presence,'errors':es}
 print(json.dumps(out,ensure_ascii=False,indent=2))
 if es:raise SystemExit(1)
if __name__=='__main__':
 if len(sys.argv)!=3:fail(['usage: auditor STORY OUTLINE'])
 main(sys.argv[1],sys.argv[2])
