import copy,hashlib,json,re,sys
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
here=Path(__file__).resolve().parent
private=here/'private-evidence'
assert all(arg=='--verify-only' for arg in sys.argv[1:]),'Only --verify-only is supported'
verify_only='--verify-only' in sys.argv[1:]
if verify_only and (private/'final-manifest-v2.json').exists():
 sealed=load_manifest=json.loads((private/'final-manifest-v2.json').read_text())
 for name,value in sealed.items():
  sealed_file=private/name;assert sealed_file.stat().st_size==value['bytes'] and hashlib.sha256(sealed_file.read_bytes()).hexdigest()==value['sha256'],('Final v2 seal drift',name)
checks=0
def check(ok,label):
 global checks
 checks+=1
 assert ok,label
def load(path):return json.loads(path.read_text())
original=load(private/'original-manifest.json')
initial_source=load(private/'source-sha.json');final_source=initial_source.copy();final_source['p/logic/NutritionTime.php']='cd7efdf847ed2c4ee9acdc4504875fa51cc63e38dca167855660bb8a99bb47b3';final_source['p/logic/Nutrition.php']='42e491f372cd78ece0921fb1157364e9bad8dc321f75ba406550c32655f416b4'
for name,v in original.items():
 f=private/name;check(f.stat().st_size==v['bytes'] and hashlib.sha256(f.read_bytes()).hexdigest()==v['sha256'],'Original seal unchanged '+name)
messages={1:'服用时间新建成功',2:'编辑营养品成功',3:'营养品分类新建成功',4:'编辑分类成功',5:'服用时间已被应用，不能删除',6:'更改状态成功',7:'状态修改失败',8:'更改状态成功',9:'修改失败',10:'删除分类成功',11:'删除营养品成功'}
protected=['nutrition_category','nutrition_template','nutrition_template_ext','nutrition_station','nutrition_station_ext','prior_product','actor_sha256']
def native_writes(wire):return [s for s in wire['prepared_SQL'] if s.get('op') in ['INSERT','UPDATE']]
numeric={'id','order_no','status','number','del_flag','is_del'}
def stored_equal(actual,expected,lane,field):
 if field in numeric:
  if lane=='B':assert type(actual) is str and actual==str(expected) and re.fullmatch(r'0|[1-9][0-9]*',actual)
  else:assert type(actual) is int and actual==int(expected)
 else:assert type(actual) is type(expected) and actual==expected
