Files
ARCADIA-Studio/harness/experiments/text-indentation-4ffff0d-20260923/verify.py
T

262 lines
18 KiB
Python

"""Verify portable captured evidence, without Git, extraction or executing captured code."""
from datetime import datetime
import hashlib
import io
import json
from pathlib import Path, PurePosixPath, PureWindowsPath
import re
import zipfile
SOURCE = '4ffff0d9d94bd1875e6b316b84ac11e5c5c9657c'
FAILED = '836f9e14e7fae726e8dc16f3e1fb22b352e8e366'
PREFIX = ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio']
def require(condition, message):
if not condition:
raise RuntimeError(message)
def pin(data):
return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
def safe_name(name):
path = PurePosixPath(name)
require(name and not path.is_absolute() and '..' not in path.parts and '\\' not in name,
'Unsafe member name: ' + name)
def archive(raw):
z = zipfile.ZipFile(io.BytesIO(raw))
names = z.namelist()
require(len(names) == len(set(names)), 'Duplicate ZIP entries')
for entry in z.infolist():
safe_name(entry.filename)
require(not entry.is_dir(), 'Unexpected directory entry')
require((entry.external_attr >> 16) & 0o170000 != 0o120000, 'Linked ZIP entry')
return z
def timestamp(value):
result = datetime.fromisoformat(value.replace('Z', '+00:00'))
require(result.tzinfo is not None, 'Timestamp requires timezone')
return result
def names(log):
found = re.findall(r'^([^\r\n]+): test\r?$', log, re.M)
require(len(found) == len(set(found)), 'Duplicate listed test')
return found
def outcomes(log):
rows = re.findall(r'^test ([^\r\n]+) \.\.\. (ok|FAILED|ignored)(?:, ([^\r\n]*))?\r?$', log, re.M)
require(len(rows) == len({n for n, _, _ in rows}), 'Duplicate test outcome')
return {n: (status, reason) for n, status, reason in rows}
def summary(log, passed, ignored, filtered):
rows = re.findall(r'^test result: ok\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;', log, re.M)
require(rows == [(str(passed), '0', str(ignored), '0', str(filtered))], 'Unexpected test summary')
def main():
root = Path(__file__).resolve().parent
expected_outer = {'.gitattributes', 'README.md', 'verify.py', 'manifest.json', 'files.json', 'evidence.zip'}
require({p.name for p in root.iterdir()} == expected_outer, 'Exact outer package membership')
require(all(p.is_file() and not p.is_symlink() for p in root.iterdir()), 'Ordinary outer files required')
pins = json.loads((root / 'files.json').read_bytes())
require(set(pins) == expected_outer - {'files.json'}, 'Outer hash inventory')
for name, wanted in pins.items():
require(pin((root / name).read_bytes()) == wanted, 'Outer hash: ' + name)
manifest = json.loads((root / 'manifest.json').read_bytes())
require(manifest['source'] == SOURCE and manifest['failedSource'] == FAILED, 'Source identifiers')
z = archive((root / 'evidence.zip').read_bytes())
require(set(z.namelist()) == set(manifest['members']), 'Exact ZIP membership')
for name, wanted in manifest['members'].items():
require(pin(z.read(name)) == {k: wanted[k] for k in ('bytes', 'sha256')}, 'Member hash: ' + name)
read = z.read
doc = lambda name: json.loads(read(name))
text = lambda name: read(name).decode('utf-8')
# Source snapshots were compared with Git blobs at assembly time. Portable
# verification recomputes their bytes and recorded Git blob IDs, not Git ancestry.
inputs, source_zips = {}, {}
for commit in (FAILED, SOURCE):
setting = manifest['sourceSets'][commit]
inventory = doc(setting['manifest'])
require(inventory['source'] == commit, 'Inventory source')
rows = inventory['inputs']
require(len(rows) == len({r['path'] for r in rows}) == 711, 'Exact 711 input identities')
inputs[commit] = {r['path']: {k: r[k] for k in ('bytes', 'sha256')} for r in rows}
source_zip = archive(read(setting['archive']))
source_zips[commit] = source_zip
require(set(source_zip.namelist()) == set(inputs[commit]), 'Source ZIP scope')
blobs = doc(setting['gitBlobs'])
require(set(blobs) == set(inputs[commit]), 'Git blob inventory scope')
for path, wanted in inputs[commit].items():
content = source_zip.read(path)
require(pin(content) == wanted, 'Source bytes: ' + path)
git_hash = hashlib.sha1(b'blob ' + str(len(content)).encode() + b'\0' + content).hexdigest()
require(git_hash == blobs[path], 'Recorded Git blob: ' + path)
changed = sorted(p for p in inputs[SOURCE] if inputs[SOURCE][p] != inputs[FAILED].get(p))
require(set(inputs[SOURCE]) == set(inputs[FAILED]) and changed == [
'crates/oa-desktop/src/app/source_indentation_tests.rs',
'crates/oa-desktop/src/source_indentation/keyboard_tests.rs'], 'Two test-only compilation fixes')
old_names = names(text('prior/desktop-list-1.stdout.log'))
old_outcomes = outcomes(text('prior/desktop-1.stdout.log'))
require(len(old_names) == len(old_outcomes) == 1185 and set(old_names) == set(old_outcomes), 'Prior actual membership')
require(all(s in ('ok', 'ignored') for s, _ in old_outcomes.values()), 'Prior outcomes')
ignored = {n: reason for n, (status, reason) in old_outcomes.items() if status == 'ignored'}
require(len(ignored) == 6, 'Six prior ignored probes')
new_names = []
for path, prefix in [
('crates/oa-desktop/src/source_indentation/tests.rs', 'source_indentation::tests::'),
('crates/oa-desktop/src/source_indentation/keyboard_tests.rs', 'source_indentation::keyboard_tests::'),
('crates/oa-desktop/src/app/source_indentation_tests.rs', 'app::source_indentation_tests::'),
]:
code = source_zips[SOURCE].read(path).decode('utf-8')
new_names.extend(prefix + name for name in re.findall(r'#\[test\]\s*fn (\w+)\(', code))
require(len(new_names) == len(set(new_names)) == 9 and not set(new_names) & set(old_names), 'Nine source-derived additions')
listing = names(text('gates/list-4ffff0d.log'))
require(len(listing) == 1194 and set(listing) == set(old_names) | set(new_names), 'Retained suite plus nine additions')
require(re.findall(r'^(\d+) tests, (\d+) benchmarks\s*$', text('gates/list-4ffff0d.log'), re.M) == [('1194', '0')], 'Actual listing summary')
runner = pin(read('helpers/run-text-indentation-gate-20260922.py'))['sha256']
admitter = pin(read('helpers/admit-text-indentation-20260922.py'))['sha256']
artifact = None
environment = None
last_end = None
for stem, mode in [('list-836f9e14', 'failed'), ('list-4ffff0d', 'list'),
('indent-4ffff0d', 'focused'), ('full-4ffff0d', 'full'), ('strict-4ffff0d', 'strict')]:
result = doc('gates/' + stem + '.result.json')
log = text('gates/' + stem + '.log')
commit = FAILED if mode == 'failed' else SOURCE
require(result['source'] == result['head_after'] == commit and result['changed_inputs'] == [], 'Unchanged gate inputs: ' + stem)
require(result['launcher_sha256'] == runner, 'Gate launcher binding')
require(result['inputs'] == stem + '.inputs.json', 'Gate manifest name')
require(doc('gates/' + result['inputs']) == doc(manifest['sourceSets'][commit]['manifest']), 'Exact gate input inventory')
start, end = timestamp(result['start']), timestamp(result['end'])
require(start <= end and (last_end is None or last_end <= start), 'Closed ordered gate interval')
last_end = end
if environment is None:
environment = result['environment']
require(result['environment'] == environment, 'Same recorded gate environment')
command = (['cargo', 'clippy', '--locked', '--offline', '-p', 'oa-desktop', '--all-targets', '--', '-D', 'warnings'] if mode == 'strict'
else PREFIX + (['--', '--list'] if mode in ('failed', 'list') else
(['source_indentation'] if mode == 'focused' else []) + ['--', '--test-threads=1']))
require(result['command'] == command and result['exit'] == (101 if mode == 'failed' else 0), 'Gate command/outcome')
if mode in ('failed', 'strict'):
require(result['artifacts'] == [] and result['listing_members'] == [] and result['listing_count'] == 0, 'No executed/listed tests for this gate')
if mode == 'failed':
require(re.findall(r'^error\[(E\d+)\]:', log, re.M) == ['E0308', 'E0599'] and 'due to 2 previous errors' in log, 'Retained initial compilation failure')
require(not names(log) and not outcomes(log), 'Failed listing is not execution')
continue
if mode == 'strict':
require('Finished `dev` profile [unoptimized]' in log and not re.search(r'^\s*(warning|error)(\[|:)', log, re.M), 'Closed strict desktop all-targets gate')
continue
require(len(result['artifacts']) == 1, 'One recorded test binary')
artifact = result['artifacts'] if artifact is None else artifact
require(result['artifacts'] == artifact, 'Same recorded test binary')
emitted = re.findall(r'Running unittests [^\r\n]+ \(([^\r\n]+\.exe)\)', log)
require(len(emitted) == 1, 'One raw test executable')
observed = PureWindowsPath(emitted[0])
if not observed.is_absolute():
target = PureWindowsPath(environment['CARGO_TARGET_DIR'])
require(observed.parts[0] == target.name and '..' not in observed.parts, 'Relative Cargo target path')
observed = target.parent / observed
require(observed == PureWindowsPath(artifact[0]['path']), 'Raw test executable binding')
selected, rows = listing, None
if mode == 'list':
require(result['listing_members'] == listing and result['listing_count'] == 1194, 'Listing receipt membership')
else:
require(result['listing_members'] == [] and result['listing_count'] == 0, 'Runtime receipt is not a listing')
selected = [n for n in listing if 'source_indentation' in n] if mode == 'focused' else listing
actual = outcomes(log)
require(list(actual) == selected, 'Actual ordered executed membership')
for name, (status, reason) in actual.items():
require(status == ('ignored' if name in ignored else 'ok'), 'Actual result: ' + name)
require(reason == ignored.get(name, ''), 'Preserved ignored reason: ' + name)
nignored = len(set(selected) & set(ignored))
summary(log, len(selected) - nignored, nignored, 1194 - len(selected))
rows = [[n, actual[n][0]] for n in selected]
admission = doc('gates/' + stem + '.admission.json')
require(admission['source'] == SOURCE and admission['listed'] == 1194 and admission['new_names'] == new_names, 'Admission source-derived membership')
require(admission['selected'] == len(selected) and admission['outcomes'] == rows and admission['unchanged_inputs'] == 711, 'Admission actual outcomes')
require(admission['helper_sha256'] == admitter, 'Admission helper identity')
require(admission['records'] == {suffix: pin(read('gates/' + stem + suffix))['sha256'] for suffix in ('.result.json', '.inputs.json', '.log')}, 'Admission raw bindings')
build = doc('build/result.json')
require(build['source'] == build['head_after'] == SOURCE and build['exit'] == 0 and build['outcome'] == 'passed' and build['failure'] is None, 'Successful identified GUI build')
require(build['changed_inputs'] == [] and build['status_after'] == '' and len(build['artifacts']) == 1, 'Unchanged GUI build inputs')
require(timestamp(build['start']) <= timestamp(build['end']), 'Closed GUI build')
require(build['command'][1:] == ['build', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', '--message-format=json-render-diagnostics'], 'GUI build command')
for field, member in [('launcher', 'helpers/build-text-gui-20260922.py'), ('inputs', 'build/inputs.json'), ('environment', 'build/environment.json'), ('stdout', 'build/cargo.jsonl'), ('stderr', 'build/cargo.stderr.log')]:
require(build[field] == pin(read(member)), 'GUI build binding: ' + field)
require(doc('build/inputs.json') == doc(manifest['sourceSets'][SOURCE]['manifest']), 'GUI build same 711 inputs')
env = doc('build/environment.json')
require(env['launcher'] == build['launcher'], 'Build environment launcher')
cargo = [json.loads(line) for line in text('build/cargo.jsonl').splitlines() if line.strip()]
require([r['success'] for r in cargo if r.get('reason') == 'build-finished'] == [True], 'Cargo success record')
gui = build['artifacts'][0]
require(gui['cargo_artifact'] in cargo and gui['name'] == 'arcadia-studio', 'Actual Cargo GUI artifact')
require(PureWindowsPath(gui['origin']) == PureWindowsPath(gui['cargo_artifact']['executable']), 'GUI executable path binding')
require(not re.search(r'^\s*(warning|error)(\[|:)', text('build/cargo.stderr.log'), re.M), 'GUI build diagnostics')
baseline = None
native_summary = {}
for date, count, success in [('20260922', 29, False), ('20260923', 30, True)]:
prefix = 'native/' + date + '/'
prep, request, launch, close = [doc(prefix + name + '.json') for name in ('preparation', 'launch-request', 'launch', 'closure')]
require(all(r['source'] == SOURCE for r in (prep, request, launch, close)), 'Native source chain')
require(prep['build'] == request['build'] == pin(read('build/result.json')) and prep['build_name'] == 'gui-4ffff0d', 'Native build binding')
require(prep['gui'] == {k: gui[k] for k in ('bytes', 'sha256')}, 'Recorded GUI receipt identity')
require(request['preparation'] == pin(read(prefix + 'preparation.json')) and request['helper'] == pin(read('helpers/text-indentation-native-' + date + '.py')), 'Native launch helper/preparation')
require(len(request['command']) == 1 and PureWindowsPath(request['command'][0]).name == 'arcadia-studio.exe', 'Native launch target')
require(launch['pid'] == close['pid'] and close['exit'] == 0 and close['gui_unchanged'] is True and close['baseline_unchanged'] is True, 'Native closed receipt')
times = [timestamp(r['utc']) for r in (prep, request, launch, close)]
require(times == sorted(times) and timestamp(build['end']) <= times[0], 'Native chronological chain')
for stream in ('stdout', 'stderr'):
require(read(prefix + stream + '.log') == b'' and close[stream] == pin(b''), 'Empty native process logs')
original = read(prefix + 'baseline.arcadia')
require(prep['fixture'] == pin(original), 'Original fixture pin')
baseline = original if baseline is None else baseline
require(original == baseline, 'Both trials use identical original fixture')
expected = b''.join(b' ' + line for line in original.splitlines(keepends=True))
actual = read(prefix + 'source-work/project.arcadia')
require(close['saved'] == pin(actual) and close['expected'] == pin(expected), 'Saved and expected byte receipts')
require(close['saved_bytes_equal_expected'] is success and (actual == expected) is success, 'Distinct native outcome scopes')
require(actual == (expected if success else original), 'Exact restored/saved byte oracle')
require(len(original) == 525 and len(expected) == 577 and len(original.splitlines()) == 13, 'Native fixture physical lines')
require(original.count(b'\r\n') == 2 and original.count(b'\n') - original.count(b'\r\n') == 11, 'Mixed CRLF/LF baseline')
require('e\u0301'.encode() in actual and '\U0001f469\u200d\U0001f680'.encode() in actual, 'Combining and ZWJ bytes')
captures = manifest['native'][date]['captureMetadata']
require(len(captures) == count and captures == sorted(captures), 'Native observation membership')
require(set(captures) == {n for n in z.namelist() if n.startswith(prefix + 'evidence/') and n.endswith('.json')}, 'Exact capture metadata set')
images, capture_times = [], []
for path in captures:
frame = doc(path)
require(frame['source'] == SOURCE, 'Capture source')
capture_times.append(timestamp(frame['time']))
for image in frame['images']:
safe_name(image['file'])
require('/' not in image['file'] and image['width'] > 0 and image['height'] > 0, 'Capture image metadata')
name = prefix + 'evidence/' + image['file']
require(read(name).startswith(b'\xff\xd8'), 'Original JPEG bytes')
images.append(name)
require(capture_times == sorted(capture_times) and times[2] <= capture_times[0] <= capture_times[-1] <= times[3], 'Capture interval/order')
require(len(images) == len(set(images)) == (33 if not success else 41), 'Distinct original image membership')
require(set(images) == {n for n in z.namelist() if n.startswith(prefix + 'evidence/') and n.endswith('.jpg')}, 'No orphan original frames')
native_summary[date] = {'observations': count, 'images': len(images), 'exit': 0, 'saved_bytes': len(actual), 'equals_indented_expectation': success}
original = read('prior/native-input.arcadia').replace(b'Compact source workflow', b'Tab indentation workflow')
require(baseline == '// Tab check: e\u0301 / \U0001f469\u200d\U0001f680\r\n// first selection\n// second selection\r\n'.encode() + original, 'Fixture preparation oracle')
print(json.dumps({'source': SOURCE, 'source_inputs': 711, 'initial_listing_compile_errors': 2,
'listed': 1194, 'focused_passed': 9, 'full_passed': 1188, 'full_ignored': 6,
'strict_exit': 0, 'gui_build_exit': 0, 'native': native_summary,
'scope': 'Recorded evidence integrity/relationships, not runtime reproduction or release acceptance.'}))
if __name__ == '__main__':
main()