133 lines
11 KiB
Python
133 lines
11 KiB
Python
"""Read-only standard-library verification; no archived source/binary execution."""
|
|
from pathlib import Path, PurePosixPath
|
|
from datetime import datetime
|
|
import hashlib, io, json, zipfile
|
|
ROOT=Path(__file__).resolve().parent
|
|
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
|
|
def need(ok,msg):
|
|
if not ok:raise ValueError(msg)
|
|
def safe(n):return not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n
|
|
def members(z,expected):
|
|
names=z.namelist()
|
|
need(len(names)==len(set(names)) and set(names)==set(expected),'archive membership')
|
|
need(all(safe(n) for n in names),'unsafe archive member')
|
|
def time_order(*values):return list(map(datetime.fromisoformat,values))==sorted(map(datetime.fromisoformat,values))
|
|
def main():
|
|
files=json.loads((ROOT/'files.json').read_bytes())
|
|
need(set(files)=={'evidence.zip','manifest.json','README.md','verify.py','prepare.py'},'outer inventory')
|
|
for n,p in files.items():need(pin((ROOT/n).read_bytes())==p,'outer hash '+n)
|
|
m=json.loads((ROOT/'manifest.json').read_bytes())
|
|
with zipfile.ZipFile(ROOT/'evidence.zip') as z:
|
|
members(z,m['members'])
|
|
for n,p in m['members'].items():need(pin(z.read(n))==p,'member hash '+n)
|
|
logical=m['logicalFiles']
|
|
need(all(safe(n) for n in logical) and set(logical.values())==set(m['members']),'logical mapping')
|
|
def raw(n):return z.read(logical[n])
|
|
def obj(n):return json.loads(raw(n))
|
|
def directory(prefix):return {n[len(prefix):]:pin(raw(n)) for n in logical if n.startswith(prefix)}
|
|
pairs=set()
|
|
for run,spec in m['cargo'].items():
|
|
p='cargo/'+run+'/'
|
|
start=obj(p+'started.json');result=obj(p+'result.json');process=obj(p+'process.json')
|
|
need(all(result[k]==v for k,v in start.items()),run+' start/result')
|
|
need(result['command']==process['command']==spec['command'],run+' commands')
|
|
need(result['head']==spec['head'] and result['exitCode']==spec['exitCode'],run+' recorded outcome')
|
|
need(result['inputsUnchanged'] is True and result['seconds']>=0,run+' source/duration')
|
|
need(process['pid']>0 and time_order(start['start'],process['observed'],result['finish']),run+' process times')
|
|
for field,n in [('inputs','inputs.json'),('archive','inputs.zip'),('stdout','stdout.log'),('stderr','stderr.log')]:need(result[field]==pin(raw(p+n)),run+' '+field)
|
|
need(result['helper']==pin(raw(spec['helper'])),run+' helper')
|
|
inputs=obj(p+'inputs.json')
|
|
need(len(inputs)==result['inputCount']==spec['inputCount'],run+' input count')
|
|
need(sum(n.startswith('crates/vendor/iced_widget/') for n in inputs)==71,run+' vendor inventory')
|
|
pair=(logical[p+'inputs.json'],logical[p+'inputs.zip'])
|
|
if pair not in pairs:
|
|
with zipfile.ZipFile(io.BytesIO(raw(p+'inputs.zip'))) as source:
|
|
members(source,inputs)
|
|
for n,expected in inputs.items():need(pin(source.read(n))==expected,run+' source '+n)
|
|
pairs.add(pair)
|
|
artifacts=[]
|
|
for line in raw(p+'stdout.log').decode('utf-8',errors='replace').splitlines():
|
|
try:item=json.loads(line)
|
|
except json.JSONDecodeError:continue
|
|
if isinstance(item,dict) and item.get('reason')=='compiler-artifact' and item.get('executable'):artifacts.append(item)
|
|
need(artifacts==[a['cargo'] for a in result['artifacts']],run+' artifact relationships')
|
|
print(run,'exit',result['exitCode'],'inputs',len(inputs),'artifact records',len(artifacts))
|
|
# Successful build1/generation are not the corrected checker admission.
|
|
older=obj('cargo/lifecycle-producer-build-1/inputs.json')
|
|
newer=obj('cargo/lifecycle-producer-build-2/inputs.json')
|
|
need(older==obj('cargo/lifecycle-producer-generate-1/inputs.json'),'generation uses older producer inputs')
|
|
source_path='harness/experiments/transition-native-lifecycle-20260913/producer/src/main.rs'
|
|
need(older[source_path]!=newer[source_path],'distinct producer revisions')
|
|
final_runs=('footer-windows-build-3','footer-format-2','lifecycle-producer-format-1')
|
|
final_inputs=obj('cargo/footer-windows-build-3/inputs.json')
|
|
final_pin='b5a62817a1b5df936b9d759241bdfc4e8283901c1d4b540b9e494995e1544d8d'
|
|
for run in final_runs:
|
|
p='cargo/'+run+'/'
|
|
need(pin(raw(p+'inputs.json'))['sha256']==final_pin and obj(p+'inputs.json')==final_inputs,run+' final source manifest')
|
|
need(obj(p+'result.json')['exitCode']==0 and len(final_inputs)==715,run+' final successful gate')
|
|
for run in final_runs[1:]:
|
|
need(not raw('cargo/'+run+'/stdout.log') and not raw('cargo/'+run+'/stderr.log'),run+' empty format streams')
|
|
final_build=obj('cargo/footer-windows-build-3/result.json')
|
|
need(len(final_build['artifacts'])==1 and final_build['artifacts'][0]['cargo']['target']['name']=='arcadia-studio','final GUI artifact')
|
|
changed={n for n in set(newer)|set(final_inputs) if newer.get(n)!=final_inputs.get(n)}
|
|
need(changed=={'crates/oa-desktop/src/workflow.rs'},'final wording-only file scope')
|
|
with zipfile.ZipFile(io.BytesIO(raw('cargo/lifecycle-producer-build-2/inputs.zip'))) as before, zipfile.ZipFile(io.BytesIO(raw('cargo/footer-windows-build-3/inputs.zip'))) as after:
|
|
path='crates/oa-desktop/src/workflow.rs';a=before.read(path);b=after.read(path)
|
|
old='{} changes · {} boundary choices · {} conflicts · {} warnings'.encode()
|
|
new='{} reviewed items · {} boundary choices · {} conflicts · {} warnings'.encode()
|
|
need(a.count(old)==1 and a.replace(old,new,1)==b,'exact final footer label delta')
|
|
build=obj('cargo/lifecycle-producer-build-2/result.json')
|
|
artifact=[a for a in build['artifacts'] if a['cargo']['target']['name']=='transition-native-lifecycle']
|
|
need(len(artifact)==1,'corrected checker artifact')
|
|
# Oracle copied build capsule must match the independently included gate.
|
|
for name in ('result.json','started.json','process.json','inputs.json','inputs.zip','stdout.log','stderr.log'):
|
|
need(raw('oracle/build-capture/'+name)==raw('cargo/lifecycle-producer-build-2/'+name),'oracle build capsule '+name)
|
|
need(pin(raw('oracle/build-capture/build-helper.py'))==build['helper'],'oracle build helper')
|
|
start=obj('oracle/started.json');summary=obj('oracle/summary.json')
|
|
need(summary['status']=='PASS' and summary['trialCount']==40,'oracle completion')
|
|
need(all(summary['context'][k]==v for k,v in start.items() if k!='start'),'oracle context')
|
|
need(start['checker']==artifact[0]['readback'] and start['buildReceipt']==pin(raw('cargo/lifecycle-producer-build-2/result.json')),'oracle checker chain')
|
|
need(start['runner']==pin(raw('oracle/oracle-falsifiers.py')),'oracle runner pin')
|
|
for path,p in directory('oracle/producer/').items():need(p==newer['harness/experiments/transition-native-lifecycle-20260913/producer/'+path],'oracle producer source '+path)
|
|
fixtures=directory('oracle/fixtures/')
|
|
need(fixtures==start['fixtureInput']==directory('oracle/repeat-generation-a/')==directory('oracle/repeat-generation-b/'),'deterministic repeated fixtures')
|
|
need(directory('oracle/specimens/')==summary['specimens'],'mutation inventory')
|
|
rows=summary['trials']
|
|
need(rows==m['oracleExpectedTrials'] and len(rows)==len({r['label'] for r in rows})==40,'forty distinct trials')
|
|
actual_labels={n.split('/')[2] for n in logical if n.startswith('oracle/trials/')}
|
|
need(actual_labels=={r['label'] for r in rows},'exact trial membership')
|
|
# Original absolute names are mapped to archive members, never opened.
|
|
original=m['oracleOriginalDirectory'].rstrip('/')+'/'
|
|
def mapped(path):
|
|
norm=path.replace('\\','/')
|
|
need(norm.startswith(original),'unmapped trial input path')
|
|
rel=norm[len(original):];need(safe(rel),'unsafe trial input mapping')
|
|
return 'oracle/'+rel
|
|
for row in rows:
|
|
label=row['label'];p='oracle/trials/'+label+'/'
|
|
r=obj(p+'result.json')
|
|
need(r['label']==label and r['exitCode']==r['expectedExitCode']==row['exitCode']==row['expectedExitCode'],label+' outcome')
|
|
need(time_order(start['start'],r['start'],r['finish'],summary['finish']),label+' time order')
|
|
need(r['checkerBefore']==r['checkerAfter']==start['checker'] and r['runner']==start['runner'] and r['buildReceipt']==start['buildReceipt'],label+' provenance')
|
|
for stream in ('stdout','stderr'):need(r[stream]==pin(raw(p+stream+'.log')),label+' '+stream)
|
|
need(r['watchedBefore']==r['watchedAfter'],label+' unchanged inputs')
|
|
for path,expected in r['watchedBefore'].items():need(pin(raw(mapped(path)))==expected,label+' watched input')
|
|
cmd=r['command'];need(cmd[0]==artifact[0]['cargo']['executable'],label+' executable command')
|
|
if cmd[1]=='check':
|
|
need(mapped(cmd[3]) in logical,label+' check entry')
|
|
opts=cmd[4:];need(len(opts)%2==0,label+' option arity')
|
|
for flag,path in zip(opts[::2],opts[1::2]):
|
|
need(flag in ('--identities','--write-identities') and mapped(path) in logical,label+' identity option')
|
|
if r['exitCode']==0:
|
|
report=obj(p+'stdout.log')
|
|
need(report['status']=='PASS' and report['comparison']=='complete Project equality' and report['state']==cmd[2] and report['entry']==cmd[3],label+' positive report')
|
|
if cmd[2].endswith(('-reopened','-noop')):need(report['trace_identity_pinned'] is True and '--identities' in opts,label+' continuity receipt')
|
|
if '--write-identities' in opts:need(obj(p+'written-identities.json')==report['new_ids']==obj(mapped(opts[opts.index('--write-identities')+1])),label+' identity write')
|
|
else:need(cmd[1]=='generate' and len(cmd)==3,label+' generation command')
|
|
stderr=raw(p+'stderr.log').decode('utf-8',errors='replace')
|
|
if r['expectedStderrFragment']:need(r['expectedStderrFragment'] in stderr,label+' rejection reason')
|
|
elif r['exitCode']==0:need(not stderr,label+' successful stderr')
|
|
print('PASS:',len(m['cargo']),'closed Cargo runs; 40 recorded oracle trials;',len(logical),'logical raw files;',len(m['members']),'stored members;',len(pairs),'source capsule pairs.')
|
|
print('Offline consistency only: no binaries/source executed; no native lifecycle completion claim.')
|
|
if __name__=='__main__':main()
|