mirror of
https://github.com/priyanshujain/messageboardbench.git
synced 2026-10-02 19:17:07 +00:00
97 lines
6.9 KiB
Python
97 lines
6.9 KiB
Python
"""Offline integrity/configuration validation of a fresh board export (no model calls)."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from inspect_ai.log import read_eval_log
|
|
|
|
|
|
def sha(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def validate(run, export):
|
|
manifest = json.loads((run / 'manifest.json').read_text())
|
|
report = json.loads((export / 'manifest.json').read_text())
|
|
episodes = json.loads((export / 'episodes.json').read_text())
|
|
inputs = {x['sample']['metadata']['episode_id']: x for path in run.glob('phase-*-inputs.json')
|
|
for x in json.loads(path.read_text())}
|
|
checks = {}
|
|
status_path = run / 'status.json'
|
|
checks['run_completed'] = status_path.is_file() and json.loads(status_path.read_text()).get('status') == 'completed'
|
|
expected = sorted((phase['team'], phase['cohort'], phase['condition'], plan['ids'][slot], plan['splits'][slot])
|
|
for phase in manifest['schedule'] for plan in manifest['team_plans'] if plan['team'] == phase['team']
|
|
for slot in range((phase['cohort']-1)*manifest['agents_per_cohort'], phase['cohort']*manifest['agents_per_cohort']))
|
|
checks['matched_schedule'] = expected == sorted((e['team'], e['cohort'], e['condition'], e['task_id'], e['split']) for e in episodes)
|
|
sources = []
|
|
for entry in json.loads((run / 'source-snapshot/index.json').read_text()):
|
|
sources.append({**entry, 'archived_hash_valid': sha(run / 'source-snapshot' / entry['archived']) == entry['sha256'],
|
|
'current_source_matches': Path(entry['source']).is_file() and sha(Path(entry['source'])) == entry['sha256']})
|
|
checks['source_archive_hashes_valid'] = all(x['archived_hash_valid'] for x in sources)
|
|
checks['planned_episode_count'] = len(episodes) == manifest['planned_episodes'] == len(inputs)
|
|
checks['unique_identities'] = len({x['episode_id'] for x in episodes}) == len(episodes)
|
|
checks['board_snapshot_hash_valid'] = sha(Path(report['board_snapshot_path'])) == report['board_sha256']
|
|
checks['export_script_hash_valid'] = sha(Path(__file__).resolve().parents[1] / 'board_report.py') == report['report_script_sha256']
|
|
checks['no_skipped_logs'] = not report['skipped_logs']
|
|
checks['no_unmatched_audit'] = not json.loads((export / 'unmatched-audit.json').read_text())
|
|
operations = json.loads((export / 'board-operations.json').read_text())
|
|
checks['all_operations_delivery_confirmed'] = all(o['delivery_confirmed'] for o in operations)
|
|
logs = {p['path']: p for p in report['logs']}
|
|
sample_checks = []
|
|
for row in episodes:
|
|
path = Path(row['log_path'])
|
|
log = read_eval_log(path, resolve_attachments=True)
|
|
sample = next(s for s in log.samples if s.uuid == row['sample_uuid'])
|
|
original = inputs[row['episode_id']]
|
|
meta = original['sample']['metadata']
|
|
cfg = log.eval.config.model_dump()
|
|
generation = log.eval.model_generate_config.model_dump()
|
|
c = {'log_hash_valid': sha(path) == logs[str(path)]['sha256'], 'log_success': log.status == 'success',
|
|
'no_error': sample.error is None, 'no_limit': sample.limit is None,
|
|
'model_exact': log.eval.model == manifest['model'],
|
|
'strict_tools': log.eval.model_args.get('strict_tools') == manifest['strict_tools'],
|
|
'task_id': str(sample.id) == str(original['sample']['id']),
|
|
'tokens_balance': row['total_tokens'] == row['input_tokens'] + (row['cache_read_tokens'] or 0) + (row['cache_write_tokens'] or 0) + row['output_tokens'],
|
|
'metadata_input_matches': all(sample.metadata.get(k) == meta[k] for k in ['test', 'test_patch', 'prompt', 'entry_point', 'condition', 'cohort', 'team', 'slot']),
|
|
'instruction_and_availability_present': (any(m.role == 'user' and original['sample']['input'] == m.text for m in sample.messages) and any(original['availability'] in m.text for m in sample.messages[:2]))}
|
|
for exported, field in [('input_tokens', 'input_tokens'), ('output_tokens', 'output_tokens'),
|
|
('total_tokens', 'total_tokens'), ('cache_read_tokens', 'input_tokens_cache_read'),
|
|
('cache_write_tokens', 'input_tokens_cache_write'), ('reasoning_tokens', 'reasoning_tokens')]:
|
|
values = [getattr(u, field) for u in sample.model_usage.values()]
|
|
expected_tokens = sum(v or 0 for v in values) if any(v is not None for v in values) else None
|
|
c[exported + '_matches_log'] = row[exported] == expected_tokens
|
|
for key in ['message_limit', 'token_limit', 'time_limit']:
|
|
c[key] = cfg.get(key) == manifest[key]
|
|
for key in ['temperature', 'reasoning_effort']:
|
|
c[key] = generation.get(key) == manifest[key]
|
|
final = json.loads((export / row['report_directory'] / 'final-artifacts.json').read_text())
|
|
c['tests_not_modified'] = final.get('test_modified_ever') is False
|
|
test_body = meta['test_patch'].replace('def check(', 'def test_func(')
|
|
expected_test = f'from func import {meta["entry_point"]}\n\n{test_body}\n\nif __name__ == "__main__":\n test_func({meta["entry_point"]})\n print("All tests passed!")\n'
|
|
c['final_test_bytes_match_input'] = final.get('submitted_artifacts', {}).get('test.py') == expected_test
|
|
c['initial_scratch_empty'] = sample.metadata.get('scratch_initial_files') == {}
|
|
for op in [o for o in operations if o['episode_id'] == row['episode_id']]:
|
|
event = sample.events[op['event_index']]
|
|
message = sample.messages[op['message_index']]
|
|
c[f'audit_{op["id"]}_exact_delivery'] = (event.id == message.tool_call_id == op['tool_call_id'] and
|
|
json.loads(message.text) == op['response'])
|
|
sample_checks.append({'episode_id': row['episode_id'], 'checks': c, 'config': cfg, 'generation': generation})
|
|
failures = [{'scope': 'run', 'check': k} for k, v in checks.items() if not v]
|
|
failures += [{'scope': x['episode_id'], 'check': k} for x in sample_checks for k, v in x['checks'].items() if not v]
|
|
return {'run_checks': checks, 'sources': sources, 'samples': sample_checks, 'failures': failures,
|
|
'totals': {k: sum(e[k] or 0 for e in episodes) for k in ['input_tokens', 'output_tokens', 'total_tokens', 'reasoning_tokens', 'cache_read_tokens']}}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--run', required=True, type=Path)
|
|
parser.add_argument('--export', required=True, type=Path)
|
|
parser.add_argument('--out', required=True, type=Path)
|
|
args = parser.parse_args()
|
|
result = validate(args.run.resolve(), args.export.resolve())
|
|
with args.out.open('x') as f:
|
|
json.dump(result, f, indent=2)
|
|
f.write('\n')
|
|
print(json.dumps({'failures': result['failures'], 'totals': result['totals']}))
|