"""Offline local-file inventory check; does not download file URLs or send data. Usage: python verify_manifest.py manifest.csv directory-with-downloaded-files Rows are operator-supplied. An empty template is NOT a successful migration. The report checks known rows only: source-system omitted files remain unknown. """ from pathlib import Path import csv, hashlib, json, sys if len(sys.argv)!=3: raise SystemExit('Usage: python verify_manifest.py manifest.csv downloaded-directory') manifest=Path(sys.argv[1]).resolve();directory=Path(sys.argv[2]).resolve() with manifest.open(encoding='utf-8-sig',newline='') as f: rows=list(csv.DictReader(f)) results=[];keys=set() for i,row in enumerate(rows,2): errors=[];key=(row.get('response_key',''),row.get('field_key',''),row.get('relative_path','')) if not key[0]:errors.append('missing-response-key') if key in keys:errors.append('duplicate-row') keys.add(key) if row.get('artifact_type') not in ('response-table','attachment','readable-pdf','field-map','other'):errors.append('unknown-artifact-type') name=row.get('relative_path','');target=(directory/name).resolve() if not name or '://' in name or not target.is_relative_to(directory):errors.append('invalid-relative-path') elif not target.is_file():errors.append('file-missing') else: digest=hashlib.sha256(target.read_bytes()).hexdigest();size=target.stat().st_size if row.get('expected_sha256') and digest!=row['expected_sha256']:errors.append('hash-mismatch') if row.get('expected_bytes') and str(size)!=row['expected_bytes']:errors.append('size-mismatch') results.append({'row':i,'status':'check-failed' if errors else 'file-present','errors':errors}) print(json.dumps({'status':'not-tested' if not rows else 'inventory-only','rows':len(rows),'failures':sum(bool(r['errors']) for r in results),'results':results,'scope':'Only supplied inventory rows were checked; no source completeness, target restoration, legal validity, or AEO/GEO outcome was established.'},ensure_ascii=False,indent=2)) if any(r['errors'] for r in results):raise SystemExit(1)