tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

bootstrap_acceptance.py (5183B)


      1 #!/usr/bin/env python3
      2 """Strict opt-in stable bootstrap acceptance, independent of cached fixture checks.
      3 Run only after two successful fresh-store generations. This audits existing
      4 outputs; it does not compile source. Failed verification never accepts a baseline.
      5 """
      6 import argparse
      7 import hashlib
      8 import json
      9 from pathlib import Path
     10 from process_rss import run_guarded
     11 import sys
     12 from bootstrap_generations import source_map, snapshot
     13 
     14 REPO = Path(__file__).resolve().parent.parent
     15 ARTIFACTS = ('rebuilt-compiler.arboricx', 'build.arboricx', 'build-stable.arboricx')
     16 
     17 
     18 def sha(path):
     19     return hashlib.sha256(path.read_bytes()).hexdigest()
     20 
     21 
     22 def verify_generation(run, runtime, source_root=None, execution_host='haskell'):
     23     """Authenticate and independently audit one full fresh compiler generation.
     24 
     25     Execution host is caller-selected, never inferred from untrusted provenance.
     26     Pair acceptance below remains explicitly Haskell-only.
     27     """
     28     status = json.loads((run / 'status.json').read_text())
     29     inputs = json.loads((run / 'inputs.json').read_text())
     30     if not status.get('complete') or status.get('backend') != 'stable':
     31         raise ValueError('completed stable generations required')
     32     if not inputs.get('fresh_store') or not status.get('fresh_store'):
     33         raise ValueError('fresh-store execution evidence required; seeded checkpoints are not reproduction')
     34     if any(status.get(key) != value for key, value in inputs.items()):
     35         raise ValueError('status/input provenance mismatch')
     36     if inputs.get('execution_host', 'haskell') != execution_host:
     37         raise ValueError('execution host mismatch')
     38     root = source_root or Path(inputs['source_root'])
     39     text, entries, _ = source_map(run / 'source-map.v1', root, 'source_map')
     40     actual = snapshot(text, entries, root)
     41     if inputs.get('root') != 'source_map' or any(inputs.get(k) != v for k, v in actual.items()):
     42         raise ValueError('frozen source/map fingerprint mismatch')
     43     if sha(runtime) != inputs['runtime_sha256']:
     44         raise ValueError('runtime identity mismatch')
     45     if sha(run / 'compiler.arboricx') != inputs['compiler_bundle_sha256']:
     46         raise ValueError('compiler bundle identity mismatch')
     47     if sha(run / 'io-adapter.arboricx') != inputs['adapter_sha256']:
     48         raise ValueError('adapter identity mismatch')
     49     for name in ARTIFACTS:
     50         if sha(run / name) != status['artifacts'][name]['sha256']:
     51             raise ValueError('packaged artifact identity mismatch: ' + name)
     52     command = [sys.executable, str(REPO / 'test/bootstrap_store_check.py'), str(run)]
     53     if source_root:
     54         command += ['--source-root', str(source_root)]
     55     audit, _ = run_guarded(command, memory_mib=8192, seconds=600)
     56     return inputs, status, audit
     57 
     58 
     59 def verify(first, second, tricu, source_root=None):
     60     first, second = first.resolve(), second.resolve()
     61     if first == second:
     62         raise ValueError('two independent generation directories required')
     63     records = []
     64     audits = []
     65     for run in (first, second):
     66         inputs, _, audit = verify_generation(run, tricu, source_root)
     67         audits.append(audit)
     68         records.append(inputs)
     69     for key in ('sources', 'map_sha256', 'backend', 'root', 'runtime_sha256', 'adapter_sha256'):
     70         if records[0][key] != records[1][key]:
     71             raise ValueError('generation inputs differ: ' + key)
     72     if sha(first / 'rebuilt-compiler.arboricx') != records[1]['compiler_bundle_sha256']:
     73         raise ValueError('second generation did not execute the first rebuilt compiler')
     74     for name in ARTIFACTS:
     75         if sha(first / name) != sha(second / name):
     76             raise ValueError('artifact reproduction failure: ' + name)
     77     # Check actual manifests, not the runner's recorded comparison booleans.
     78     for alias in records[0]['sources']:
     79         a = (first / 'cas/cache/source-build' / alias).read_text().split('\t')[2]
     80         b = (second / 'cas/cache/source-build' / alias).read_text().split('\t')[2]
     81         if a != b:
     82             raise ValueError('manifest reproduction failure: ' + alias)
     83     return dict(accepted=True, backend='stable', generations=[str(first), str(second)],
     84                 inputs=records, audits=audits,
     85                 artifacts={name: sha(second / name) for name in ARTIFACTS})
     86 
     87 
     88 def main():
     89     parser = argparse.ArgumentParser(description=__doc__)
     90     parser.add_argument('first', type=Path)
     91     parser.add_argument('second', type=Path)
     92     parser.add_argument('--tricu', type=Path, required=True)
     93     parser.add_argument('--source-root', type=Path, help='explicit relocated frozen source for restore validation')
     94     parser.add_argument('--output', type=Path, required=True, help='new acceptance evidence file; never overwritten')
     95     args = parser.parse_args()
     96     if args.output.exists():
     97         parser.error('acceptance evidence already exists; do not overwrite')
     98     evidence = verify(args.first, args.second, args.tricu.resolve(), args.source_root)
     99     args.output.write_text(json.dumps(evidence, indent=2) + '\n')
    100     print('Accepted independently audited fresh-store stable reproduction: ' + str(args.output))
    101 
    102 
    103 if __name__ == '__main__':
    104     main()