local_control_consumers.py (6860B)
1 #!/usr/bin/env python3 2 """Opt-in local-control fragments: full constructed code, values and effect traces. 3 4 Uses authenticated published HTTP/server kernels as unchanged baselines and the 5 existing independent consumer oracles. Never replaces either fixture cohort. 6 Cost samples are whole CLI invocations, not live-network benchmarks. 7 """ 8 import argparse 9 import json 10 from pathlib import Path 11 import time 12 from bundle_dag import read 13 from fixture_sources import verify 14 from http_headers import LEAF, TRUE, byte_tree as bs, number, pair, tree_list, expect, sha 15 from local_control import decode_text 16 from source_order import Runner, field 17 from registry_server import pure_cases, effect_cases 18 19 ROOT = Path(__file__).resolve().parent.parent 20 SOURCES = { 21 'ascii': '''main byte = case (Nat.inRange byte 65 90) of 22 ^true = Nat.add byte 32 23 _ = byte''', 24 'hash': '''main hash = (let rec 25 walk [] 0 = true 26 walk _ 0 = false 27 walk [byte | rest] remaining = case (bundleHexByte? byte) of 28 ^true = walk rest (Nat.subtract remaining 1) 29 _ = false 30 walk _ _ = false 31 in walk hash 64)''', 32 'serve': '''main root hash = (do bind 33 reply <- getBundleByHash root hash 34 case reply of 35 ((t t), (bytes, _)) = pure (bundleResponse bytes) 36 (t, (status, message)) = pure (errorResponse status message) 37 _ = pure (errorResponse 500 "Invalid bundle result"))''', 38 } 39 40 41 def expanded(bundle, name): 42 """Bounded iterative wire expansion; no evaluation or source frontend.""" 43 nodes, exports = bundle[2:4] 44 out = bytearray(); pending = [exports[name]] 45 while pending: 46 tag, *children = nodes[pending.pop()] 47 out.append(tag); pending.extend(reversed(children)) 48 if len(out) > 32*1024*1024: 49 raise ValueError('fragment environment tree exceeds 32 MiB') 50 return bytes(out) 51 52 53 def main(): 54 p = argparse.ArgumentParser(description=__doc__) 55 p.add_argument('tricu', type=Path) 56 p.add_argument('--unit', type=Path, required=True) 57 p.add_argument('--driver', type=Path, required=True) 58 p.add_argument('--directory', type=Path, required=True) 59 args = p.parse_args() 60 root = args.directory.resolve(); root.mkdir(parents=True, exist_ok=False) 61 fixtures = ROOT/'test/fixtures'; verify(fixtures) 62 runner = Runner(args.tricu, root) 63 compiler = read(fixtures/'compiler-library.arboricx') 64 support = read(fixtures/'support-library.arboricx') 65 bindings = [pair(bs(name.encode()), expanded(compiler, old)) for name, old in [ 66 ('true', 'base.true'), ('false', 'base.false'), ('Nat.add', 'native_math.add'), 67 ('Nat.inRange', 'native_math.inRange'), ('Nat.subtract', 'native_math.subtract'), 68 ('pure', 'io.pure'), ('bind', 'io.bind')]] 69 bindings += [pair(bs(name.encode()), expanded(support, old)) for name, old in [ 70 ('bundleHexByte?', 'arboricx_server.bundleHexByte?'), 71 ('getBundleByHash', 'arboricx_server.getBundleByHash'), 72 ('bundleResponse', 'arboricx_server.bundleResponse'), ('errorResponse', 'http.errorResponse')]] 73 environment = root/'environment.tree'; environment.write_bytes(tree_list(bindings)) 74 report = dict(complete=False, cases=0, fragments={}, inputs={}) 75 for label, path in [('unit', args.unit), ('driver', args.driver), 76 ('compiler-reference', fixtures/'compiler-library.arboricx'), 77 ('support-reference', fixtures/'support-library.arboricx')]: 78 selected = root/(label+'.arboricx'); selected.write_bytes(path.read_bytes()) 79 report['inputs'][label] = sha(selected.read_bytes()) 80 try: 81 unit = runner.imported(root/'unit.arboricx', 'unit') 82 driver = runner.imported(root/'driver.arboricx', 'driver') 83 for label, source in SOURCES.items(): 84 source_path = root/(label+'.source.tree'); source_path.write_bytes(bs(source.encode())) 85 answer = runner.invoke(label+'-compile', unit, 'compileStableModuleDefinitions', 86 ['--arg-tree-file', environment, '--arg-tree-file', source_path]) 87 expect(field(answer, 0), TRUE, label+' compile') 88 definitions = field(answer, 1, 0) 89 if decode_text(field(definitions, 0, 0)) != 'main': raise ValueError('unexpected export') 90 expect(field(definitions, 1), LEAF, label+' no escaped locals') 91 expect(field(answer, 1, 1), LEAF, label+' compile remainder') 92 code = field(definitions, 0, 1) 93 baseline = expanded(support, {'ascii':'http.toLowerAsciiByte', 94 'hash':'arboricx_server.bundleHashValid?', 'serve':'arboricx_server.serveBundleHash'}[label]) 95 report['fragments'][label] = dict(source_sha256=sha(source.encode()), 96 candidate_wire_bytes=len(code), baseline_wire_bytes=len(baseline)) 97 if label == 'ascii': 98 rows = [(number(n),) for n in range(256)] 99 expected = tree_list([number(n+32 if 65 <= n <= 90 else n) for n in range(256)]) 100 elif label == 'hash': 101 cases = [c for c in pure_cases() if c.export == 'bundleHashValid?'] 102 rows = [c.arguments for c in cases]; expected = tree_list([c.expected for c in cases]) 103 else: 104 cases = [c for c in effect_cases() if c.export == 'serveBundleHash'] 105 for variant, tree in [('baseline', baseline), ('candidate', code)]: 106 code_path = root/(label+'-'+variant+'.tree'); code_path.write_bytes(tree) 107 if label != 'serve': 108 rows_path = root/(label+'-rows.tree'); rows_path.write_bytes(tree_list([tree_list(r) for r in rows])) 109 actual = runner.invoke(label+'-'+variant, driver, 'values', 110 ['--arg-tree-file', code_path, '--arg-tree-file', rows_path]) 111 expect(actual, expected, label+' '+variant+' independent values') 112 else: 113 for i, case in enumerate(cases): 114 paths = [] 115 for j, argument in enumerate(case.arguments): 116 path = root/f'serve-{i}-{j}.tree'; path.write_bytes(argument) 117 paths.extend(['--arg-tree-file', path]) 118 actual = runner.invoke('serve-'+variant+'-'+str(i), driver, 'effects', 119 ['--arg-tree-file', code_path, *paths]) 120 expect(actual, case.expected, case.label+' '+variant+' independent full trace') 121 report['cases'] += len(cases) if label == 'serve' else len(rows) 122 report['complete'] = True 123 finally: 124 report['seconds'] = time.monotonic()-runner.started 125 (root/'report.json').write_text(json.dumps(report, indent=2)+'\n') 126 print(f'PASS: {report["cases"]} consumer cases against candidate and baseline') 127 128 129 if __name__ == '__main__': main()