#!/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 x['error'] is None and len(x['cases'])==18
    for c in x['cases']:
        assert c['http_status']==200 and c['trace_format_valid'] is True
        assert c['body']['status']==(500 if c['scenario']=='PILOT_DL_EMPTY_RULES_REFUSAL' else 200)
        if c['method']=='GET':assert c['complete_original_projection_equal'] is True
    assert x['rule_replacement']=={'old_rules_physical_remaining':0,'new_rules':2,'parent_pk_same':True,'other_parent_child_raw_unchanged':True}
    assert x['soft_delete']=={'parent_is_delete':1,'parent_physical_remaining':1,'child_rows_retained':2,'child_raw_unchanged':True,'no_active_goods_reference':True}
    assert x['retention']=={'mode':'retained-owned','fresh_counts':{'delivery':2,'delivery_rule':3},'natural_two_table_sha_unchanged':True,'exact_owned_pks_only':True,'large_table_full_sha_claimed':False}
    assert x['resources']=={'token_absent':True,'cache_files':0}
    assert x['independent_final_original']=={'parents':2,'children':3,'all_parent_child_ids_and_ownership_verified':True,'all_physical_input_columns_and_clob_values_verified':True,'all_creation_update_unix_windows_verified':True,'A_soft_deleted_B_active':True,'child_parent_FK_independently_verified_after_commit':True,'natural_two_table_sha_unchanged':True,'old_rules_physically_deleted_by_actual_edit':2,'extra_physical_cleanup':False}
    assert len(x['original_proofs'])==5
    assert all(p['new_child_fk_verified_independently_after_commit'] is True for p in x['original_proofs'])
    assert len(x['write_audit'])==10
    for e in x['write_audit']:assert e['owned_scope_verified'] is True and e['table'] in ('yoshop_delivery','yoshop_delivery_rule') and e['tenant']==2147001221
    assert sum(e['operation']=='DELETE' for e in x['write_audit'])==1
    assert not any(e.get('fingerprint_lexical_warning') for e in x['events'])
    assert x['loaded_boundary']['terminal_model_sha']=='9a8022323d9f4b4378dd46d67a13ac734aff180686a669fb734142513a3009b0'
    assert x['loaded_boundary']['common_rule_sha']=='3413695d32e92adef05d3512db2060ff96da49519200f140e16286bdf7dbed09'
def compare(b,c):
    validate(b);validate(c)
    for key in ('cases','original_proofs','rule_replacement','soft_delete','retention','resources','independent_final_original','loaded_boundary'):exact(b[key],c[key],key)
    project=lambda x:[{k:e[k] for k in ('operation','table','tenant','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)==6
    return {'actual_http_pairs':18,'positive_pairs':17,'empty_rules_refusal_pairs':1,'new_actual_positive_methods':methods,'strict_wire_type_value_order_differences':0,'actual_common_source_hashes':len(set(bh)&set(ch)),'retained_parent_rows_each':2,'retained_child_rows_each':3,'old_rules_physically_deleted_by_real_edit_each':2,'parent_soft_delete_child_unchanged':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))
