#!/usr/bin/env python3
"""Strict source-audited synthetic Spec-ID mapping, without value/order coercion."""
import argparse
from copy import deepcopy
import importlib.util
import json
from pathlib import Path
import re

p=argparse.ArgumentParser();p.add_argument('b_runtime');p.add_argument('c_runtime');args=p.parse_args()
module=Path(__file__).resolve().parents[3]/'work/2026-09-28-smart-canteen-dm8-poc/http-harness/strict_response_compare.py'
spec=importlib.util.spec_from_file_location('strict',module);strict=importlib.util.module_from_spec(spec);spec.loader.exec_module(strict)
br,b,bm=[strict.load_json(Path(args.b_runtime)/n) for n in ['report.json','responses.json','fixture_map.json']]
cr,c,cm=[strict.load_json(Path(args.c_runtime)/n) for n in ['report.json','responses.json','fixture_map.json']]
expected={f'{m}_{kind}' for m in ['TERMINAL','STORE'] for kind in ['FULL','REVERSED_VALUES','ZERO','CLEANED_FULL','CLEANED_SECOND']}
if set(b)!=expected or set(c)!=expected:raise SystemExit('Required ten real HTTP scenarios absent')
if br['source_sha256']!=cr['source_sha256']:raise SystemExit('Business source differs')
if any(bm[k]!=cm[k] for k in ['tenant','goods','marker']):raise SystemExit('Synthetic input context differs')
for report in [br,cr]:
    if report['failures'] or not report['natural_unchanged'] or any(report['independent_remaining'].values()) or report['http_dml_observed'] or not report['token_cleaned']:
        raise SystemExit('Source/natural/cleanup proof absent')
    if report['fixture_proof']!={'groups':2,'values':5,'relations':7,'goods_rows':0,'store_rows':0}:
        raise SystemExit('Unexpected fixture expansion')
    if report['cleanup']!={'relations':7,'values':5,'groups':2}:raise SystemExit('Fixture cleanup incomplete')

def indexed(value):
    return value if isinstance(value,dict) else {str(i):v for i,v in enumerate(value)}

def mapped(body,mapping):
    result=deepcopy(body);groups=indexed(mapping['spec_ids']);values=indexed(mapping['value_ids'])
    sg={value:int(key)+101 for key,value in groups.items()};sv={value:int(key)+1001 for key,value in values.items()}
    for group in result['data']['list']:
        sid=group.get('spec_id')
        if strict.kind(sid)!='number' or sid not in sg:raise ValueError('Generated spec ID lacks independent ownership')
        group['spec_id']=sg[sid]
        for value in group['valueList']:
            vid=value.get('spec_value_id')
            if strict.kind(vid)!='number' or vid not in sv:raise ValueError('Generated value ID lacks independent ownership')
            index=sv[vid]-1001;expected_group=101 if index<3 else 102
            if group['spec_id']!=expected_group:raise ValueError('Value mapped to wrong independently verified group')
            value['spec_value_id']=sv[vid]
    return result

rule={'$["trace_id"]':{'type':'string','pattern':'[0-9a-f]{13}',
    'reason':'Validated distinct framework trace','require_distinct':True}}
def wire_shape(value):
    kind=strict.kind(value)
    if kind=='object':
        return {'type':'object','properties':{k:wire_shape(v) for k,v in sorted(value.items())} if value else []}
    if kind=='array':return {'type':'array','items':[wire_shape(v) for v in value]}
    return {'type':'NULL' if kind=='null' else kind}

cases=[]
for name in sorted(expected):
    bc=next(x for x in br['cases'] if x['scenario']==name);cc=next(x for x in cr['cases'] if x['scenario']==name)
    if any(bc[k]!=cc[k] for k in ['method','path','args','groups','values']):raise SystemExit('Source arguments or row counts differ')
    if not bc['source_equal'] or not cc['source_equal']:raise SystemExit('Independent source projection differs')
    for response in [b[name],c[name]]:
        if response['http_status']!=200 or response['body'].get('status')!=200:raise SystemExit('Positive response absent')
        if re.fullmatch('[0-9a-f]{13}',response['body'].get('trace_id','')) is None:raise SystemExit('Trace absent')
        if 'wire_shape' not in response or response['wire_shape']!=wire_shape(response['body']):
            raise SystemExit('Original wire object/array structure proof absent or inconsistent')
    diffs=strict.compare(mapped(b[name]['body'],bm),mapped(c[name]['body'],cm),rules=rule)
    wire_diffs=strict.compare(b[name]['wire_shape'],c[name]['wire_shape'])
    cases.append({'scenario':name,'method':bc['method'],'path':bc['path'],'args':bc['args'],'groups':bc['groups'],'values':bc['values'],
        'source_projection_equal_both':True,'strict_differences':len(diffs),'wire_shape_differences':len(wire_diffs),
        'difference_paths_and_types':[{'path':p,'mysql_type':l,'dm_type':r} for p,l,r,_,_ in diffs]})
def runtime(report):
    events=[e for e in report['events'] if e.get('kind')=='query_observed']
    return {'queries':len(events),'fingerprints':len({e['fingerprint'] for e in events}),
        'operations':{op:sum(e['operation']==op for e in events) for op in sorted({e['operation'] for e in events})},
        'classification':{k:sum(e.get(k,0) for e in report['events'] if e.get('kind')=='listener_classification') for k in ['metadata_events','connection_events','unclassified_events']},
        'lexical_warnings':sum(bool(e.get('lexical_warning')) for e in events),
        'callsites':sorted({site for e in events for site in e.get('callsites',[])})}
result={'date':'2026-10-10','baseline':br['baseline'],'exact_get_paths':2,'scenario_pairs':10,
    'nonempty_positive_pairs':4,'empty_boundary_pairs':2,'cleanup_empty_pairs':4,
    'strict_equal_pairs':sum(not x['strict_differences'] for x in cases),
    'wire_shape_equal_pairs':sum(not x['wire_shape_differences'] for x in cases),'cases':cases,
    'fixture_scope':'Synthetic tenant and virtual Goods IDs; only 2 spec + 5 spec_value + 7 goods_spec_rel rows, no Store/Goods/account records',
    'audit':{lane:{k:r[k] for k in ['fixture_proof','cleanup','independent_remaining','before','after','natural_unchanged','http_dml_observed','token_cleaned']} for lane,r in [('B',br),('C',cr)]},
    'source_sha256':br['source_sha256'],'runtime':{'B':runtime(br),'C':runtime(cr)},
    'normalization':'Only independently source-verified generated spec/value IDs map to numeric semantic IDs, and distinct validated trace_id; no name/value/type/order changes',
    'scope':'Two formal GET relation-read nonempty paths; not actual Goods/Store whole flow or full-interface acceptance'}
print(json.dumps(result,indent=2,ensure_ascii=False,default=str));raise SystemExit(any(x['strict_differences'] or x['wire_shape_differences'] for x in cases))
