"""Offline verification: python artifacts/scripts/verify.py [--without-manifest].
Checks the integrity manifest, reconciles every accepted receipt's original usage object with the
published counters, re-derives the pause-test ranking, the identical-content cost model (Sections 6-7)
and the headline numbers of the manuscript, and checks the manuscript structure."""
import json,hashlib,sys,re
from decimal import Decimal
from pathlib import Path
ROOT=Path(__file__).resolve().parents[2];checks=0

def check(ok,message):
 global checks
 if not ok:raise AssertionError(message)
 checks+=1

def read(p):return json.loads(p.read_text(encoding='utf8'))
if '--without-manifest' not in sys.argv:
 lines=(ROOT/'SHA256SUMS.txt').read_text().splitlines();listed=set()
 for line in lines:
  digest,name=line.split('  ',1);file=(ROOT/name).resolve();check(file.is_relative_to(ROOT),'unsafe manifest path');check(file.is_file(),'missing '+name);check(hashlib.sha256(file.read_bytes()).hexdigest()==digest,'SHA mismatch '+name);listed.add(name)
 check(len(listed)==len(lines),'duplicate manifest entries')
 actual={p.relative_to(ROOT).as_posix() for p in ROOT.rglob('*') if p.is_file() and p.name!='SHA256SUMS.txt' and not any(x in p.relative_to(ROOT).parts for x in ['build','qa','rebuild','__pycache__']) and p.suffix not in ['.aux','.log','.out']}
 check(listed==actual,'unexpected or unlisted release file')
D=read(ROOT/'artifacts/data/measurements.json');check(len(D['series'])==13,'13 configurations');total=0;S={s['id']:s for s in D['series']}
for s in D['series']:
 w=read(ROOT/f"artifacts/data/workload-{s['track']}.json");check(len(s['turns'])==5,'five turns')
 for t in s['turns']:
  total+=1;j=read(ROOT/f"artifacts/receipts/{s['id']}-turn{t['turn']}.json");u=j['usage'];id=s['id'];track=s['track']
  if id.startswith('agnt-'):
   I=u['inputTokens'];R=u['cacheReadTokens'];W=u['cacheCreationTokens'];O=u['outputTokens'];U=I-R-W;w5=u['cacheCreation5mTokens'];w1=u['cacheCreation1hTokens']
   events=j['providerUsage']
   if track=='claude':
    v=next(e['usage'] for e in events if e['type']=='message_start');end=next(e['usage'] for e in events if e['type']=='message_delta');check(I==v['input_tokens']+v['cache_read_input_tokens']+v['cache_creation_input_tokens'],'AGNT raw input');check(R==v['cache_read_input_tokens'] and W==v['cache_creation_input_tokens'] and O==end['output_tokens'],'AGNT raw counters');check(v['cache_creation']['ephemeral_5m_input_tokens']==0 and v['cache_creation']['ephemeral_1h_input_tokens']==W,'AGNT all writes 1h')
   else:
    v=next(e['usage'] for e in events if e['type']=='response.completed');check(I==v['input_tokens'] and R==v['input_tokens_details']['cached_tokens'] and O==v['output_tokens'],'AGNT raw Codex counters')
   check(j['toolCallsCount']==0,'no AGNT tool execution')
  elif id=='claude':
   U=u['input_tokens'];R=u['cache_read_input_tokens'];W=u['cache_creation_input_tokens'];O=u['output_tokens'];I=U+R+W;w5=u['cache_creation']['ephemeral_5m_input_tokens'];w1=u['cache_creation']['ephemeral_1h_input_tokens']
  elif id=='codex-r2':
   I=u['input_tokens'];R=u['cached_input_tokens'];W=u['cache_write_input_tokens'];O=u['output_tokens'];U=I-R-W;w5=w1=0
  elif id.startswith('hermes'):
   U=u['session_input_tokens'];R=u['session_cache_read_tokens'];W=u['session_cache_write_tokens'];O=u['session_output_tokens'];I=U+R+W;w5=W if track=='claude' and s['ttl']=='5m' else 0;w1=W if track=='claude' and s['ttl']=='1h' else 0;check(u['session_api_calls']==1,'one Hermes call')
  else:
   U=u['input'];R=u['cacheRead'];W=u['cacheWrite'];O=u['output'];I=U+R+W;w1=u.get('cttl',{}).get('ephemeral1h',u.get('cacheWrite1h',0));w5=u.get('cttl',{}).get('ephemeral5m',W-w1 if track=='claude' else 0)
   if 'contextUsage' in u:check(u['contextUsage']['promptTokens']==I,'context total')
  for key,val in [('input',I),('cacheRead',R),('cacheWrite',W),('output',O),('uncached',U),('cacheWrite5m',w5),('cacheWrite1h',w1)]:check(isinstance(val,int) and val>=0 and val==t[key],'counter '+key)
  check(I==U+R+W,'input conservation');check(abs(R/I-t['readShare'])<1e-12,'read fraction');check(j['reply']==f"OK{t['turn']}",'guard G1 reply');check(j['model']==('claude-sonnet-5' if track=='claude' else 'gpt-6-astra'),'guard G1 model')
  prompt=w['turns'][t['turn']-1];digest=hashlib.sha256(prompt['text'].encode()).hexdigest();check(digest==prompt['sha256']==t['promptSha256']==j['promptSha256'],'guard G2 prompt SHA');check(len(prompt['text'].encode())==7878,'payload byte count')
  if t['turn']==5:check(t['gapMs']>=390000,'guard G3 pause')
  elif t['turn']>1:check(0<=t['gapMs']<300000,'guard G3 burst')
  if track=='claude' and t['turn']>1:check(I-s['turns'][t['turn']-2]['input'] in range(5264,5306),'guard G3 growth = payload + overhead')
  cost=(Decimal(2)*U+Decimal('.2')*R+Decimal('2.5')*w5+Decimal(4)*w1+Decimal(10)*O)/Decimal(1000000) if track=='claude' else (Decimal(10)*U+R+Decimal('12.5')*W+Decimal(50)*O)/Decimal(1000000)
  check(abs(cost-Decimal(str(t['apiEquivalentUSD'])))<Decimal('1e-12'),'price formula')
 check(abs(sum(t['apiEquivalentUSD'] for t in s['turns'])-s['summary']['apiEquivalentUSD'])<1e-10,'sequence cost')
 check(abs(sum(t['cacheRead'] for t in s['turns'])/sum(t['input'] for t in s['turns'])-s['summary']['weightedReadShare'])<1e-12,'weighted share')
