#!/usr/bin/env python3
"""Build three finite-allowlist archives, then inspect their actual bytes.
Does not deploy, email, upload, change accounts or modify files outside this report.
Unknown input files are never silently copied. Binary review hashes are required.
"""
from pathlib import Path
import hashlib,json,zipfile
from privacy import scan_archive
R=Path(__file__).resolve().parents[1]
TOOLS=['build.py','icons.py','graphics.py','privacy.py','test_static.py','test_privacy.py','test_browser.py','export_pdf.py','package.py']
ASSETS=['assets/site.css','assets/site.js','assets/model.js','assets/report-data.js','assets/typography.css','assets/OFL-Manrope.txt','assets/icons.svg','brand/lunis-logo.svg','brand/lunis-kachel.svg']
GRAPHICS=['anforderungsgate','architektur','bauablauf','fachablauf','freigabeweg','klassisch-modell','lehrlinge','mindestloehne','one-shot-bilanz','privacy-weg','quellenweg','roi-modell','schema-module','stunden','teststand','ticketjahre','ticketstatus','wellen','zeitfenster']
DATA=['navigation.json','funktionsmatrix.json','modelle.json','markt.json','wettbewerb.json','gespraechsnotizen.json','quellen.json','demos.json','media.json','markentexte.json','pdf-index.json','page-metadata.json','visual-review.json']
PROOF=['browser-checks.json','static-checks.json','privacy-tests.json','pdf-export.json','pdf-text-checks.json','visual-review.md','pdf-contact-sheet.jpg','pdf-pages-01.jpg','pdf-pages-25.jpg','pdf-pages-49.jpg','pdf-matrix-detail.png','pdf-markt-detail.png','layout-index.png','layout-funktion.png','layout-research.png']
META=['README.md','REPORT.md','BLOCKED.md']
EXPORTS=['exports/Funktionsmatrix.csv','exports/Wettbewerb.csv','exports/Leistungsblatt.csv','exports/Gespraechsnotizen.txt']

def make():
 nav=json.loads((R/'data/navigation.json').read_text());demos=json.loads((R/'data/demos.json').read_text());sources=json.loads((R/'data/quellen.json').read_text());media=json.loads((R/'data/media.json').read_text());pdfs=json.loads((R/'data/pdf-index.json').read_text());review=json.loads((R/'data/visual-review.json').read_text())
 pdfpaths=['exports/'+name+'.pdf' for name in pdfs.values()]+['exports/Lunis-Endbericht.pdf']
 material=ASSETS+[f'assets/graphics/{name}.svg' for name in GRAPHICS]+['data/'+x for x in DATA]+['quellen/'+x['id']+'.md' for x in sources]+['tools/'+x for x in TOOLS]+EXPORTS+META
 content=[slug+'.html' for slug,_,_ in nav]+material+[x['datei'] for x in demos]+[x['datei'] for x in media]+['assets/media/lunis-logo-review.png']+pdfpaths+['proof/'+x for x in PROOF]+['proof/pdf/'+slug+'.png' for slug,_,_ in nav]
 content=list(dict.fromkeys(content));records={}
 for rel in content:
  p=R/rel
  if not p.is_file() or p.is_symlink():raise SystemExit('Missing or unsafe required file: '+rel)
  data=p.read_bytes();digest=hashlib.sha256(data).hexdigest();binary=p.suffix.lower() in {'.pdf','.webp','.png','.jpg','.jpeg'}
  if binary and review.get('files',{}).get(rel)!=digest:raise SystemExit('Binary review absent or stale: '+rel)
  records['endbericht/'+rel]={'bytes':len(data),'sha256':digest,'reviewed_binary':binary}
 def archive(file,paths):
  p=R/file
  with zipfile.ZipFile(p,'w',compression=zipfile.ZIP_DEFLATED,compresslevel=9) as z:
   for rel in sorted(set(paths)):
    data=(R/rel).read_bytes();info=zipfile.ZipInfo('endbericht/'+rel,date_time=(2026,9,21,0,0,0));info.compress_type=zipfile.ZIP_DEFLATED;info.create_system=3;info.external_attr=(0o100644)<<16;z.writestr(info,data)
  data=p.read_bytes();records['endbericht/'+file]={'bytes':len(data),'sha256':hashlib.sha256(data).hexdigest()}
  result=scan_archive(p,records)
  if result['status']!='PASS':
   (R/'proof/package-errors.json').write_text(json.dumps(result,indent=2,ensure_ascii=False));raise SystemExit('Archive check failed. See proof/package-errors.json')
  return result
 results={}
 results['pdfs']=archive('exports/Lunis-PDFs.zip',pdfpaths)
 results['material']=archive('exports/Lunis-Daten-Skripte.zip',material)
 content+=['exports/Lunis-PDFs.zip','exports/Lunis-Daten-Skripte.zip']
 # The manifest intentionally excludes itself and the outer archive to avoid circular hashes.
 manifest={'schema':'lunis-report-manifest-v2','date':'2026-09-21','scope':'Explicit curated files only. This manifest and outer ZIP are not self-hashed.','files':{p:records['endbericht/'+p] for p in sorted(content)}}
 (R/'MANIFEST.json').write_text(json.dumps(manifest,indent=2,ensure_ascii=False))
 raw=(R/'MANIFEST.json').read_bytes();records['endbericht/MANIFEST.json']={'bytes':len(raw),'sha256':hashlib.sha256(raw).hexdigest()}
 content+=['MANIFEST.json'];results['website']=archive('Lunis-Endbericht-v2.zip',content)
 outcome={'status':'PASS','archives':results,'website_files':len(content),'website_bytes':(R/'Lunis-Endbericht-v2.zip').stat().st_size,'website_sha256':hashlib.sha256((R/'Lunis-Endbericht-v2.zip').read_bytes()).hexdigest(),'self_report_scope':'This verification record is outside the checked ZIP to avoid circular hashes.','limits':'No browser acceptance, legal clearance or universal anonymization guarantee.'}
 (R/'proof/package-check.json').write_text(json.dumps(outcome,ensure_ascii=False,indent=2));print(json.dumps({k:v for k,v in outcome.items() if k!='archives'},ensure_ascii=False))
if __name__=='__main__':make()
