100 lines
9.7 KiB
Python
100 lines
9.7 KiB
Python
"""Offline archive/source/lifetime evidence checks. Screenshots require independent human review."""
|
|
from pathlib import Path,PurePosixPath
|
|
from decimal import Decimal
|
|
import argparse,hashlib,json,re,zipfile
|
|
|
|
def pin(data):return {'bytes':len(data),'sha256':hashlib.sha256(data).hexdigest()}
|
|
def main():
|
|
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('directory',type=Path);args=parser.parse_args();root=args.directory.resolve()
|
|
manifest=json.loads((root/'manifest.json').read_text(encoding='utf-8'));raw=(root/'native-evidence.zip').read_bytes();assert pin(raw)==manifest['archive'],'Archive pin mismatch'
|
|
with zipfile.ZipFile(root/'native-evidence.zip') as archive:
|
|
names=archive.namelist();assert len(names)==len(set(names)) and set(names)==set(manifest['members']),'Archive member inventory mismatch'
|
|
for name in names:
|
|
p=PurePosixPath(name);assert not p.is_absolute() and '..' not in p.parts and '\\' not in name and ((archive.getinfo(name).external_attr>>16)&0o170000)!=0o120000,'Unsafe member'
|
|
files={name:archive.read(name) for name in names}
|
|
for name,data in files.items():assert pin(data)==manifest['members'][name],name
|
|
assert not any(PurePosixPath(n).suffix.lower() in ('.exe','.dll') for n in files),'Binary bytes unexpectedly included'
|
|
assert (root/'verify.py').read_bytes()==files['helpers/native-evidence-verify.py'],'External verifier differs from archived exact helper'
|
|
def js(name):return json.loads(files[name].decode('utf-8-sig'))
|
|
fixture=js('fixtures/fixture-manifest.json')
|
|
for name,expected in fixture['files'].items():assert pin(files['fixtures/'+name])==expected,'Fixture pin mismatch'
|
|
phase_names={'baseline':'baseline.arcadia','messages':'expected-messages.arcadia','messages-and-lanes':'expected-messages-and-lanes.arcadia'}
|
|
number=rb'[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?'
|
|
fields=[(rb'(occurrence @alpha_report; \}; send_y )('+number+rb')(;)',Decimal(184)),(rb'(occurrence @gamma_report; \}; send_y )('+number+rb')(;)',Decimal(384)),(rb'(lifeline @intake_lane; x )('+number+rb')(;)',Decimal(172)),(rb'(lifeline @review_lane; x )('+number+rb')(;)',Decimal(432))]
|
|
def check_source(actual,phase):
|
|
expected=files['fixtures/'+phase_names[phase]]
|
|
if phase=='baseline':assert actual==expected,'Baseline source differs';return
|
|
def section(data):
|
|
a=b' diagram trial_a ';b=b' diagram trial_b ';assert data.count(a)==data.count(b)==1
|
|
start=data.index(a);end=data.index(b);assert start<end;return start,end
|
|
ea,eb=section(expected);aa,ab=section(actual);replacements=[]
|
|
for pattern,wanted in fields[:2 if phase=='messages' else 4]:
|
|
em=list(re.finditer(pattern,expected[ea:eb]));am=list(re.finditer(pattern,actual[aa:ab]));assert len(em)==len(am)==1
|
|
value=Decimal(am[0].group(2).decode('ascii'));assert value.is_finite() and value==wanted==Decimal(em[0].group(2).decode('ascii')),'Wrong moved coordinate'
|
|
replacements.append((aa+am[0].start(2),aa+am[0].end(2),em[0].group(2)))
|
|
for start,end,spelling in sorted(replacements,reverse=True):actual=actual[:start]+spelling+actual[end:]
|
|
assert actual==expected,'Other source bytes differ'
|
|
config=js('review-config.json');assert config['runs']==manifest['runs'];reports=[]
|
|
for run in config['runs']:
|
|
prefix='runs/'+run['name']+'/'
|
|
prepared=js(prefix+'prepared.json');started=js(prefix+'started.json');finished=js(prefix+'finished.json');problems=[]
|
|
def require(condition,description):
|
|
if not condition: problems.append(description)
|
|
require(pin(files[prefix+'prepared.json'])==started['prepared'],'Started prepared receipt binding')
|
|
for rel,expected in prepared['immutable'].items():
|
|
name=prefix+rel
|
|
require((pin(files[name]) if name in files else manifest['excluded_binary_bytes'].get(name))==expected,'Immutable pin: '+rel)
|
|
require(prepared['helper']==pin(files[prefix+'evidence/native-harness.py']),'Native helper binding')
|
|
receipt=js(prefix+'evidence/result.json');proof=js(prefix+'evidence/source-proof.json');inputs=js(prefix+'evidence/inputs.json')
|
|
require(receipt['exitCode']==0 and receipt['inputsUnchanged'] is True,'Closed successful unchanged build')
|
|
require(proof['result']==pin(files[prefix+'evidence/result.json']),'Build result proof pin')
|
|
require(proof['inputMap']==receipt['inputs']==pin(files[prefix+'evidence/inputs.json']),'Input map binding')
|
|
require(proof['inputCount']==receipt['inputCount']==len(inputs),'Input inventory count')
|
|
require(proof['commit']==receipt['head']==prepared['source'],'Source identity')
|
|
gui=[a for a in receipt['artifacts'] if a['cargo']['target']['name']=='arcadia-studio' and not a['cargo']['profile']['test']]
|
|
require(len(gui)==1 and gui[0]['readback']==prepared['artifact']==manifest['excluded_binary_bytes'].get(prefix+'bin/arcadia-studio.exe'),'Exact GUI artifact binding')
|
|
require(finished['exitCode']==0 and finished['immutableUnchanged'] is True,'Normal exit and immutable report')
|
|
for stream in ['stdout','stderr']:require(finished[stream]==pin(files[prefix+'app.'+stream+'.log']),'Final log pin: '+stream)
|
|
require(finished['working']==pin(files[prefix+'working.arcadia']),'Final working source pin')
|
|
if prefix+'reopen-origin.json' in files:
|
|
origin=js(prefix+'reopen-origin.json');from_name='runs/'+origin['from_run']+'/'+origin['from_file'];require(from_name in files,'Reopen source present')
|
|
if from_name in files:require(pin(files[from_name])==origin['from_pin']==origin['copied_pin']==started['workingBefore'],'Reopened input exact copy binding')
|
|
else:require(started['workingBefore']==fixture['files']['baseline.arcadia'],'Fresh run begins at original baseline')
|
|
phases=dict(run.get('source_phases',{}));source_files=[n for n in files if n.startswith(prefix) and n.endswith('.arcadia') and '/evidence/' not in n and n!=prefix+'baseline.arcadia']
|
|
for name in source_files:
|
|
rel=name[len(prefix):];sidecar=name[:-8]+'.json'
|
|
phase=phases.get(rel)
|
|
if sidecar in files:
|
|
saved=js(sidecar)
|
|
if 'phase' in saved:
|
|
require(phase is None or phase==saved['phase'],'Snapshot phase assignment consistency');phase=phase or saved['phase']
|
|
require(saved['pin']==pin(files[name]),'Snapshot receipt source pin')
|
|
if phase not in phase_names:problems.append('Explicit phase missing: '+rel);continue
|
|
try:check_source(files[name],phase)
|
|
except (AssertionError,ValueError,KeyError) as error:problems.append('Source comparison '+rel+': '+str(error))
|
|
# Every referenced screenshot must be present; archive inventory pins its exact bytes.
|
|
captures=0
|
|
for name in files:
|
|
if name.startswith(prefix) and name.endswith('.json'):
|
|
obj=js(name)
|
|
if isinstance(obj,dict) and 'images' in obj:
|
|
captures+=1
|
|
for image in obj['images']:require(prefix+image['file'] in files,'Capture image missing: '+image['file'])
|
|
require(captures>0,'No native capture observations retained')
|
|
reports.append({'run':run['name'],'reviewer_accepted':run['accepted'],'checks_pass':not problems,'problems':problems,'capture_records':captures})
|
|
if run['accepted']:assert not problems,json.dumps(reports[-1],indent=2)
|
|
old=js('preflight/fixture-preflight-1/result.json') # Retained as history, never promoted to a pass.
|
|
current=js('preflight/fixture-preflight-2/result.json');assert current['status'].startswith('PASS') and current['cliUnchanged'] is True
|
|
cp='preflight/fixture-preflight-2/';cr=js(cp+'evidence/result.json');proof=js(cp+'evidence/source-proof.json')
|
|
assert current['receipt']==proof['result']==pin(files[cp+'evidence/result.json']) and current['proof']==pin(files[cp+'evidence/source-proof.json'])
|
|
cli=[a for a in cr['artifacts'] if a['cargo']['target']['name']=='arcadia' and not a['cargo']['profile']['test']];assert len(cli)==1 and cli[0]['readback']==current['cli']==manifest['excluded_binary_bytes'][cp+'bin/arcadia.exe']
|
|
assert {check['fixture'] for check in current['checks']}==set(fixture['files']) and len(current['checks'])==3,'All three fixture preflights required'
|
|
assert cr['exitCode']==0 and cr['inputsUnchanged'] is True and current['source']==cr['head']==proof['commit'],'Current CLI closed source binding'
|
|
for check in current['checks']:
|
|
name=check['fixture'];assert check['exitCode']==0 and check['json_report'] and check['fixtureUnchanged']
|
|
assert pin(files[cp+name+'.stdout.json'])==check['stdout'] and pin(files[cp+name+'.stderr.log'])==check['stderr']
|
|
assert pin(files[cp+'fixtures/'+name])==fixture['files'][name]
|
|
report=js(cp+name+'.stdout.json');assert isinstance(report,dict) and report['operation']=='check' and report['exit_code']==0 and report['written'] is False and not any(d['severity']=='error' for d in report['diagnostics']),'Raw CLI report must independently confirm valid read-only check'
|
|
print(json.dumps({'status':'PASS offline archive, source and receipt consistency checks','runs':reports,'old_preflight_retained':True,'limitations':['Human visual/interaction claims not inferred from screenshots.','Omitted executable/DLL bytes cannot be rehashed offline.','Build input map/source proof consistency checked; Git blobs not independently available offline.','Failed attempts retained with accepted=false are reported, not silently asserted passing.']},indent=2))
|
|
if __name__=='__main__': main()
|