tricu

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

commit a3b1cc20776e20ddcb160db9d7ba520caa92e91b
parent cbf109e13134a62f66fb11f9af2e28eb78e3e3d8
Author: James Eversole <james@eversole.co>
Date:   Fri, 16 Oct 2026 12:27:07 -0500

Define isolated cross-runtime reproduction with frozen inputs and independent acceptance

Validated native_math compilation on both hosts under 1 GiB/120s limits with exact audited object equality. Re-audited retained Haskell acceptance after extracting the per-generation verifier. Full H1/H2/H3/Z3 experiment and retention remain pending.

Diffstat:
M.gitignore | 3+++
Mext/zig/README.md | 22++++++++++++++++++++++
Mtest/bootstrap_acceptance.py | 64++++++++++++++++++++++++++++++++++++++--------------------------
Atest/bootstrap_cross_host.py | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 257 insertions(+), 26 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -17,3 +17,6 @@ dist* .pi paseo.json + +# Python harness bytecode +test/__pycache__/ diff --git a/ext/zig/README.md b/ext/zig/README.md @@ -280,3 +280,25 @@ a 512 MiB address-space cap, and Haskell has a 512 MiB managed-heap cap and an 8 RTS stack cap. Small successful samples should remain far below these limits. These measurements are descriptive host smokes, not compiler or bootstrap speed comparisons, and no timing threshold is a test gate. + +### Isolated mapped-library probe + +`test/bootstrap_cross_host.py --probe` runs the frozen `native_math` closure on +both hosts, with identical kits and a 1 GiB/120-second budget per job. It requires +Linux `bwrap` with working user/PID namespaces and a Nix Python environment: + +```sh +nix develop -c python3 test/bootstrap_cross_host.py --probe \ + --source-commit COMMITTED_SOURCE --directory /tmp/cross-host-probe-new \ + --tricu "$HASKELL/bin/tricu" --zig-library "$ZIG/lib/libarboricx.so" +``` + +Each sandbox mounts only the read-only source snapshot and kit, Nix dependencies, +an initially empty writable work directory, and ephemeral system directories. +Prior generation stores and the live checkout are absent. The ordinary generation +supervisor runs inside the sandbox, so its RSS monitor observes the execution +child rather than the outer isolation wrapper. The probe compares complete +independently audited object inventories. Its experiment definition records the +source commit/archive, mapped-input hashes, runtimes, Python, tooling, limits and +comparison boundaries. Historical `ext/zig/result` is the sole excluded archive +entry: a tracked absolute build-output symlink, not a compiler input. diff --git a/test/bootstrap_acceptance.py b/test/bootstrap_acceptance.py @@ -19,6 +19,43 @@ def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() +def verify_generation(run, runtime, source_root=None, execution_host='haskell'): + """Authenticate and independently audit one full fresh compiler generation. + + Execution host is caller-selected, never inferred from untrusted provenance. + Pair acceptance below remains explicitly Haskell-only. + """ + status = json.loads((run / 'status.json').read_text()) + inputs = json.loads((run / 'inputs.json').read_text()) + if not status.get('complete') or status.get('backend') != 'stable': + raise ValueError('completed stable generations required') + if not inputs.get('fresh_store') or not status.get('fresh_store'): + raise ValueError('fresh-store execution evidence required; seeded checkpoints are not reproduction') + if any(status.get(key) != value for key, value in inputs.items()): + raise ValueError('status/input provenance mismatch') + if inputs.get('execution_host', 'haskell') != execution_host: + raise ValueError('execution host mismatch') + root = source_root or Path(inputs['source_root']) + text, entries, _ = source_map(run / 'source-map.v1', root, 'source_map') + actual = snapshot(text, entries, root) + if inputs.get('root') != 'source_map' or any(inputs.get(k) != v for k, v in actual.items()): + raise ValueError('frozen source/map fingerprint mismatch') + if sha(runtime) != inputs['runtime_sha256']: + raise ValueError('runtime identity mismatch') + if sha(run / 'compiler.arboricx') != inputs['compiler_bundle_sha256']: + raise ValueError('compiler bundle identity mismatch') + if sha(run / 'io-adapter.arboricx') != inputs['adapter_sha256']: + raise ValueError('adapter identity mismatch') + for name in ARTIFACTS: + if sha(run / name) != status['artifacts'][name]['sha256']: + raise ValueError('packaged artifact identity mismatch: ' + name) + command = [sys.executable, str(REPO / 'test/bootstrap_store_check.py'), str(run)] + if source_root: + command += ['--source-root', str(source_root)] + audit, _ = run_guarded(command, memory_mib=8192, seconds=600) + return inputs, status, audit + + def verify(first, second, tricu, source_root=None): first, second = first.resolve(), second.resolve() if first == second: @@ -26,32 +63,7 @@ def verify(first, second, tricu, source_root=None): records = [] audits = [] for run in (first, second): - status = json.loads((run / 'status.json').read_text()) - inputs = json.loads((run / 'inputs.json').read_text()) - if not status.get('complete') or status.get('backend') != 'stable': - raise ValueError('completed stable generations required') - if not inputs.get('fresh_store') or not status.get('fresh_store'): - raise ValueError('fresh-store execution evidence required; seeded checkpoints are not reproduction') - if any(status.get(key) != value for key, value in inputs.items()): - raise ValueError('status/input provenance mismatch') - root = source_root or Path(inputs['source_root']) - text, entries, _ = source_map(run / 'source-map.v1', root, 'source_map') - actual = snapshot(text, entries, root) - if inputs.get('root') != 'source_map' or any(inputs.get(k) != v for k, v in actual.items()): - raise ValueError('frozen source/map fingerprint mismatch') - if sha(tricu) != inputs['runtime_sha256']: - raise ValueError('runtime identity mismatch') - if sha(run / 'compiler.arboricx') != inputs['compiler_bundle_sha256']: - raise ValueError('compiler bundle identity mismatch') - if sha(run / 'io-adapter.arboricx') != inputs['adapter_sha256']: - raise ValueError('adapter identity mismatch') - for name in ARTIFACTS: - if sha(run / name) != status['artifacts'][name]['sha256']: - raise ValueError('packaged artifact identity mismatch: ' + name) - command = [sys.executable, str(REPO / 'test/bootstrap_store_check.py'), str(run)] - if source_root: - command += ['--source-root', str(source_root)] - audit, _ = run_guarded(command, memory_mib=8192, seconds=600) + inputs, _, audit = verify_generation(run, tricu, source_root) audits.append(audit) records.append(inputs) for key in ('sources', 'map_sha256', 'backend', 'root', 'runtime_sha256', 'adapter_sha256'): diff --git a/test/bootstrap_cross_host.py b/test/bootstrap_cross_host.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""Opt-in isolated H1 -> H2 -> {H3,Z3} reproduction, or bounded native_math probe. + +Every job sees only /source, /kit, /work, /nix/store and ephemeral /tmp,/proc,/dev. +The ordinary supervisor runs INSIDE isolation and monitors the actual execution +child. Haskell transport is common; prior stores are never mounted. This does +not perform toolchain promotion, smokes or mandatory rolling retention. +""" +import argparse +import json +from pathlib import Path +import shutil +import subprocess +import sys +import tarfile + +from bootstrap_acceptance import ARTIFACTS, sha, verify, verify_generation +from bootstrap_generations import source_map, snapshot +from bootstrap_inputs import load_kit +from bootstrap_retention_lock import protect_generations +from process_rss import run_guarded + +ROOT = Path(__file__).resolve().parent.parent + + +def save(path, value): + path.write_text(json.dumps(value, indent=2)+'\n') + + +def inventory(run): + return {p.name:sha(p) for p in (run/'cas/objects').glob('*/*')} + + +def verify_cross(first, second, control, zig, runtime, library, source): + if len({p.resolve() for p in (first,second,control,zig)}) != 4: + raise ValueError('four distinct fresh generations required') + pair = verify(first, second, runtime, source) + control_pair = verify(second, control, runtime, source) + zi, zs, audit = verify_generation(zig, library, source, 'zig') + hi = control_pair['inputs'][1] + for key in ('sources','map_sha256','backend','root','adapter_sha256', + 'compiler_bundle_sha256','compiler_tree','compiler_tree_kind', + 'worker_export','backend_options'): + if zi[key] != hi[key]: + raise ValueError('cross-host input mismatch: '+key) + if zi.get('transport_runtime_sha256') != sha(runtime) or zi.get('transport_host') != 'haskell': + raise ValueError('cross-host transport identity mismatch') + if zi.get('executor_sha256') != sha(source/'test/zig_generation_executor.py'): + raise ValueError('execution launcher identity mismatch') + request = json.loads((zig/'execution-request.json').read_text()) + if request != dict(worker_export=zi['worker_export'], + compiler_bundle_sha256=zi['compiler_bundle_sha256'], + adapter_sha256=zi['adapter_sha256'], + arguments=['/source','/work/generation/overlay','/work/generation/cas', + zi['compiler_bundle_sha256'],(zig/'source-map.v1').read_text(),'source_map']): + raise ValueError('isolated Zig execution request mismatch') + for name in ARTIFACTS: + if sha(second/name) != sha(zig/name): + raise ValueError('cross-host packaged artifact mismatch: '+name) + # Byte-identical full inventories include manifests, receipts and every + # published indexed object, not just a subset of selected export hashes. + expected = inventory(second) + for run in (control,zig): + if inventory(run) != expected: + raise ValueError('complete canonical object/receipt inventory differs: '+str(run)) + for alias in hi['sources']: + if (second/'cas/cache/source-build'/alias).read_bytes() != (run/'cas/cache/source-build'/alias).read_bytes(): + raise ValueError('complete checkpoint mismatch: '+alias) + return dict(accepted=True, scope='cross-host-fixed-point-not-retention', + generations=list(map(str,(first,second,control,zig))), + haskell_pair=pair, haskell_control=control_pair, + zig_inputs=zi, zig_audit=audit, objects=expected, + artifacts={name:sha(zig/name) for name in ARTIFACTS}) + + +def sandbox_command(source, kit, stage, runtime, library, host, root, memory, seconds): + command = ['bwrap','--die-with-parent','--unshare-all', + '--ro-bind','/nix/store','/nix/store','--proc','/proc','--dev','/dev', + '--tmpfs','/tmp','--ro-bind',str(source),'/source', + '--ro-bind',str(kit),'/kit','--bind',str(stage),'/work', + '--chdir','/source','--setenv','HOME','/tmp','--setenv','TMPDIR','/tmp', + '--setenv','PYTHONDONTWRITEBYTECODE','1', + sys.executable,'/source/test/bootstrap_generations.py','/work/generation', + '--bootstrap-kit','/kit','--map','/source/bootstrap/compiler-source-map.v1', + '--source-root','/source','--root',root,'--backend','stable','--fresh', + '--tricu',str(runtime),'--execution-host',host,'--capabilities','8', + '--memory-mib',str(memory),'--wall-seconds',str(seconds), + '--initial-seconds',str(min(600,seconds)), + '--extended-seconds',str(min(1800,seconds))] + if host=='zig': command += ['--zig-library',str(library)] + return command + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--directory',type=Path,required=True) + p.add_argument('--source-commit',required=True) + p.add_argument('--tricu',type=Path,required=True) + p.add_argument('--zig-library',type=Path,required=True) + p.add_argument('--probe',action='store_true',help='native_math on both hosts, 1 GiB/120s; no full generations') + p.add_argument('--verify-only',action='store_true') + args=p.parse_args() + directory=args.directory.resolve() + runtime,library=args.tricu.resolve(),args.zig_library.resolve() + source=directory/'source' + locks=protect_generations([directory]) + if args.verify_only: + evidence=verify_cross(*(directory/label/'generation' for label in ('H1','H2','H3','Z3')), + runtime,library,source) + print(json.dumps(evidence,indent=2)) + return + directory.mkdir(parents=True,exist_ok=False) + commit=subprocess.check_output(['git','rev-parse',args.source_commit+'^{commit}'],cwd=ROOT,text=True).strip() + archive=directory/'source.tar' + with archive.open('wb') as out: + subprocess.run(['git','archive',commit],cwd=ROOT,stdout=out,check=True) + source.mkdir() + with tarfile.open(archive) as tar: + # Historical tracked build-output symlink, excluded by recovery archives + # too. Keep all other extraction safety checks; never follow it. + tar.extractall(source,members=(m for m in tar if m.name!='ext/zig/result'),filter='data') + # The sandbox also mounts this snapshot read-only; never use live sources. + map_text,entries,required=source_map(source/'bootstrap/compiler-source-map.v1',source,'source_map') + harness=directory/'harness/test' + harness.mkdir(parents=True) + for path in (ROOT/'test').glob('*.py'): shutil.copyfile(path,harness/path.name) + tooling={path.name:sha(path) for path in harness.glob('*.py')} + memory,seconds=(1024,120) if args.probe else (32768,3600) + definition=dict(schema='tricu.cross-host-experiment.v1',source_commit=commit, + source_archive_sha256=sha(archive), excluded_source_paths=['ext/zig/result'], + runtime=dict(path=str(runtime),sha256=sha(runtime)), + zig_library=dict(path=str(library),sha256=sha(library)), + python=dict(path=sys.executable,sha256=sha(Path(sys.executable))), + harness_sha256=sha(Path(__file__)), tooling_sha256=tooling, + limits=dict(memory_mib=memory,wall_seconds=seconds,capabilities=8), + expected='H2 = H3 = Z3: complete objects, receipts, checkpoints and three bundles', + comparison_exclusions=['runtime/transport provenance','timings','logs','filesystem locations','invocation records'], + **snapshot(map_text,entries,source)) + save(directory/'experiment.json',definition) + shutil.copyfile(__file__,directory/'experiment-harness.py') + report=dict(complete=False,scope='probe' if args.probe else 'full',stages=[]) + save(directory/'report.json',report) + def job(label,kit,host): + # Verify the kit before crossing the sandbox boundary; the shared runner + # independently captures/authenticates it again inside. + load_kit(kit,'stable') + stage=directory/label + stage.mkdir() + command=sandbox_command(source,kit,stage,runtime,library,host, + 'native_math' if args.probe else 'source_map',memory,seconds) + save(stage/'sandbox-command.json',command) + try: + reply,_=run_guarded(command,memory_mib=memory,seconds=seconds+30) + except Exception as error: + (stage/'supervisor.log').write_text(str(getattr(error,'output',''))+'\n'+str(error)) + report['failure']=label + save(directory/'report.json',report) + raise + (stage/'supervisor.log').write_text(reply) + report['stages'].append(label) + save(directory/'report.json',report) + print('Completed isolated '+label,flush=True) + return stage/'generation' + def kit_from(run,label): + kit=directory/(label+'-kit') + kit.mkdir() + artifacts={} + for role,name,export in [('compiler','rebuilt-compiler.arboricx','sourceMapBuildStable'), + ('adapter','io-adapter.arboricx','main')]: + shutil.copyfile(run/name,kit/name) + artifacts[role]=dict(file=name,export=export,sha256=sha(kit/name)) + save(kit/'bootstrap-kit.json',dict(schema='tricu.bootstrap-kit.v1',backend='stable', + parent_generation=str(run),artifacts=artifacts)) + return kit + first=job('H1',source/'bootstrap/rebuild','haskell') + if args.probe: + zig=job('Z3',source/'bootstrap/rebuild','zig') + if inventory(first)!=inventory(zig): raise ValueError('probe complete object inventories differ') + else: + second=job('H2',kit_from(first,'H1'),'haskell') + save(directory/'accepted-pair.json',verify(first,second,runtime,source)) + kit=kit_from(second,'H2') + control=job('H3',kit,'haskell') + zig=job('Z3',kit,'zig') + save(directory/'accepted-cross-host.json',verify_cross(first,second,control,zig,runtime,library,source)) + if tooling != {name:sha(ROOT/'test'/name) for name in tooling}: + raise ValueError('experiment tooling changed during execution') + report['complete']=True + save(directory/'report.json',report) + print('Experiment verified; full-run promotion/smokes/retention remain separate: '+str(directory)) + + +if __name__=='__main__': + main()