def creation_window(row,wire,label):
 value=row['create_time'];assert type(value) is str
 if wire['lane']=='C':assert re.fullmatch(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.000000',value);value=value[:-7]
 else:assert re.fullmatch(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}',value)
 actual=datetime.strptime(value,'%Y-%m-%d %H:%M:%S').replace(tzinfo=ZoneInfo('Asia/Shanghai')).timestamp()
 if label.startswith('E'):
  timing=load(private/'extra-timing-evidence.json')['cases'][label];start=timing['private-sent-'+label]['mtime_epoch'];end=timing['private-case-'+label+'.wire.json']['mtime_epoch']
 else:start=wire['began_epoch'];end=wire['ended_epoch']
 assert type(start) is int and type(end) is int and start<=end and start-1<=actual<=end+1
 return value
def verify_wire(wire):
 lane=wire['lane'];case=wire['case'];step=case['step'];label=case.get('label',str(step));extra=label.startswith('E');retest=label.startswith('R')
 assert wire['error'] is None and wire['HTTP']==200
 result=json.loads(wire['body']);assert list(result)==['code','message','data'] and result['data']==[]
 original_red=lane=='C' and label in ['7','9'];expected_code=0 if extra or original_red else (1 if step in [5,7,9] else 0)
 expected_message='更改状态成功' if original_red else messages[step]
 assert result=={'code':expected_code,'message':expected_message,'data':[]}
 before,after=wire['before'],wire['after']
 for key in protected:assert before[key]==after[key]
 for table in ['nutrition_time','nutrition_set']:
  natural=lambda state:[r for r in state[table] if not r['name'].startswith('CDXNW11D6A2-')]
  assert natural(before)==natural(after)
 writes=native_writes(wire);assert len(writes)==(0 if step==5 else 1)
 for statement in writes:
  assert statement['ok'] is True and 'error' not in statement
  op=statement['op'];sql=statement['queryString'].lower();assert not re.search(r';|--|/\*',sql)
  assert ('ydy_'+case['table']) in sql
  assert all(k in statement for k in ['bindings','native_rowCount','execute_parameters'])
  if op=='UPDATE':assert 'uuid' in sql and re.search(r'\bwhere\b',sql)
  fields=case['params'].copy() if op=='INSERT' or step in [2,4] else ({'status':'0'} if step in [6,7,8,9] else ({'is_del':0} if step==10 else {'del_flag':1}))
  if step==4:fields.pop('uuid')
  for field,value in fields.items():
   bound=statement['bindings'][':data__'+field];assert type(bound['value']) is type(value) and bound['value']==value and bound['type']==(1 if field in numeric else 2)
  if op=='UPDATE':
   where=[b for key,b in statement['bindings'].items() if key.lower().startswith(':where_') and 'uuid' in key.lower()];assert len(where)==1 and where[0]=={'value':case['params']['uuid'],'type':2}
  elif op=='INSERT':
   target=next(r for r in after[case['table']] if r['name']==case['params']['name']);date=creation_window(target,wire,label)
   for field in ['uuid','create_by','create_time']:
    expected=date if field=='create_time' else target[field];assert statement['bindings'][':data__'+field]=={'value':expected,'type':2}
  if retest:
   assert statement['native_rowCount']==0
   if lane=='C':assert re.search(r'status[`" ]*<>',sql)
   else:assert '<>' not in sql
 if step in [5,7,9]:assert before==after
 elif step in [2,4,6,8,10,11] and not extra:
  fields=set(case['params'])-{'uuid'} if step in [2,4] else ({'status'} if step in [6,8] else ({'is_del'} if step==10 else {'del_flag'}))
  target=case['params']['uuid'];table=case['table'];b=[r for r in before[table] if r['uuid']==target];a=[r for r in after[table] if r['uuid']==target];assert len(b)==len(a)==1
  expected=b[0].copy()
  desired={f:case['params'][f] for f in fields} if step in [2,4] else ({'status':0} if step in [6,8] else ({'is_del':0} if step==10 else {'del_flag':1}))
  for f in fields:stored_equal(a[0][f],desired[f],lane,f);expected[f]=a[0][f]
  assert expected==a[0]
  bt=copy.deepcopy(before);bt[table]=[a[0] if r['uuid']==target else r for r in bt[table]];assert bt==after
 elif step in [1,3] or label=='E1':
  table=case['table'];fresh=[r for r in after[table] if r not in before[table]];assert len(fresh)==1
  row=fresh[0];assert re.fullmatch(r'[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}',row['uuid']) and int(row['id'])>0;stored_equal(row['id'],int(row['id']),lane,'id')
  assert len(row)==(9 if table=='nutrition_time' else 10)
  for f,v in case['params'].items():stored_equal(row[f],v,lane,f)
  assert hashlib.sha256(row['create_by'].encode()).hexdigest()=='efcac9c3ed28940cbd075c4b8bdd20c5a6165984c1f413546f9620b5e8b9d044'
  stored_equal(row['status'],1,lane,'status');creation_window(row,wire,label)
  if table=='nutrition_time':stored_equal(row['del_flag'],0,lane,'del_flag')
  else:
   stored_equal(row['is_del'],1,lane,'is_del');stored_equal(row['number'],0,lane,'number');times=[r for r in before['nutrition_time'] if r['name'].startswith('CDXNW11D6A2-')];assert len(times)==1 and row['use_time']==case['params']['use_time']==times[0]['uuid']
  expected=copy.deepcopy(before);expected[table].append(row);assert expected==after
 elif label=='E2':
  table='nutrition_time';uuid=case['params']['uuid'];b=next(r for r in before[table] if r['uuid']==uuid);a=next(r for r in after[table] if r['uuid']==uuid);expected=b.copy();expected['del_flag']=a['del_flag'];stored_equal(a['del_flag'],1,lane,'del_flag');stored_equal(a['status'],1,lane,'status');assert expected==a
  whole=copy.deepcopy(before);whole[table]=[a if r['uuid']==uuid else r for r in whole[table]];assert whole==after
 return label
summary={'HTTP':28,'HTTP_by_lane':{'B':15,'C':13},'unique_methods':8,'B1':'EVIDENCE_INCOMPLETE','original_business_red':[{'lane':'C','step':7},{'lane':'C','step':9}],'retest_HTTP':4,'post_retest_softdelete_HTTP':4,'B_extra_HTTP':2,'physical_DELETE':0,'DDL':0,'CLI_prepare_DML':0,'retained_rows':{'B':{'Time':2,'Set':1},'C':{'Time':1,'Set':1}},'API_INSERT':{'B':3,'C':2},'UPDATE_statements':{'B':11,'C':10},'native_preserved':{},'prepared_statements':{},'oracle_SELECT_queries':{}}
samples=[]
for lane in ['B','C']:
 target=private/(lane+'-execution');wires=sorted(target.glob('private-case-*.wire.json'));check(len(wires)==(14 if lane=='B' else 13),'Exact final complete wires')
 count={'INSERT':0,'UPDATE':0};prepared=0;oracle=0
 for file in wires:
  wire=load(file);label=verify_wire(wire);check(True,'Wire proof '+lane+'/'+label);samples.append(wire)
  check(wire['source_sha']==(final_source if label.startswith(('R','E')) or label in ['10','11'] else initial_source),'Exact original/final source pin')
  body=(target/('private-case-'+label+'.httpbody')).read_bytes();check(body==wire['body'].encode() and hashlib.sha256(body).hexdigest()==wire['body_sha256'],'Exact final raw body')
  native=target/('private-case-'+label+'.native.jsonl');journal=[json.loads(line) for line in native.read_text().splitlines()];check([j['ordinal'] for j in journal]==list(range(len(wire['prepared_SQL']))),'Native immediate journal complete');check([{k:v for k,v in j.items() if k!='ordinal'} for j in journal]==wire['prepared_SQL'],'Exact immediate native journal')
  for s in native_writes(wire):count[s['op']]+=1
  prepared+=len(wire['prepared_SQL']);oracle+=len(wire['oracle_native_SELECT'])
 for label in ['R7','R9']:
  final=(target/('private-case-'+label+'.httpbody')).read_bytes();b=(private/'B-execution'/('private-case-'+label[1:]+'.httpbody')).read_bytes();check(final==b,'Retest exact original MySQL body '+lane+'/'+label)
 summary['native_preserved'][lane]=count;summary['prepared_statements'][lane]=prepared;summary['oracle_SELECT_queries'][lane]=oracle
 final=load(target/('private-final-write-extra.json' if lane=='B' else 'private-final-write-main.json'));check(final['passed'] is True,'Independent final proof');source=final['source']
 check(source==load(target/('private-case-E2.wire.json' if lane=='B' else 'private-case-11.wire.json'))['after'],'Full final snapshot equals last complete native readback')
 if lane=='B':check(final['main']==load(target/'private-case-11.wire.json')['after'],'Main snapshot remains exact through extra evidence probe')
 base=load(private/('B-private-protected-source.json' if lane=='B' else 'private-protected-source.json'))
 for key in protected:check(source[key]==base[key],'Final complete protected '+key)
 for table,expected_count in [('nutrition_time',2 if lane=='B' else 1),('nutrition_set',1)]:
  old=load(private/(('B-' if lane=='B' else '')+'private-'+table+'-rows.json'));natural=[r for r in source[table] if not r['name'].startswith('CDXNW11D6A2-')];check(old==natural,'Final all natural rows exact');own=[r for r in source[table] if r['name'].startswith('CDXNW11D6A2-')];check(len(own)==expected_count,'Exact own retained count')
  for row in own:
   check(int(row['del_flag' if table=='nutrition_time' else 'is_del'])==(1 if table=='nutrition_time' else 0),'Final softdeleted flags')
   check(int(row['status'])==(1 if row['name'].endswith('-extra-evidence') else 0),'Final status original/default')
   if table=='nutrition_set':check(int(row['number'])==0,'Set number unchanged')
 cleanup=load(target/'private-cleanup-write.json');check(cleanup['passed'] and cleanup['raw_evidence_SHA_unchanged'] and cleanup['original_parent_still_alive'],'Own resource cleanup/original preservation')
check(summary['native_preserved']=={'B':{'INSERT':2,'UPDATE':11},'C':{'INSERT':2,'UPDATE':10}},'Exact native writes, B1 missing honest')
mutations=0
mutation_categories={}
def must_reject(changed,kind):
 global mutations
 rejected=False
 try:verify_wire(changed)
 except (AssertionError,KeyError,TypeError,ValueError):rejected=True
 check(rejected,'Mutation rejected '+kind);mutations+=1;mutation_categories[kind]=mutation_categories.get(kind,0)+1
for sample in samples:
 for kind in ['response','protected','native_count','natural']:
  changed=copy.deepcopy(sample)
  if kind=='response':body=json.loads(changed['body']);body['code']=99;changed['body']=json.dumps(body)
  elif kind=='protected':changed['after']['actor_sha256']='tampered'
  elif kind=='native_count':changed['prepared_SQL']=[s for s in changed['prepared_SQL'] if s.get('op') not in ['INSERT','UPDATE']]+[{'op':'UPDATE'}]*2
  else:changed['after']['nutrition_time'][0]['name']='tampered natural row'
  must_reject(changed,kind)
 case=sample['case'];step=case['step'];label=case.get('label',str(step));table=case['table'];lane=sample['lane'];op=native_writes(sample)
 if op:
  changed=copy.deepcopy(sample);statement=native_writes(changed)[0];key=next(k for k in statement['bindings'] if k.startswith(':data__'));statement['bindings'][key]['type']=99;must_reject(changed,'native_bind_type')
  changed=copy.deepcopy(sample);statement=native_writes(changed)[0];key=next(k for k in statement['bindings'] if k.startswith(':data__'));statement['bindings'][key]['value']='WRONG';must_reject(changed,'native_bind_value')
 if step in [2,4,6,8,10,11]:
  field='order_no' if step in [2,4] else ('status' if step in [6,8] else ('is_del' if step==10 else 'del_flag'));bad=620999 if field=='order_no' else (1 if field in ['status','is_del'] else 0)
  changed=copy.deepcopy(sample);target=case['params']['uuid'];row=next(r for r in changed['after'][table] if r['uuid']==target);row[field]=str(bad) if lane=='B' else bad
  bound=native_writes(changed)[0]['bindings'][':data__'+field];bound['value']=str(bad) if type(bound['value']) is str else bad;must_reject(changed,'allowed_column_wrong_value')
  changed=copy.deepcopy(sample);row=next(r for r in changed['after'][table] if r['uuid']==target);row[field]=int(row[field]) if lane=='B' else str(row[field]);must_reject(changed,'allowed_column_wrong_native_type')
 if step in [1,3]:
  for field in (['status','del_flag'] if table=='nutrition_time' else ['status','is_del','number','use_time']):
   changed=copy.deepcopy(sample);row=next(r for r in changed['after'][table] if r['name']==case['params']['name']);bad='NATURAL-UUID' if field=='use_time' else (1 if field in ['del_flag','number'] else 0);row[field]=str(bad) if lane=='B' or field=='use_time' else bad;must_reject(changed,'insert_default_or_relationship_'+field)
  for bad in ['2026-00-00 00:00:00','2026-10-01 00:00:00']:
   changed=copy.deepcopy(sample);row=next(r for r in changed['after'][table] if r['name']==case['params']['name']);row['create_time']=bad+('.000000' if lane=='C' else '');native_writes(changed)[0]['bindings'][':data__create_time']['value']=bad;must_reject(changed,'insert_invalid_or_outside_window_date')
summary['assertions']=checks;summary['mutation_rejections']=mutations;summary['mutation_categories']=mutation_categories
if not verify_only:
 output=private/'final-verification-v2.json';output.write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n');output.chmod(0o600)
print(json.dumps(summary,ensure_ascii=False))
