113 lines
8.0 KiB
Python
113 lines
8.0 KiB
Python
"""Read-only, Python-standard-library verification. Never executes archived code."""
|
|
from pathlib import Path, PurePosixPath
|
|
from datetime import datetime
|
|
import hashlib
|
|
import io
|
|
import json
|
|
import re
|
|
import zipfile
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
|
|
def require(ok, message):
|
|
if not ok:
|
|
raise ValueError(message)
|
|
|
|
def digest(data):
|
|
return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
|
|
|
def members(z, expected):
|
|
names = z.namelist()
|
|
require(len(names) == len(set(names)), 'duplicate ZIP members')
|
|
require(set(names) == set(expected), 'archive membership mismatch')
|
|
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 member')
|
|
|
|
def main():
|
|
inventory = json.loads((ROOT / 'files.json').read_text())
|
|
for name, expected in inventory.items():
|
|
require(digest((ROOT / name).read_bytes()) == expected, 'external file hash: ' + name)
|
|
manifest = json.loads((ROOT / 'manifest.json').read_text())
|
|
with zipfile.ZipFile(ROOT / 'evidence.zip') as z:
|
|
members(z, manifest['members'])
|
|
for name, expected in manifest['members'].items():
|
|
require(digest(z.read(name)) == expected, 'member hash: ' + name)
|
|
def raw(name): return z.read(name)
|
|
def obj(name): return json.loads(raw(name))
|
|
checked = set()
|
|
for run, spec in manifest['runs'].items():
|
|
if spec['kind'] != 'cargo':
|
|
continue
|
|
prefix = 'runs/' + run + '/'
|
|
start, result, process = (obj(prefix + n + '.json') for n in ('started', 'result', 'process'))
|
|
require(all(result[k] == v for k, v in start.items()), run + ': start/result relationship')
|
|
require(result['command'] == spec['command'] == process['command'], run + ': command')
|
|
require(result['head'] == spec['head'], run + ': HEAD')
|
|
require(result['exitCode'] == spec['exitCode'] and result['inputsUnchanged'] is True, run + ': outcome')
|
|
require(datetime.fromisoformat(result['finish']) >= datetime.fromisoformat(start['start']), run + ': times')
|
|
require(result['seconds'] >= 0, run + ': duration')
|
|
require(process['pid']>0 and datetime.fromisoformat(start['start'])<=datetime.fromisoformat(process['observed'])<=datetime.fromisoformat(result['finish']), run+': process observation')
|
|
require(result['helper'] == digest(raw('helpers/gate-transition.py')), run + ': helper')
|
|
for field, file in [('inputs', spec['inputs']), ('archive', spec['archive']), ('stdout', prefix+'stdout.log'), ('stderr', prefix+'stderr.log')]:
|
|
require(result[field] == digest(raw(file)), run + ': receipt ' + field)
|
|
inputs = obj(spec['inputs'])
|
|
require(len(inputs) == result['inputCount'] == spec['inputCount'], run + ': input count')
|
|
require(sum(p.startswith('crates/vendor/iced_widget/') for p in inputs) == 71, run + ': vendor count')
|
|
pair = (spec['inputs'], spec['archive'])
|
|
if pair not in checked:
|
|
with zipfile.ZipFile(io.BytesIO(raw(spec['archive']))) as source:
|
|
members(source, inputs)
|
|
for name, expected in inputs.items():
|
|
require(digest(source.read(name)) == expected, run + ': source hash ' + name)
|
|
checked.add(pair)
|
|
stdout = raw(prefix+'stdout.log').decode('utf-8', errors='replace')
|
|
rows = [[int(x) for x in row] for row in re.findall(r'test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;', stdout)]
|
|
require(rows == spec['testRows'], run + ': exact test summaries')
|
|
require([sum(row[i] for row in rows) for i in range(5)] == spec['testTotals'], run + ': test totals')
|
|
artifacts = []
|
|
for line in stdout.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)
|
|
require(artifacts == [a['cargo'] for a in result['artifacts']], run + ': artifact receipt relationship')
|
|
print(run, 'exit', result['exitCode'], 'test totals', spec['testTotals'])
|
|
readbacks=obj('helpers/commit-readbacks.json')
|
|
require(set(readbacks)=={spec['inputCommit'] for spec in manifest['runs'].values() if 'inputCommit' in spec}, 'readback commit coverage')
|
|
base_inputs=obj(manifest['runs']['core-transition-2']['inputs'])
|
|
for commit,readback in readbacks.items():
|
|
require(readback['status']=='PASS' and readback['commit']==commit, 'commit readback status')
|
|
specs=[spec for spec in manifest['runs'].values() if spec.get('inputCommit')==commit]
|
|
success=specs[0];inputs=obj(success['inputs'])
|
|
require(all(obj(spec['inputs'])==inputs for spec in specs), 'per-commit input equality')
|
|
require(readback['inputs']==digest(raw(success['inputs'])) and len(inputs)==readback['inputCount']==710, '710 readback')
|
|
require(set(readback['gitBlobs'])==set(inputs), 'readback blob inventory')
|
|
with zipfile.ZipFile(io.BytesIO(raw(success['archive']))) as source:
|
|
for name,oid in readback['gitBlobs'].items():
|
|
b=source.read(name)
|
|
require(hashlib.sha1(b'blob '+str(len(b)).encode()+b'\0'+b).hexdigest()==oid, 'readback blob '+name)
|
|
if commit==manifest['formattedSourceCommit']:
|
|
changed={p:{'before':base_inputs.get(p),'after':inputs.get(p)} for p in sorted(set(base_inputs)|set(inputs)) if base_inputs.get(p)!=inputs.get(p)}
|
|
require(changed==manifest['formattingInputChanges'] and set(changed)=={'crates/oa-desktop/src/diagram/focus.rs'}, 'one-file formatting input difference')
|
|
with zipfile.ZipFile(io.BytesIO(raw(manifest['runs']['core-transition-2']['archive']))) as old, zipfile.ZipFile(io.BytesIO(raw(success['archive']))) as new:
|
|
path='crates/oa-desktop/src/diagram/focus.rs'
|
|
before=old.read(path);after=new.read(path)
|
|
compact=b' != (self.context.document, self.context.diagram, self.context.epoch)'
|
|
expanded=b' != (\n self.context.document,\n self.context.diagram,\n self.context.epoch,\n )'
|
|
require(before.count(compact)==1 and before.replace(compact,expanded,1)==after, 'exact tuple-format-only delta')
|
|
for run in ('core-transition-2','desktop-workflow-1','core-workflows-1','clippy-1','format-1'):
|
|
if run in manifest['runs']:require(manifest['runs'][run]['inputCommit']==manifest['testedSourceCommit'], 'earlier execution source scope '+run)
|
|
for run in ('format-2','windows-build-1'):
|
|
if run in manifest['runs']:require(manifest['runs'][run]['inputCommit']==manifest['formattedSourceCommit'], 'formatted source scope '+run)
|
|
if 'format-2' in manifest['runs']:
|
|
require(not raw('runs/format-2/stdout.log') and not raw('runs/format-2/stderr.log'), 'format2 empty success streams')
|
|
if 'windows-build-1' in manifest['runs']:
|
|
build=obj('runs/windows-build-1/result.json')
|
|
require(build['exitCode']==0 and len(build['artifacts'])==3, 'Windows build outcome')
|
|
require({a['cargo']['target']['name'] for a in build['artifacts']}=={'arcadia','arcadia-server','arcadia-studio'}, 'Windows build executable membership')
|
|
require(obj(manifest['runs']['lifecycle-1']['inputs'])!=base_inputs, 'initial failure remains distinct')
|
|
print('PASS:',len(manifest['runs']),'closed runs;',len(manifest['members']),'members;',len(checked),'input pairs;710 successful input blob bindings.')
|
|
print('No archived code executed. Local commit-readback summary is not authenticated commit membership.')
|
|
|
|
if __name__=='__main__':main()
|