#!/usr/bin/env python3
from __future__ import annotations
import datetime, hashlib, json, os, sys, tempfile
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
MANIFEST=ROOT/'script/project-manifest.json'
KEYS=['story_brief','characters','ledger','outline','identity_registry','reveal_ledger']
def sha(p):
 h=hashlib.sha256()
 with p.open('rb') as f:
  for b in iter(lambda:f.read(8*1024*1024),b''):h.update(b)
 return h.hexdigest()
def resolve(m,k):
 v=m['active_paths'].get(k)
 if not v: raise RuntimeError(f'missing active_paths.{k}')
 p=Path(v) if Path(v).is_absolute() else ROOT/v
 p.resolve().relative_to(ROOT.resolve());return p
def atomic(p,obj):
 p.parent.mkdir(parents=True,exist_ok=True);data=(json.dumps(obj,ensure_ascii=False,indent=2)+'\n').encode()
 fd,n=tempfile.mkstemp(dir=p.parent,prefix='.'+p.name+'.')
 try:
  with os.fdopen(fd,'wb') as f:f.write(data);f.flush();os.fsync(f.fileno())
  os.replace(n,p)
 finally:
  if os.path.exists(n):os.unlink(n)
def main():
 m=json.loads(MANIFEST.read_text());paths={k:resolve(m,k) for k in KEYS};out=resolve(m,'authority_receipt')
 values={k:json.loads(p.read_text(encoding='utf-8')) for k,p in paths.items()}
 title=values['story_brief'].get('canonical_title')
 checks={
 'json_parse':True,
 'all_locked':all(v.get('status')=='locked' for v in values.values()),
 'title_consistency':title==m.get('story_title')==values['identity_registry'].get('canonical_title')==values['reveal_ledger'].get('canonical_title'),
 'target_range':values['story_brief'].get('initial_target_words')==[10500,12500] and values['story_brief'].get('target_minutes')==[40,60],
 'cast_complete':len(values['characters'].get('characters',[]))>=6 and len(values['identity_registry'].get('entities',[]))>=6,
 'architecture_complete':len(values['outline'].get('beats',[]))==24 and len(values['ledger'].get('open_loops',[]))<=3 and len(values['ledger'].get('foreshadow_reveal_payoff',[]))>=3 and bool(values['ledger'].get('timeline')) and bool(values['ledger'].get('knowledge')) and bool(values['ledger'].get('relationship')) and bool(values['ledger'].get('props')),
 'cold_open_catchup':values['outline'].get('cold_open_contract',{}).get('actual_timeline_beat')==21,
 'agency_consent':bool(values['characters'].get('consent_contract')) and all(c.get('competence') and c.get('boundary') and c.get('change_proof') for c in values['characters']['characters'][:2]),
 'originality_design':len(values['story_brief'].get('originality_differentiators',[]))>=5,
 'fictional_world':len(values['identity_registry'].get('fictional_places',[]))>=4,
 }
 verified=all(checks.values());r={'status':'completed' if verified else 'failed','verified':verified,'canonical_title':title,'hashes':{str(p.relative_to(ROOT)):sha(p) for p in paths.values()},'checks':{k:{'verified':v,'value':v} for k,v in checks.items()},'blockers':[k for k,v in checks.items() if not v],'checked_at':datetime.datetime.now(datetime.timezone.utc).isoformat()}
 atomic(out,r);print(json.dumps({'status':r['status'],'verified':verified,'blockers':r['blockers'],'receipt':str(out)},ensure_ascii=False));return 0 if verified else 1
if __name__=='__main__':
 try:sys.exit(main())
 except Exception as e:print(json.dumps({'status':'failed','verified':False,'error':f'{type(e).__name__}: {e}'},ensure_ascii=False),file=sys.stderr);sys.exit(1)
