Files
EscapistsReintroduced/docs/evidence/package-2026-09-20-radio-maintenance/erri-radio-maintenance-verify-payloads.py
T

82 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""Read-only exact current five-addon PAK inspection; optional new /tmp report dir.
No extraction, native launch, package modification or qualification upgrade.
"""
from pathlib import Path
import argparse,hashlib,json,sys,zipfile
ROOT=Path('/home/jakub/devel/escapistsrereintroduced')
SESSION=Path('/tmp/erri-radio-maintenance-packages')
sys.path.insert(0,str(ROOT/'tools'))
import native_package as package
import pak_inventory
import packed_external
AUTHORED_SHA='811dff0d2800f7ac2e0c5954c6885695eb8a31d3af82ba294677191312a9e592'
ORIGINAL_SHA='b2a1b0966a4add6af5272ddf2ea619cbe9b5d88c0446e7588fa58f4d514be436'
def require(value,message):
if not value:raise ValueError(message)
def exact_payload(project,entries,authored):
actual={'addons/'+project+'/'+entry['path']:entry['sha256'] for entry in entries}
require(len(actual)==len(entries),'Duplicate PAK paths')
prefix='addons/'+project+'/'
expected={name:sha for name,sha in authored.items() if name.startswith(prefix) and not name.endswith('.meta') and name!=prefix+'addon.gproj'}
require(bool(expected),'Missing expected addon content: '+project)
missing=sorted(set(expected)-set(actual));extra=sorted(set(actual)-set(expected));changed=sorted(name for name in expected.keys()&actual.keys() if actual[name]!=expected[name])
require(actual==expected,'PAK/source mismatch '+project+': missing='+repr(missing[:8])+' extra='+repr(extra[:8])+' changed='+repr(changed[:8]))
return len(actual)
def verify(source_zip=None):
require(SESSION==SESSION.resolve() and SESSION.is_dir(),'Session redirected')
for name in ('authored-before.json','package-report.json'):
require((SESSION/name).is_file() and not (SESSION/name).is_symlink(),'Baseline file redirected: '+name)
require(package.digest(SESSION/'authored-before.json')==AUTHORED_SHA,'Frozen authored manifest changed')
require(package.digest(SESSION/'package-report.json')==ORIGINAL_SHA,'Original failed package report changed')
authored=json.loads((SESSION/'authored-before.json').read_text())
require(package.inventory(ROOT)==authored and package.copied_authored_snapshot(SESSION/'source',authored)==authored,'Current/captured authored source differs from freeze')
artifacts=packed_external.inventory(SESSION/'packages') # exact five directories and fifteen regular runtime files; new AI closure
report={'kind':'EXACT DEVELOPMENT FIVE-ADDON PAYLOAD/SOURCE REVIEW','authored_manifest_sha256':AUTHORED_SHA,'original_package_report_sha256':ORIGINAL_SHA,'source_addon_files':len(authored),'projects':{},'artifacts':artifacts,'all_payloads_match':False,'build_qualified':False,'release_qualified':False,'native_consumption_verified':False}
inventories={}
for project in packed_external.PROJECTS:
folder=SESSION/'packages'/project
require(artifacts[project+'/addon.gproj']['sha256']==authored['addons/'+project+'/addon.gproj'],'Packed project bytes differ from authored project: '+project)
value=pak_inventory.inventory(folder/'data.pak')
require(value['archive_sha256']==artifacts[project+'/data.pak']['sha256'],'PAK changed between runtime and content inventory: '+project)
count=exact_payload(project,value['entries'],authored)
inventories[project]=value;report['projects'][project]={'entries':count,'exact_paths_and_hashes_match':True}
if source_zip is not None:
require(source_zip.is_file() and not source_zip.is_symlink(),'Source ZIP must be a regular file')
zip_sha=package.digest(source_zip)
with zipfile.ZipFile(source_zip) as archive:
manifest=json.loads(archive.read('BUNDLE-MANIFEST.json'))
require(manifest.get('kind')=='DEVELOPMENT SOURCE BUNDLE' and manifest.get('native_build') is False,'Not a development source bundle')
require({name:sha for name,sha in manifest['files'].items() if name.startswith('addons/')}==authored,'Source ZIP does not match this frozen package input')
require(set(manifest['files'])==set(authored)|{'BUNDLE-README.md','EXTERNAL-ADDONS.json'},'Unexpected development source bundle payload')
require(json.loads(archive.read('EXTERNAL-ADDONS.json'))==json.loads((SESSION/'external-addons-policy.json').read_text()),'Source ZIP external policy differs from frozen build')
require(len(archive.namelist())==len(set(archive.namelist())) and set(archive.namelist())==set(manifest['files'])|{'BUNDLE-MANIFEST.json'},'Source ZIP has duplicate/unmanifested entries')
for name,sha in manifest['files'].items():require(hashlib.sha256(archive.read(name)).hexdigest()==sha,'Source ZIP payload hash mismatch: '+name)
require(package.digest(source_zip)==zip_sha,'Source ZIP changed during review')
report['matching_source_zip']={'path':str(source_zip),'sha256':zip_sha}
require(packed_external.inventory(SESSION/'packages')==artifacts,'Runtime files changed during content review')
require(package.inventory(ROOT)==authored and package.copied_authored_snapshot(SESSION/'source',authored)==authored,'Source changed during content review')
require(package.digest(SESSION/'authored-before.json')==AUTHORED_SHA and package.digest(SESSION/'package-report.json')==ORIGINAL_SHA,'Original baseline changed during content review')
report['all_payloads_match']=True
return report,inventories
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--output',type=Path,help='Optional new direct /tmp child for JSON inventories/review');parser.add_argument('--source-zip',type=Path);args=parser.parse_args()
if args.output is not None:
output=args.output
require(output.is_absolute() and output.parent==Path('/tmp') and output==output.resolve() and not output.exists() and not output.is_symlink(),'Output must be a new canonical direct /tmp child')
report,inventories=verify(args.source_zip)
if args.output is not None:
args.output.mkdir(exist_ok=False)
for project,value in inventories.items():package.save(args.output/(project+'-pak-inventory.json'),value)
package.save(args.output/'payload-source-review.json',report)
print(json.dumps(report,indent=2))
return 0
if __name__=='__main__':
try:raise SystemExit(main())
except (OSError,ValueError,pak_inventory.PakError,zipfile.BadZipFile) as error:print('REFUSED: '+str(error),file=sys.stderr);raise SystemExit(1)