check(total==65,'65 requests')
# Workload identity within each track, distinct across tracks.
for n in range(5):
 for track in ['claude','codex']:check(len({s['turns'][n]['promptSha256'] for s in D['series'] if s['track']==track})==1,'byte-identical payload per track')
 check(S['agnt-claude']['turns'][n]['promptSha256']!=S['agnt-codex']['turns'][n]['promptSha256'],'tracks carry distinct run ids')
# Pause-test partition and ranking.
h=lambda i:S[i]['turns'][4]['cacheRead']/S[i]['turns'][4]['input']
for i in ['omp-r2','openclaw','hermes']:check(S[i]['turns'][4]['cacheRead']==0 and S[i]['turns'][4]['cacheWrite5m']==S[i]['turns'][4]['cacheWrite'],'5m default expired '+i)
for i in ['agnt-claude','claude','omp-long','openclaw-long','hermes-long']:check(h(i)>0.8 and S[i]['turns'][4]['cacheWrite5m']==0,'1h survived '+i)
for track in ['claude','codex']:
 group=[s for s in D['series'] if s['track']==track];winner=max(group,key=lambda s:s['turns'][4]['readShare']);check(winner['name']=='AGNT','primary ranking');check(round(100*winner['turns'][4]['readShare'],2)==(90.45 if track=='claude' else 88.42),'headline number')
# Identical-content cost model (Section 6) re-derived from closed forms.
sc=read(ROOT/'artifacts/figures/scenario.json');c1=sc['identicalContent']['c1'];m=sc['identicalContent']['m'];PR=sc['prices']
check(c1==S['agnt-claude']['turns'][0]['input']-S['agnt-claude']['turns'][0]['uncached'] and m==S['agnt-claude']['turns'][1]['input']-S['agnt-claude']['turns'][0]['input'],'identical-content parameters are measured')
def session(n,ttl,o,burst=4):
 g=m+o
 if ttl=='1h':prem=c1+(n-1)*g;reads=sum(c1+(t-2)*g for t in range(2,n+1));return prem,reads,(PR['write1h']*prem+PR['cacheRead']*reads)/1e6
 prem=c1;reads=0
 for t in range(2,n+1):
  if (t-1)%burst==0:prem+=c1+(t-1)*g
  else:prem+=g;reads+=c1+(t-2)*g
 return prem,reads,(PR['write5m']*prem+PR['cacheRead']*reads)/1e6
