#!/usr/bin/env python3
import hashlib,json,xml.etree.ElementTree as ET
from pathlib import Path
E=Path(__file__).resolve().parent;A=E/'artifacts';Q=E/'receipts';D='4e9962be49b72951c3b9ed7e1a4fe36d0ca03e757872ae03364c1ab7a790f32b';CORE='88caabf3dee923e720463d917d35b6dd32ecc31102ed3ab4df4c6a203df4a760';W='33688875a0d1ce20955bd8272257ee34d84cb138f6a23a43e5d16682368ae5e5';OLD=Path('/home/cube/projects/richard/traning coach/.omo/evidence/task26/task26-gates18-20-clarification-normalized-'+D+'-st_01a0080c');C=Path('/home/cube/projects/richard/traning coach/.omo/evidence/task26/task26-clarification-normalized-successor-'+D)
sha=lambda p:hashlib.sha256(Path(p).read_bytes()).hexdigest()
def dump(p,x):p.write_text(json.dumps(x,ensure_ascii=False,sort_keys=True,separators=(',',':'))+'\n')
def j(n):
 p=A/f'{n}.junit.xml';x=ET.parse(p).getroot();ss=[x] if x.tag=='testsuite' else x.findall('testsuite');d={k:sum(int(float(s.attrib.get(k,0))) for s in ss) for k in ('tests','failures','errors','skipped')};d['passed']=d['tests']-d['failures']-d['errors']-d['skipped'];d['sha256']=sha(p);d['path']=str(p.relative_to(E));return d
def cmd(n):
 d=A/n;return {'name':n,'argv':(d/'command.txt').read_text().strip(),'launcher_environment_sha256':sha(d/'launcher-environment.nul'),'exit_code':int((d/'exit-code.txt').read_text()),'started_at_utc':(d/'started-at-utc.txt').read_text().strip(),'ended_at_utc':(d/'ended-at-utc.txt').read_text().strip(),'duration_ms':int((d/'duration-ms.txt').read_text()),'stdout_sha256':sha(d/'stdout.txt'),'stderr_sha256':sha(d/'stderr.txt')}
