#!/usr/bin/env python3 """Read-only artifact census for C266-C479. No historical calculation is rerun. Run from the research workspace, optionally --extra-zip PATH repeatedly. The output lists a retrievable packet origin and verifies all manifest payloads, report/results certificate hashes, and predecessor links where available. """ from pathlib import Path, PurePosixPath import argparse, hashlib, json, re, zipfile from collections import defaultdict def sha(b): return hashlib.sha256(b).hexdigest() def dump(p,x): p.write_text(json.dumps(x,indent=2,ensure_ascii=False)+'\n') def number(x): m=re.fullmatch(r'C(\d+)',str(x));return int(m[1]) if m else None def main(): ap=argparse.ArgumentParser();ap.add_argument('--base',default='.');ap.add_argument('--out',default='handoff_C479_work/coverage');ap.add_argument('--extra-zip',action='append',default=[]);ap.add_argument('--normalize',action='store_true');args=ap.parse_args();base=Path(args.base).resolve();out=base/args.out;out.mkdir(parents=True,exist_ok=True) candidates=defaultdict(list); zip_index=[]; errors=[] def rel(p): try:return str(Path(p).relative_to(base)) except ValueError:return str(p) def local_candidate(p): try: d=json.loads((p/'CERTIFICATE.json').read_text());n=number(d.get('step')) if n and 266<=n<=479:candidates[n].append({'kind':'directory','directory':rel(p),'certificate':d}) except Exception as e: errors.append({'path':str(p),'error':str(e)}) for dirname in ('axis_ledger_work','recovery_C423/compact'): for p in (base/dirname).glob('C[0-9]*'): if p.is_dir() and re.fullmatch('C[0-9]+',p.name) and (p/'CERTIFICATE.json').is_file():local_candidate(p) # Include archives retrieved for handoff, but exclude subsequently built handoff output. zips=set(base.glob('deliverables/*.zip'))|set(base.glob('recovery_C423/**/*.zip'))|set(base.glob('handoff_C479_work/recovered/**/*.zip'))|set(base.glob('handoff_C479_work/downloads/**/*.zip'))|set(base.glob('handoff_C479_work/recovered_archives/**/*.zip'))|{Path(x).resolve() for x in args.extra_zip} for p in sorted(zips): try: with zipfile.ZipFile(p) as z: names=z.namelist(); found=[] for name in names: if PurePosixPath(name).name!='CERTIFICATE.json' or '/sources/' in name:continue d=json.loads(z.read(name));n=number(d.get('step')) if n and 266<=n<=479: pref=str(PurePosixPath(name).parent); pref='' if pref=='.' else pref+'/' # Flat compact certificate collections are not a packet unless report/results nearby. cand={'kind':'archive','archive':rel(p),'prefix':pref,'certificate':d} if any(v==pref+'evidence/RESULTS.json' for v in names):candidates[n].append(cand);found.append(n) nested=[x for x in names if x.lower().endswith('.zip')] zip_index.append({'archive':rel(p),'members':len(names),'packet_steps':sorted(set(found)),'nested_zip_members':nested}) except Exception as e:errors.append({'archive':rel(p),'error':str(e)}) def load_packet(c): if c['kind']=='directory': p=base/c['directory'];return {str(f.relative_to(p)):f.read_bytes() for f in p.rglob('*') if f.is_file()} with zipfile.ZipFile(base/c['archive']) as z:return {k[len(c['prefix']):]:z.read(k) for k in z.namelist() if k.startswith(c['prefix']) and not k.endswith('/')} rows=[] for n in range(266,480): step=f'C{n}';cs=candidates[n]; row={'step':step,'number':n,'available':False,'alternatives':[ {k:v for k,v in c.items() if k!='certificate'} for c in cs]} if not cs:rows.append(row);continue c=cs[0];files=load_packet(c);cert=c['certificate'];manifest_name='ARTIFACT_MANIFEST.json' if 'ARTIFACT_MANIFEST.json' in files else 'MANIFEST.json';fail=[] if manifest_name not in files:fail.append('manifest missing');entries=[] else: manifest=json.loads(files[manifest_name]); entries=manifest.get('files',manifest) if isinstance(entries,dict):entries=[dict(path=k,**v) if isinstance(v,dict) else {'path':k,'sha256':v} for k,v in entries.items()] payloads=[] for e in entries: p=e.get('path',e.get('file'));data=files.get(p);ok=data is not None and sha(data)==e.get('sha256') and (not isinstance(e.get('bytes'),int) or len(data)==e['bytes']);payloads.append({'path':p,'bytes':len(data) if data is not None else None,'sha256':sha(data) if data is not None else None,'verified':ok}) if not ok:fail.append('manifest:'+str(p)) bindings=cert.get('bindings',{});report=bindings.get('report') or next((k for k in files if '/' not in k and re.match(r'490d_C'+str(n)+r'_',k) and k.endswith('.md')),None) results=bindings.get('results','evidence/RESULTS.json');checkpoint=bindings.get('checkpoint') or next((k for k in files if '/' not in k and k.startswith('490d_Mirror_Checkpoint_After_C'+str(n)+'_')),None) checks={} for key,path in [('report',report),('results',results),('checkpoint',checkpoint)]: expected=cert.get(key+'_sha256');data=files.get(path);ok=(data is not None and (expected is None or sha(data)==expected));checks[key]={'path':path,'sha256':sha(data) if data is not None else None,'bytes':len(data) if data is not None else None,'certificate_expected_sha256':expected,'certificate_binding_verified':ok if expected else None,'present':data is not None} if not ok:fail.append(key+':missing/hash mismatch') r=json.loads(files[results]) if results in files else {};status=r.get('status',r.get('passed',r.get('all_passed')));passing=status is True or str(status).lower() in ('pass','passed','true','ok','success') if not passing:fail.append('results not passing:'+str(status)) row.update({'available':True,'title':cert.get('title'),'origin':{k:v for k,v in c.items() if k!='certificate'},'certificate_sha256':sha(files['CERTIFICATE.json']),'certificate_status':cert.get('status'),'previous_certificate_sha256':cert.get('previous_certificate_sha256'),'manifest':manifest_name,'manifest_sha256':sha(files[manifest_name]) if manifest_name in files else None,'payload_count':len(entries),'manifest_payloads_verified':not any(x.startswith('manifest') for x in fail),'checks':checks,'recorded_result_status':status,'recorded_result_passing':passing,'recorded_check_count':r.get('assertions_passed',r.get('check_count',r.get('checks_passed'))),'packet_verified':not fail,'failures':fail,'payloads':payloads}) if args.normalize and row['packet_verified']: dest=out/'packets'/step;dest.mkdir(parents=True,exist_ok=True) for name in sorted({e['path'] for e in payloads}|{manifest_name}): q=dest/name if q.resolve().is_relative_to(dest.resolve()):q.parent.mkdir(parents=True,exist_ok=True);q.write_bytes(files[name]) else:raise ValueError('Unsafe archive path: '+name) row['normalized_packet_directory']=rel(dest) rows.append(row) byn={r['number']:r for r in rows};links=[] for r in rows: prev=byn.get(r['number']-1) if r['available'] and prev and prev['available'] and r.get('previous_certificate_sha256'): links.append({'from':prev['step'],'to':r['step'],'verified':r['previous_certificate_sha256']==prev['certificate_sha256']}) summary={'range':'C266–C479','expected_steps':214,'full_packets_available':sum(r['available'] for r in rows),'full_packets_verified':sum(r.get('packet_verified',False) for r in rows),'missing_full_packets':[r['step'] for r in rows if not r['available']],'failed_packets':[r['step'] for r in rows if r['available'] and not r.get('packet_verified')],'manifest_payloads_verified':sum(r.get('payload_count',0) for r in rows if r.get('manifest_payloads_verified')),'verified_predecessor_links':sum(x['verified'] for x in links),'failed_predecessor_links':[x for x in links if not x['verified']],'scan_errors':errors,'research_rerun':False,'proof_scope':'Fresh actual-byte integrity and availability; passing statuses are inherited verified result records, not rerun science.'} dump(out/'C266_C479_COVERAGE.json',{'summary':summary,'records':rows,'predecessor_links':links});dump(out/'ZIP_INDEX.json',zip_index);print(json.dumps(summary,indent=2)) if __name__=='__main__':main()