tricu

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

native_header_equivalence.py (4716B)


      1 """Verification-only proof for mechanical native headers, never a compiler.
      2 
      3 A completed generation compiled its retained native overlays. Accept only map
      4 kind changes from unit/reexport to native whose current authored bytes equal
      5 those overlays exactly. No body, dependency, export, namespace or path change
      6 can pass. This does NOT establish a new generation or new map receipt.
      7 """
      8 import hashlib
      9 import json
     10 from pathlib import Path
     11 from bootstrap_generations import source_map
     12 from generation_backend import BACKENDS, recorded_backend
     13 
     14 
     15 def digest(domain, payload):
     16     return hashlib.sha256(domain.encode() + b'\0' + payload).hexdigest()
     17 
     18 
     19 def native_header_equivalence(generation: Path, repo: Path, map_path: Path):
     20     status = json.loads((generation / 'status.json').read_text())
     21     provenance = json.loads((generation / 'inputs.json').read_text())
     22     if not status['complete'] or status['root'] != 'source_map':
     23         raise ValueError('header equivalence requires a completed source_map generation')
     24     old_text = (generation / 'source-map.v1').read_text()
     25     if hashlib.sha256(old_text.encode()).hexdigest() != provenance['map_sha256']:
     26         raise ValueError('retained map digest mismatch')
     27     new_text, _, _ = source_map(map_path, repo, 'source_map')
     28     old_rows = [line.split('\t') for line in old_text.splitlines()[1:]]
     29     new_rows = [line.split('\t') for line in new_text.splitlines()[1:]]
     30     if len(old_rows) != len(new_rows):
     31         raise ValueError('header equivalence cannot change map membership')
     32     changed, stages = [], {}
     33     backend = recorded_backend(provenance)
     34     if recorded_backend(status) != backend:
     35         raise ValueError('backend provenance mismatch')
     36     compiler = provenance['compiler_bundle_sha256']
     37     options = digest('arboricx.build-options.v1', BACKENDS[backend][1].encode())
     38     cas = generation / 'cas'
     39 
     40     def verified(domain, pin):
     41         if len(pin) != 64 or any(c not in '0123456789abcdef' for c in pin):
     42             raise ValueError('invalid retained object pin')
     43         payload = (cas / 'objects' / pin[:3] / pin).read_bytes()
     44         if digest(domain, payload) != pin:
     45             raise ValueError('retained object digest mismatch')
     46         return payload
     47 
     48     for old, new in zip(old_rows, new_rows):
     49         if old[1:] != new[1:]:
     50             raise ValueError('header equivalence cannot change map edges, paths or row order')
     51         _, alias, _, path, _ = new
     52         payload = (repo / path).read_bytes()
     53         overlay = (generation / 'overlay' / (alias + '.tri')).read_bytes()
     54         if old[0] != new[0]:
     55             if old[0] not in ('unit', 'reexport') or new[0] != 'native':
     56                 raise ValueError('unsupported map-kind transition')
     57             if payload != overlay:
     58                 raise ValueError('native module differs from retained staged bytes: ' + alias)
     59             changed.append(alias)
     60         elif hashlib.sha256(payload).hexdigest() != provenance['sources'][alias]:
     61             raise ValueError('non-header source change: ' + alias)
     62         # Authenticate the actual compiler input, not merely an unauthenticated
     63         # overlay file. The completed generation and its dependency closure remain
     64         # the origin of this fixture; packing independently verifies every tree.
     65         version, key, manifest, receipt = (cas / 'cache/source-build' / alias).read_text().split('\t')
     66         if version != 'arboricx.source-build-cache.v1':
     67             raise ValueError('invalid retained checkpoint')
     68         rows = verified('arboricx.build-receipt.v1', receipt).decode().splitlines()
     69         if rows[:5] != ['arboricx.build-receipt.v1',
     70                         'source\t' + digest('arboricx.source.v1', overlay),
     71                         'compiler\t' + compiler, 'options\t' + options,
     72                         'output\t' + manifest]:
     73             raise ValueError('retained staged-source receipt mismatch: ' + alias)
     74         key_bytes = ('arboricx.source-build-key.v1\n' + '\n'.join(rows[1:4]) + '\n' +
     75                      ('\n'.join(rows[5:]) + '\n' if rows[5:] else '')).encode()
     76         if digest('arboricx.source-build-key.v1', key_bytes) != key:
     77             raise ValueError('retained checkpoint key mismatch')
     78         verified('arboricx.module-manifest.v1', manifest)
     79         stages[alias] = digest('arboricx.source.v1', overlay)
     80     if not changed:
     81         raise ValueError('no native header transitions to verify')
     82     return new_rows, dict(kind='byte-identical-native-headers.v1',
     83                          origin_map_sha256=provenance['map_sha256'],
     84                          current_map_sha256=hashlib.sha256(new_text.encode()).hexdigest(),
     85                          changed=changed, staged_sources=stages)