assert (A/'original-fail-root-before.sha256').read_bytes()==(A/'original-fail-root-after.sha256').read_bytes();assert (A/'original-fail-receipts-before.sha256').read_bytes()==(A/'original-fail-receipts-after.sha256').read_bytes();assert (A/'git-status-before.nul').read_bytes()==(A/'git-status-after.nul').read_bytes();assert (A/'candidate-pins-before.sha256').read_bytes()==(A/'candidate-pins-after.sha256').read_bytes();assert (A/'service-before.txt').read_bytes()==(A/'service-after.txt').read_bytes()
bind={'full_candidate_digest':D,'core_candidate_digest':CORE,'wheel_sha256':W,'candidate_root':str(C),'candidate_manifest_sha256':sha(C/'candidate-manifest.json'),'source_delta_sha256':sha(C/'bindings/source-delta.json')};old={'root':str(OLD),'invalid_run_preserved':True,'root_tree_manifest_sha256':sha(A/'original-fail-root-before.sha256'),'gate18_receipt_sha256':sha(OLD/'receipts/gate18-receipt.json'),'gate19_receipt_sha256':sha(OLD/'receipts/gate19-receipt.json'),'gate20_fail_receipt_sha256':sha(OLD/'receipts/gate20-receipt.json'),'fail_index_sha256':sha(OLD/'receipts/receipt-index.json'),'invalid_full_junit_sha256':sha(OLD/'artifacts/gate20-full-gateway.junit.xml')}
controls=json.loads((A/'adjudicate-controls/stdout.txt').read_text());nodes=json.loads((A/'failing-nodes.json').read_text())
adj={'schema':'task26-gate20-harness-adjudication-v1','binding':bind,'verdict':'PASS_INVALID_HARNESS_PROVEN','invalid_run':old,'failing_node_binding':{'count':22,'json_sha256':sha(A/'failing-nodes.json'),'text_sha256':sha(A/'failing-nodes.txt'),'source_junit_sha256':nodes['source_junit_sha256'],'nodes':nodes['nodes']},'controls':controls,'commands':[cmd(n) for n in ['control-a-direct-normal','control-b-unshare-net-normal-home','control-c-footer-tmp-home','control-c-footer-normal-home','adjudicate-controls']],'candidate_causality_count':0}
rebind={'schema':'task26-gates18-19-immutable-rebind-v1','binding':bind,'verdict':'PASS_REBOUND','gate18':{'receipt_sha256':old['gate18_receipt_sha256'],'junit_sha256':'b89cbddb122a30badd65495c450203b470d19ccb05d00a621ea021dedfef2575','tests':5},'gate19':{'receipt_sha256':old['gate19_receipt_sha256'],'predecessor_junit_sha256':'14d17368ee965a0a599c24ca51021bac30943b3417b6b165a4d0117313f3e80d','predecessor_tests':94,'successor_junit_sha256':'9d101e090d4379fba0093d83af7a59db318922a06d1cb28c94a6112b1deb53b9','successor_tests':136},'basis':'Same exact candidate/core/wheel binding; source, candidate and prior receipt trees are byte-identical.'}
typec=json.loads((A/'type-classification/stdout.txt').read_text());parity=json.loads((A/'source-wheel-parity/stdout.txt').read_text());imports=json.loads((A/'installed-wheel-imports-final/stdout.txt').read_text());full=j('corrected-full-gateway');installed=j('installed-wheel-tests');built=(A/'reproducible-build.sha256').read_text().split()[0]
g20={'schema':'task26-gate20-corrected-harness-receipt-v1','binding':bind,'verdict':'PASS_CORRECTED_HARNESS','invalid_run_preserved':True,'invalid_gate20_fail_receipt_sha256':old['gate20_fail_receipt_sha256'],'harness_adjudication':'PASS_INVALID_HARNESS_PROVEN','corrected_authoritative_full_gateway':full,'corrected_environment':{'HOME':'/home/cube','TMPDIR':'UNSET','PYTHONDONTWRITEBYTECODE':'1','pytest_cache':'disabled','network_namespace':'host','PYTHONPATH':'/home/cube/projects/richard/hermes-agent:/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli','effective_environment_sha256':sha(A/'corrected-full-gateway/effective-environment.txt')},'installed_wheel_tests':installed,'installed_wheel_imports':imports,'installed_wheel_cli':'PASS','reproducible_wheel':{'expected_sha256':W,'actual_sha256':built,'match':built==W},'source_wheel_parity':parity,'ruff':'PASS','compile':'PASS','type_regression':typec,'candidate_verifier':json.loads((A/'candidate-reverify/stdout.txt').read_text()),'commands':[cmd(n) for n in ['corrected-full-gateway','candidate-reverify','source-wheel-parity','ruff','compile','reproducible-build','wheel-install','installed-wheel-tests','installed-wheel-imports-final','installed-wheel-cli-final','basedpyright-current','basedpyright-baseline','type-classification']],'invariants':{'git_status_unchanged':True,'candidate_pins_unchanged':True,'service_unchanged':True,'service_state':(A/'service-after.txt').read_text().splitlines(),'original_fail_root_byte_identical':True,'original_fail_receipts_byte_identical':True,'source_or_candidate_mutated':False,'live_profile_or_service_mutated':False}}
for n,x in [('harness-adjudication-receipt.json',adj),('gate18-19-rebind-receipt.json',rebind),('gate20-corrected-receipt.json',g20)]:dump(Q/n,x)
rs=[{'path':f'receipts/{p.name}','sha256':sha(p)} for p in sorted(Q.glob('*receipt.json'))];rows=[]
for p in sorted(A.rglob('*')):
 if p.is_file():rows.append((str(p.relative_to(E)),sha(p),p.stat().st_size))
h=hashlib.sha256();[h.update(f'{s}  {z}  {p}\n'.encode()) for p,s,z in rows];idx={'schema':'task26-gate20-corrected-certification-index-v1','binding':bind,'overall_verdict':'PASS_CORRECTED_HARNESS','invalid_run_preserved':True,'invalid_gate20_verdict':'FAIL','corrected_gate20_verdict':'PASS_CORRECTED_HARNESS','gate18_gate19_rebound':'PASS_REBOUND','receipts':rs,'artifact_tree':{'file_count':len(rows),'entries_sha256':h.hexdigest(),'total_bytes':sum(z for _,_,z in rows)},'original_fail_root':old};dump(Q/'receipt-index.json',idx)
with (Q/'receipt-hashes.sha256').open('w') as f:
 for p in sorted(Q.glob('*.json')):f.write(f'{sha(p)}  {p.name}\n')
for p in Q.iterdir():p.chmod(0o400)
print(json.dumps({'adjudication_receipt_sha256':sha(Q/'harness-adjudication-receipt.json'),'gate18_19_rebind_receipt_sha256':sha(Q/'gate18-19-rebind-receipt.json'),'gate20_corrected_receipt_sha256':sha(Q/'gate20-corrected-receipt.json'),'receipt_index_sha256':sha(Q/'receipt-index.json'),'artifact_count':len(rows),'artifact_entries_sha256':h.hexdigest(),'verdict':'PASS_CORRECTED_HARNESS'},sort_keys=True))
