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:
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()