bootstrap_retention_probe.py (5682B)
1 #!/usr/bin/env python3 2 """Isolated retained-toolchain probes, invoked only by bootstrap_retention.py. 3 No compiler generation checkpoint or fixture publication is created here. 4 """ 5 import argparse 6 import hashlib 7 import json 8 from pathlib import Path 9 import subprocess 10 import sys 11 12 from bootstrap_retention import REPO, sha, save 13 from bootstrap_acceptance import verify 14 15 16 def main(): 17 p = argparse.ArgumentParser(description=__doc__) 18 p.add_argument('--root', type=Path, required=True) 19 p.add_argument('--plan', type=Path, required=True) 20 p.add_argument('--directory', type=Path, required=True) 21 a = p.parse_args() 22 plan = json.loads(a.plan.read_text()); config = plan['config'] 23 root, out = a.root, a.directory 24 # Refuse a non-isolated invocation; every removal target must be invisible. 25 if any((root / row['objects']).exists() for row in plan['stores']): 26 raise ValueError('obsolete object stores are still visible') 27 report = dict(complete=False, obsolete_stores_hidden=True, toolchains=[]) 28 def run(label, command, seconds=900): 29 with (out / (label + '.log')).open('w') as log: 30 subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, check=True, timeout=seconds) 31 try: 32 for t in config['toolchains']: 33 role = t['role']; generation = root / t['generation']; source = root / t['source'] 34 acceptance = root / t['acceptance']; runtime = t['runtime'] 35 accepted = json.loads(acceptance.read_text()) 36 # These cohorts still include first generation, so rerun full-pair 37 # acceptance as well as the generation-two audit. Portable-only 38 # retirement of first generation needs an explicitly different gate. 39 fresh = verify(Path(accepted['generations'][0]), generation, Path(runtime), source) 40 if fresh['artifacts'] != accepted['artifacts']: 41 raise ValueError('retained acceptance artifacts differ') 42 run(role + '-portable', [sys.executable, str(REPO / 'test/portable_cli.py'), runtime, 43 '--backend', 'stable', '--indexed', '--compiler-bundle', str(generation / 'build-stable.arboricx')]) 44 run(role + '-smoke', [sys.executable, str(REPO / 'test/bootstrap_smoke.py'), runtime, 45 '--generation', str(generation), '--acceptance', str(acceptance), 46 '--source-root', str(source), '--directory', str(out / (role + '-smoke'))]) 47 scoped = out / (role + '-map') 48 run(role + '-map', [sys.executable, str(REPO / 'test/bootstrap_generations.py'), 49 str(generation), str(scoped), '--tricu', runtime, '--source-root', str(source), 50 '--map', str(generation / 'source-map.v1'), '--root', 'native_math', '--backend', 'stable', 51 '--fresh', '--adapter', str(generation / 'io-adapter.arboricx'), '--capabilities', '2', 52 '--memory-mib', '8192', '--wall-seconds', '900']) 53 run(role + '-map-audit', [sys.executable, str(REPO / 'test/bootstrap_store_check.py'), 54 str(scoped), '--source-root', str(source), '--root', 'native_math']) 55 old = (generation / 'cas/cache/source-build/native_math').read_text().split('\t')[2] 56 new = (scoped / 'cas/cache/source-build/native_math').read_text().split('\t')[2] 57 if old != new: 58 raise ValueError('scoped map-builder changed complete native_math manifest') 59 smoke = json.loads((out / (role + '-smoke/report.json')).read_text()) 60 if not smoke['complete'] or not smoke['wrong_output_rejected']: 61 raise ValueError('incomplete retained compiler smoke') 62 report['toolchains'].append(dict(role=role, artifacts=fresh['artifacts'], 63 acceptance_sha256=sha(acceptance), runtime_sha256=sha(runtime), 64 fresh_pair_audits=fresh['audits'], portable_cli=True, 65 complete_results=len(smoke['cases']), scoped_map_manifest=new)) 66 support = config['support'] 67 runtime = next(t['runtime'] for t in config['toolchains'] if t['role'] == support['toolchain']) 68 publication = out / 'support-reproduced' 69 command = [sys.executable, str(REPO / 'test/repair_support_fixture.py'), runtime, 70 str(root / support['preparation']), '--output-directory', str(publication), 71 '--capabilities', '2', '--verification-memory-mib', '8192'] 72 if support.get('relocate_patterns'): 73 command += ['--relocate-patterns'] 74 run('support', command, seconds=1500) 75 compared = {} 76 for name in ['support-library.arboricx', 'support-rebuilt.arboricx', 'support-library.v1']: 77 if sha(publication / name) != sha(REPO / 'test/fixtures' / name): 78 raise ValueError('current support fixture did not reproduce: ' + name) 79 compared[name] = sha(publication / name) 80 expected = REPO / 'test/fixtures/support-receipts' 81 actual = publication / 'support-receipts' 82 if {p.name: sha(p) for p in actual.iterdir()} != {p.name: sha(p) for p in expected.iterdir()}: 83 raise ValueError('support receipt/manifests did not reproduce') 84 run('fixture-freshness', [sys.executable, str(REPO / 'test/fixture_sources.py'), 'current']) 85 report.update(complete=True, support_sha256=compared, 86 support_receipts_reproduced=True, fixture_sources_current=True) 87 finally: 88 save(out / 'report.json', report) 89 print('PASS isolated pair audits, CLI integrity/diagnostics, correctness smokes, scoped map-builds and support reproduction') 90 91 92 if __name__ == '__main__': 93 main()