Files
ARCADIA-Studio/harness/experiments/ordinary-text-native-20260913/verify.py
T

101 lines
8.8 KiB
Python

"""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()