diff --git a/research/t74-low-alias-width-cap/README.md b/research/t74-low-alias-width-cap/README.md index 59d36ad132..a6818fde36 100644 --- a/research/t74-low-alias-width-cap/README.md +++ b/research/t74-low-alias-width-cap/README.md @@ -117,7 +117,7 @@ python3 -I -B research/t74-low-alias-width-cap/check_export.py ``` Expected: `PASS_T74_LOW_ALIAS_WIDTH_CAP 1687/1000000 patterns 1163 ... rejecting_controls 8` -and `PASS_T74_WIDTH_CAP_EXPORT controls 4`. +and `PASS_T74_WIDTH_CAP_EXPORT controls 8`. The package also registers its commands for the repository replay hook: @@ -152,6 +152,17 @@ stock/precision contract is established here. The cap concerns the primitive recurrence b; applying it to an outer κ requires that outer construction's inherited b-to-κ relationship. +The arithmetic loader executes the exact source bytes checked against +`INPUTS.json`, without consulting Python bytecode caches. `-B` alone suppresses +cache writes but still permits cache reads. Four export controls install +conflicting timestamp or unchecked-hash caches and replay in both normal and +optimized mode; every run must retain the original receipt and leave the export +unchanged. This repairs the same source/cache distinction identified in the +related [PR2766](https://github.com/CrocSwap/integer-mult-bounds/pull/2766). +[PR2553](https://github.com/CrocSwap/integer-mult-bounds/pull/2553) isolates the +registered runner's cache namespace; this fix also covers direct verifier calls. +The arithmetic source, inputs, dual and expected mathematical receipt are unchanged. + ## Open next questions 1. **Can an anticipatory alias keep its lender narrow?** The 32 low aliases diff --git a/research/t74-low-alias-width-cap/VERIFY.json b/research/t74-low-alias-width-cap/VERIFY.json index 521f2e533e..12066fd9bc 100644 --- a/research/t74-low-alias-width-cap/VERIFY.json +++ b/research/t74-low-alias-width-cap/VERIFY.json @@ -24,7 +24,7 @@ "{package}/check_export.py" ], "timeout_seconds": 60, - "checks": "Standalone read-only export, no-clobber, input and receipt tamper rejection." + "checks": "Standalone read-only export, no-clobber, input and receipt tamper rejection. Four source/bytecode binding controls: timestamp and unchecked-hash caches under normal and optimized Python." } ] } diff --git a/research/t74-low-alias-width-cap/check_export.py b/research/t74-low-alias-width-cap/check_export.py index 73a62e723a..5f0f79fad4 100644 --- a/research/t74-low-alias-width-cap/check_export.py +++ b/research/t74-low-alias-width-cap/check_export.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 """Verify source-only read-only replay, evidence binding and no-clobber.""" +import importlib.util +import py_compile +import struct import hashlib import json from pathlib import Path @@ -26,6 +29,40 @@ def replay(root, *args): cwd=root.parent, capture_output=True, text=True, timeout=30) +def check_bytecode_caches(root): + """Cached code must not replace the authenticated arithmetic source.""" + source = root / 'vendor/fine_moment.py' + original = source.read_bytes() + stamp = source.stat() + cache_dir = source.parent / '__pycache__' + # This root is a new source-only export: no existing cache may be removed. + need(not cache_dir.exists(), 'unexpected cache in fresh export') + with tempfile.TemporaryDirectory(prefix='t74-cache-control-') as tmp: + wrong_source = Path(tmp) / 'wrong.py' + wrong_source.write_text("raise RuntimeError('UNAUTHENTICATED_BYTECODE_EXECUTED')\n") + for mode in (py_compile.PycInvalidationMode.TIMESTAMP, + py_compile.PycInvalidationMode.UNCHECKED_HASH): + for optimized in (False, True): + cache = Path(importlib.util.cache_from_source( + str(source), optimization='1' if optimized else '')) + cache.parent.mkdir(exist_ok=True) + py_compile.compile(str(wrong_source), cfile=str(cache), dfile=str(source), + doraise=True, optimize=int(optimized), invalidation_mode=mode) + if mode == py_compile.PycInvalidationMode.TIMESTAMP: + data = bytearray(cache.read_bytes()) + data[8:16] = struct.pack('