Files

76 lines
3.9 KiB
Python

"""Audit saved numerical data and freeze provenance before archive/cleanup."""
import argparse
import csv
import hashlib
import json
import math
import os
import pathlib
import platform
import re
import shutil
import time
def main():
p = argparse.ArgumentParser()
p.add_argument('--root', required=True, type=pathlib.Path)
p.add_argument('--analysis-code-commit', help='Commit actually used for the weight analysis; omit if unknown')
args = p.parse_args()
root = args.root.resolve()
if not __debug__:
raise RuntimeError('Validation requires Python assertions enabled')
checked = {}
for mode in ('tensor', 'block'):
with (root / 'analysis' / f'{mode}_pairs.csv').open() as f:
rows = list(csv.DictReader(f))
coverage = json.loads((root / 'analysis' / f'{mode}_coverage.json').read_text())
if len(rows) != coverage['pairs_verified']:
raise ValueError('Coverage count mismatch')
for row in rows:
for key in ('cosine', 'pearson'):
if row[key] and (not math.isfinite(float(row[key])) or abs(float(row[key])) > 1 + 1e-7):
raise ValueError(f'Invalid correlation: {mode}/{key}')
for key in ('error_member_to_rep', 'error_rep_to_member', 'symmetric_error', 'scaled_error',
'sampled_error_q99_over_member_rms', 'screen_vs_exact_min_error_absolute_difference'):
value = float(row[key])
if not math.isfinite(value) or value < 0:
raise ValueError(f'Invalid error: {mode}/{key}')
if not math.isfinite(float(row['scale'])):
raise ValueError('Invalid scaling diagnostic')
checked[mode] = {'rows': len(rows), 'finite_metrics': True, 'coverage_matches': True}
test_log = (root / 'tests.log').read_text()
match = re.search(r'Ran (\d+) tests in ([\d.]+)s', test_log)
if not match or '\nOK\n' not in test_log:
raise ValueError('Final tests did not pass')
qc = {'passed': True, 'checked_unix': time.time(), 'numerical_tables': checked,
'final_tests': int(match[1]), 'final_test_seconds': float(match[2]),
'earlier_parallel_test_timeout_seconds': 300 if (root / 'tests-during-io-timeout.txt').exists() else None}
(root / 'numerical-qc.json').write_text(json.dumps(qc, indent=2))
for src, dest in [('tests.log', 'test-results.txt'), ('analysis.log', 'analysis-run.txt'),
('plot.log', 'plot-run.txt'), ('download-ranged.log', 'download-progress.txt'),
('supervisor.log', 'supervisor-run.txt')]:
shutil.copy2(root / src, root / dest)
mem = {}
for line in pathlib.Path('/proc/meminfo').read_text().splitlines():
key, value = line.split(':', 1)
if key in ('MemTotal', 'MemAvailable', 'SwapTotal', 'SwapFree'):
mem[key + '_KiB'] = int(value.split()[0])
environment = {'recorded_unix': time.time(), 'python': platform.python_version(),
'platform': platform.platform(), 'machine': platform.machine(),
'logical_cpus': os.cpu_count(), 'memory_snapshot': mem,
'blas_threads': 1, 'assertions_enabled': __debug__,
'analysis_io_priority': 'best-effort 7, applied during block verification',
'analysis_code_commit': args.analysis_code_commit,
'note': 'Shared host; see resource-observation.txt. Memory snapshot is post-analysis.'}
(root / 'execution-environment.json').write_text(json.dumps(environment, indent=2))
project = pathlib.Path(__file__).resolve().parents[1]
files = sorted((project / 'pilot').glob('*.py')) + [project / 'requirements.txt', project / 'tests/test_pilot.py']
hashes = [f'{hashlib.sha256(file.read_bytes()).hexdigest()} {file.relative_to(project)}' for file in files]
(root / 'code_checksums.txt').write_text('\n'.join(hashes) + '\n')
print(json.dumps(qc))
if __name__ == '__main__':
main()