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)