"""Portable read-only evidence verification; no archived helper or binary runs.""" from pathlib import Path, PurePosixPath from datetime import datetime import hashlib, io, json, zipfile ROOT=Path(__file__).resolve().parent def require(ok,message): if not ok: raise ValueError(message) def pin(b): return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()} def membership(z,expected): names=z.namelist() require(len(names)==len(set(names)) and set(names)==set(expected),'ZIP membership') require(all(not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n for n in names),'unsafe ZIP path') def main(): files=json.loads((ROOT/'files.json').read_bytes()) for name,p in files.items(): require(pin((ROOT/name).read_bytes())==p,'outer hash '+name) manifest=json.loads((ROOT/'manifest.json').read_bytes()) with zipfile.ZipFile(ROOT/'evidence.zip') as z: membership(z,manifest['members']) for name,p in manifest['members'].items():require(pin(z.read(name))==p,'member hash '+name) def raw(p):return z.read(p) def obj(p):return json.loads(raw(p)) build=obj('build/result.json'); start=obj('build/started.json') require(all(build[k]==v for k,v in start.items()),'build start/result') require(build['exitCode']==0 and build['inputsUnchanged'] is True,'build success') require(build['head']==manifest['productionCommit'],'build commit') require(datetime.fromisoformat(build['start'])<=datetime.fromisoformat(build['finish']),'build time order') require(build['command']==obj('build/process.json')['command'],'build process command') for field,path in [('inputs','build/inputs.json'),('archive','build/inputs.zip'),('helper','build/gate-union.py'),('stdout','build/stdout.log'),('stderr','build/stderr.log')]: require(build[field]==pin(raw(path)),'build receipt '+field) inputs=obj('build/inputs.json') require(len(inputs)==build['inputCount']==707,'707 build inputs') require(sum(p.startswith('crates/vendor/iced_widget/') for p in inputs)==71,'vendor inventory') with zipfile.ZipFile(io.BytesIO(raw('build/inputs.zip'))) as source: membership(source,inputs) for name,p in inputs.items():require(pin(source.read(name))==p,'source input '+name) artifacts=[] for line in raw('build/stdout.log').decode('utf-8').splitlines(): try:a=json.loads(line) except json.JSONDecodeError:continue if a.get('reason')=='compiler-artifact' and a.get('executable'):artifacts.append(a) require(artifacts==[a['cargo'] for a in build['artifacts']],'build artifact receipts') binaries={a['cargo']['target']['name']:a['readback'] for a in build['artifacts']} executables={a['cargo']['target']['name']:a['cargo']['executable'] for a in build['artifacts']} require(set(binaries)=={'arcadia','arcadia-server','arcadia-studio'},'build artifact targets') result=obj('native/trial-1/result.json');start=obj('native/trial-1/started.json') require(all(result[k]==v for k,v in start.items()),'native start/result') require(result['exitCode']==0 and result['inputUnchanged'] is True,'native closure/input') require(result['command']==[executables['arcadia-studio']],'native exact GUI command') process=obj('native/trial-1/process.json') require(isinstance(process['pid'],int) and process['pid']>0,'native positive process ID') require(datetime.fromisoformat(result['start'])<=datetime.fromisoformat(process['observed'])<=datetime.fromisoformat(result['finish']),'native process observation time') require(result['binary']==result['binaryAfter']==binaries['arcadia-studio'],'GUI binary receipt chain') require(result['buildReceipt']==pin(raw('build/result.json')) and result['buildInputs']==pin(raw('build/inputs.json')) and result['buildHead']==build['head'],'native build receipt chain') for field,path in [('helper','native/run-native.py'),('captureHelper','native/capture-snippet.js'),('inputArchive','native/trial-1/input.zip'),('stdout','native/trial-1/stdout.log'),('stderr','native/trial-1/stderr.log')]: require(result[field]==pin(raw(path)),'native receipt '+field) require(not raw('native/trial-1/stdout.log') and not raw('native/trial-1/stderr.log'),'native empty streams') with zipfile.ZipFile(io.BytesIO(raw('native/trial-1/input.zip'))) as fixture: membership(fixture,result['input']) for name,p in result['input'].items():require(pin(fixture.read(name))==p and fixture.read(name)==raw('native/input/'+name),'fixture bytes') captures=manifest['captures']; seen=[] for name in captures: capture=obj(name) require(datetime.fromisoformat(result['start'])<=datetime.fromisoformat(capture['time'])<=datetime.fromisoformat(result['finish']),'capture timestamp') for image in capture['images']: path='native/trial-1/'+image['file'] require(path in manifest['members'] and image['width']>0 and image['height']>0,'capture image reference') require(raw(path).startswith(b'\xff\xd8'),'JPEG signature') seen.append(path) images=[n for n in manifest['members'] if n.startswith('native/trial-1/') and n.endswith('.jpg')] require(len(seen)==len(set(seen)) and set(seen)==set(images),'all raw frames referenced exactly once') require(len(captures)==manifest['captureCount'] and len(images)==manifest['imageCount'],'capture counts') original=raw('native/input/project.arcadia') expected={'project.arcadia':original[:-2]+b'\n include "oa/activities/activity_new_activity.arcadia";\n}\n', 'oa/activities/activity_new_activity.arcadia':b'\noa_activity new_activity "New activity" id "d2b6d784-dea5-433d-aea9-214c604744e5" {\n}\n\n\n'} require(original.endswith(b'}\n') and expected['project.arcadia'].count(b'position { x 120.0; y 80.0; };')==1,'fixture oracle preconditions') for bundle in ['saved-before','saved-after']: if bundle=='saved-after':expected['project.arcadia']=expected['project.arcadia'].replace(b'position { x 120.0; y 80.0; };',b'position { x 190.0; y 80.0; };',1) prefix='native/'+bundle+'/' actual={n[len(prefix):]:raw(n) for n in manifest['members'] if n.startswith(prefix)} require(actual==expected,bundle+' exact source-byte oracle') receipt=obj('native/bundle-checks/'+bundle+'.result.json') report=obj('native/bundle-checks/'+bundle+'.stdout.json') expected_entry=result['cwd'].replace('\\','/').rstrip('/')+'/.local/union-native/'+bundle+'/project.arcadia' require(len(receipt['command'])==4 and receipt['command'][0]==executables['arcadia'] and receipt['command'][1]=='check' and receipt['command'][2].replace('\\','/')==expected_entry and receipt['command'][3]=='--json',bundle+' exact CLI command') require(datetime.fromisoformat(receipt['start'])<=datetime.fromisoformat(receipt['finish']),bundle+' CLI time order') require(receipt['files']=={n:pin(b) for n,b in actual.items()} and receipt['filesUnchanged'] is True,bundle+' file receipts') require(receipt['binary']==binaries['arcadia'] and receipt['buildReceipt']==pin(raw('build/result.json')),bundle+' CLI build receipt') require(receipt['producer']==pin(raw('native/check-saved-bundles.py')),bundle+' producer') require(receipt['exitCode']==0 and report['status']=='checked',bundle+' CLI status') for field,suffix in [('stdout','.stdout.json'),('stderr','.stderr.log')]:require(receipt[field]==pin(raw('native/bundle-checks/'+bundle+suffix)),bundle+' '+field) require(not raw('native/bundle-checks/'+bundle+'.stderr.log'),bundle+' empty stderr') require(len(report['diagnostics'])==4 and all(d['severity']=='warning' for d in report['diagnostics']),bundle+' warnings') require(receipt['diagnostics']==[{k:d[k] for k in ('severity','code','object_id')} for d in report['diagnostics']],bundle+' diagnostic receipt') require(not report['changes'] and not report['written'] and report['identities_to_stabilize']==0,bundle+' nonmutating stable-ID check') summary=obj('native/bundle-checks/summary.json') require(summary['status']=='PASS' and summary['runs']==['saved-before','saved-after'] and summary['producer']==pin(raw('native/check-saved-bundles.py')),'bundle summary') print('PASS:',len(manifest['members']),'members;',len(captures),'captures;',len(images),'unaltered frames; 707 build inputs; native closure and both saved-byte/CLI receipt oracles.') print('No archived code executed. Screenshot interpretation and full parsed-model equality are not machine-verified.') if __name__=='__main__':main()