generation_io.py (3653B)
1 """Declared generation framing, never inferred from execution or tree shape. 2 3 Missing metadata denotes isolated historical v1. New records carry both versions: 4 a transition can execute v1 while producing v2, using a separately selected output 5 adapter. These are caller assertions bound to recorded artifact bytes, not a proof 6 of arbitrary worker semantics or an authority sandbox. 7 """ 8 import hashlib 9 import argparse 10 11 FIELDS = ('execution_io_version', 'produced_io_version', 'produced_adapter_sha256') 12 13 14 def version(value): 15 if type(value) is not int or value not in (1, 2): 16 raise ValueError('IO version must be integer 1 or 2') 17 return value 18 19 20 def cli_version(text): 21 if text not in ('1', '2'): 22 raise argparse.ArgumentTypeError('IO version must be 1 or 2') 23 return int(text) 24 25 26 def versions(record): 27 if not isinstance(record, dict): 28 raise ValueError('invalid generation IO metadata') 29 present = [key in record for key in FIELDS] 30 if any(present) and not all(present): 31 raise ValueError('incomplete generation IO metadata') 32 if not any(present): 33 return 1, 1 34 pin = record['produced_adapter_sha256'] 35 if not isinstance(pin, str) or len(pin) != 64 or any(c not in '0123456789abcdef' for c in pin): 36 raise ValueError('invalid produced adapter digest') 37 return version(record['execution_io_version']), version(record['produced_io_version']) 38 39 40 def produced_adapter(directory, record): 41 versions(record) 42 name = 'produced-io-adapter.arboricx' if 'produced_io_version' in record else 'io-adapter.arboricx' 43 payload = (directory / name).read_bytes() 44 expected = record.get('produced_adapter_sha256', record.get('adapter_sha256')) 45 if hashlib.sha256(payload).hexdigest() != expected: 46 raise ValueError('produced adapter identity mismatch') 47 return payload 48 49 50 def execution_flags(io_version, host='haskell'): 51 value = version(io_version) 52 if host not in ('haskell', 'haskell-ffi'): 53 raise ValueError('selected execution host does not support IO') 54 if host == 'haskell-ffi' and value != 2: 55 raise ValueError('Haskell embedding requires IO version 2') 56 return ['--unsafe-io'] if value == 1 else [] 57 58 59 def check_pair(first, second): 60 _, output = versions(first) 61 execution, reproduced = versions(second) 62 if output != execution or output != reproduced: 63 raise ValueError('generation IO version chain mismatch') 64 a = first.get('produced_adapter_sha256', first['adapter_sha256']) 65 b = second.get('produced_adapter_sha256', second['adapter_sha256']) 66 if a != second['adapter_sha256'] or a != b: 67 raise ValueError('generation adapter chain mismatch') 68 69 70 def check_invocation(record, command, host='haskell'): 71 execution, _ = versions(record) 72 execution_flags(execution, host) 73 if not isinstance(command, list) or not all(isinstance(arg, str) for arg in command): 74 raise ValueError('invalid generation invocation') 75 if host != 'haskell': 76 return 77 grants = {'--unsafe-io', '--allow-read', '--allow-write', '--allow-read-all', '--allow-write-all'} 78 if execution == 2 and any(arg.split('=', 1)[0] in grants for arg in command): 79 raise ValueError('invocation IO authority mismatch') 80 if execution == 1 and '--unsafe-io' not in command: 81 raise ValueError('invocation IO authority mismatch') 82 83 84 def compiler_flags(io_version): 85 # Retained v1 runtimes predate the selector. Their documented default is v1; 86 # do not probe failure and retry. HostIO always requires the explicit new flag. 87 return [] if version(io_version) == 1 else ['--toolchain-io-version', '2']