#!/usr/bin/env python3
import json
from pathlib import Path
def exact(a,b,path='$'):
    if type(a) is not type(b):raise AssertionError('type '+path)
    if isinstance(a,dict):
        if set(a)!=set(b):raise AssertionError('keys '+path)
        for k in a:exact(a[k],b[k],path+'.'+k)
    elif isinstance(a,list):
        if len(a)!=len(b):raise AssertionError('length '+path)
        for i,(x,y) in enumerate(zip(a,b)):exact(x,y,f'{path}[{i}]')
    elif a!=b:raise AssertionError('value '+path)
def validate(x):
    assert not x['errors']
    assert len(x['cases'])==(40 if x['lane']=='B' else 42)
    assert len({c['scenario'] for c in x['cases']})==len(x['cases'])
    for c in x['cases']:
        assert c['method']==('GET' if c['path'].endswith('/detail') else 'POST')
        assert c['http_status']==200 and c['trace_format_valid'] is True
        assert c['body']['status']==(500 if c['scenario'].endswith('INVALID_KEY_REFUSAL') else 200)
        if 'complete_original_projection_equal' in c:assert c['complete_original_projection_equal'] is True
    assert x['retention']=={'mode':'retained-owned','fresh_counts':{'h5_setting':2,'wxapp_setting':4},'natural_two_table_full_sha_unchanged':True,'exact_owned_composite_tuples_only':True}
    assert x['resources']=={'tokens_absent':True,'cache_files':0}
    f=x['independent_final_original']
    assert f=={'fresh_counts':{'h5_setting':2,'wxapp_setting':4},'six_exact_composite_keys_owned':True,'all_five_original_columns_verified':True,'full_JSON_scalar_container_and_array_order_verified':True,'all_basic_disabled_and_examine_switch_zero':True,'native_unix_HTTP_windows_verified':True,'natural_two_table_all_fields_sha_unchanged':True,'independent_connection_after_worker_stop':True,'private_cache_files':0,'own_port_listening':False}
    assert len(x['write_audit'])==16
    assert sum(a['operation']=='INSERT' for a in x['write_audit'])==6
    assert sum(a['operation']=='UPDATE' for a in x['write_audit'])==10
    for e in x['write_audit']:assert e['owned_scope_verified'] is True and e['table'] in ('yoshop_h5_setting','yoshop_wxapp_setting')
    assert not any(e.get('fingerprint_lexical_warning') for e in x['events'])
def compare(b,c):
    validate(b);validate(c)
    bm={x['scenario']:x for x in b['cases']};cm={x['scenario']:x for x in c['cases']}
    assert set(bm)<set(cm) and set(cm)-set(bm)=={'PILOT_CS_STORE_H5_DEFAULT','PILOT_CS_STORE_H5_CREATE'}
    for s in bm:exact(bm[s],cm[s],s)
    for k in ('retention','resources','independent_final_original'):exact(b[k],c[k],k)
    assert c['loaded_boundary']['dm_builder_sha']=='58e1e79ce69881cc305c6fd33adec0e9a3d030ac15b4c00ae464cbaa4cce2b59'
    exact(b['loaded_boundary'],{k:v for k,v in c['loaded_boundary'].items() if k!='dm_builder_sha'},'all original business source, plus separate root-applied DM-only Builder gate')
    project=lambda x:[{k:e[k] for k in ('operation','table','owned_scope_verified')} for e in x['write_audit']]
    exact(project(b),project(c),'all DML')
    hashes=lambda x:{k:v for e in x['events'] for k,v in e.get('source_hashes',{}).items()}
    bh,ch=hashes(b),hashes(c)
    for k in set(bh)&set(ch):exact(bh[k],ch[k],'actual source '+k)
    methods=sorted({x['method']+' '+x['path'] for x in b['cases']});assert len(methods)==8
    return {'actual_available_wire_pairs':40,'strict_positive_pairs':38,'invalid_key_refusal_pairs':2,'new_positive_methods':methods,'strict_wire_value_type_container_array_order_differences':0,'common_actual_source_hashes':len(set(bh)&set(ch)),'retained_each_engine':{'h5_setting':2,'wxapp_setting':4},'initial_two_B_raw_lost_and_explicitly_not_counted':True,'no_successful_B_creation_replayed':True,'all_retained_basic_disabled':True}
if __name__=='__main__':
    p=Path(__file__).parent;print(json.dumps(compare(json.loads((p/'evidence-B.json').read_text()),json.loads((p/'evidence-C.json').read_text())),indent=2))