control=lambda n:PR['input']*sum(c1+(t-1)*m for t in range(1,n+1))/1e6
strat={'agnt-claude':('1h',0),'claude':('1h',40),'omp-r2':('5m',0),'openclaw':('5m',20),'hermes':('5m',0)}
for i,(ttl,o) in strat.items():
 g=S[i]['turns'][1]['input']-S[i]['turns'][0]['input']-m;check(max(g,0)==o,'overhead measured '+i)
 prem,reads,usd=session(20,ttl,o);check(prem==sc['oneHour'][i]['premium'] and reads==sc['oneHour'][i]['reads'] and abs(usd-sc['oneHour'][i]['usd'])<1e-12,'one-hour projection '+i)
 for (L,n),mult in zip(sc['lengths'],sc['multipliers'][i]):check(abs(session(n,ttl,o)[2]/control(n)-mult)<1e-12,'multiplier '+i+' '+L)
check(abs(control(20)-sc['control20']['usd'])<1e-12 and abs(control(20)-sc['burnRatePerHour'])<1e-12,'control and burn rate')
Ru=sc['burnRatePerHour'];profiles=[(2,1,2),(2,2,3),(2,4,4)]
for i in list(strat)+['none']:
 for p,(sess,L,li) in enumerate(profiles):
  expect=22*sess*(sc['multipliers'][i][li] if i!='none' else 1)*L*Ru;check(abs(expect-sc['monthly'][i][p])<1e-9,'monthly '+i)
 check(abs(5*12*sc['monthly'][i][2]-sc['teamAnnual'][i])<1e-9,'team annual '+i)
for i in ['omp-r2','openclaw','hermes','none']:check(abs(sc['monthly'][i][2]-sc['monthly']['agnt-claude'][2]-sc['cacheTaxHeavy'][i])<1e-9,'cache tax '+i)
check(min(sc['monthly'][i][2] for i in strat)==sc['monthly']['agnt-claude'][2],'AGNT lowest projected cost')
check(all(sc['multipliers']['agnt-claude'][k]>sc['multipliers']['agnt-claude'][k+1] for k in range(4)),'AGNT multiplier monotone decreasing')
check(abs(100000*PR['input']*1.25/1e6-sc['amplifier']['perEvent'])<1e-12 and abs(30*22*sc['amplifier']['perEvent']-sc['amplifier']['perMonth'])<1e-9,'amplifier arithmetic')
r5_1h=session(5,'1h',0)[2];r5_5m=session(5,'5m',0)[2];check(abs(r5_1h-sc['fiveTurn']['1h'][2])<1e-12 and abs(r5_5m-sc['fiveTurn']['5m'][2])<1e-12,'five-turn controlled cost')
# Manuscript structure and headline numbers.
html=(ROOT/'cache-wars-2.html').read_text(encoding='utf8');tex=(ROOT/'cache-wars-2.tex').read_text(encoding='utf8')
check(html.count('<svg ')==13,'thirteen inline charts');check(html.count('<table>')==20,'twenty tables');check(len(re.findall(r'<h2 id="s\d+"',html))==11,'11 numbered sections');check(html.count('class="eq"')==4,'four equations')
check(tex.count(r'\includegraphics')==13 and tex.count(r'\begin{longtable}')==20 and tex.count(r'\section{')==11,'LaTeX mirrors HTML structure')
prose=re.sub(r'<svg\b[\s\S]*?</svg>','',html,flags=re.I);prose=re.sub(r'<[^>]+>',' ',prose)
for needle in ['90.45%','88.42%',f'${sc["oneHour"]["agnt-claude"]["usd"]:.2f} (AGNT)',f'${sc["control20"]["usd"]:.2f} uncached ceiling',f'${sc["monthly"]["agnt-claude"][2]:,.0f}/month (AGNT)','the-cache-wars-prompt-cache-efficiency-llm-agent-harnesses','Original paper [1]','Listing 1','(G1)','(G2)','(G3)']:check(needle in prose or needle in html,'manuscript states '+needle)
check('The Cache Wars 2' in html and 'The Cache Wars 2' in tex,'title')
exec(compile((ROOT/'artifacts/scripts/verify_codex.py').read_text(encoding='utf8'),'verify_codex.py','exec'))
print(json.dumps({'status':'PASS','checks':checks,'requests':65,'configurations':13,'figures':13,'tables':20,'scope':'manifest integrity, original usage reconciliation, guards, workloads, prices, pause-test ranking, identical-content cost model, headline numbers and manuscript structure'},indent=2))
