#!/usr/bin/env python3 """Paired cache-off/cache-on injected acceptance on the reserved normal board. Stage beside the three published card session/acceptance/budget tools. Raw captures and process logs remain private; fixed public reports retain failures. """ import argparse import datetime import fcntl import hashlib import importlib.util import json import os from pathlib import Path import platform import re import shutil import subprocess import sys import time SYSTEM='/nix/store/gnr36q39hmy4pq7ipwac1r1rpfbyqxd4-nixos-system-nixos-26.11.20260919.20b1ddd' PACKAGE='/nix/store/hxilq8jdlb0b8mj8jzmvhwsw9mwnr69y-k230-card-shell' SWAY='/nix/store/6an3zjnjv5jbyxzh80cm0cnaajq6spx9-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway' CONFIG='/nix/store/7amq3c8lnlvg82la2g4zxhiik716fgq9-k230-sway.conf' CLIENT='/nix/store/bxiz9zkb33m4v97gkgzx91aswfm7cf9f-card-composition-probe-client-riscv64-unknown-linux-gnu-0.1/bin/card-composition-probe-client' def write(path, value): temp=path.with_suffix('.new');temp.write_text(json.dumps(value,indent=2)+'\n');temp.replace(path) def load(name,path): spec=importlib.util.spec_from_file_location(name,path) module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module);return module def runtime_directory(output, index): namespace=hashlib.sha256(str(output).encode()).hexdigest()[:16] return Path('/run/k230-card-shell-cache'+namespace+str(index)) def main(): p=argparse.ArgumentParser(description=__doc__) p.add_argument('--board',action='store_true',required=True) p.add_argument('--revision',required=True) p.add_argument('--output',type=Path,required=True) args=p.parse_args() if not re.fullmatch('[a-f0-9]{40}',args.revision):p.error('full source revision required') if not re.fullmatch('/var/lib/k230/card-scaled-cache-[a-z0-9-]+',str(args.output)) or args.output.exists():p.error('new protected output required') if os.geteuid()!=0 or not platform.machine().startswith('riscv') or os.path.realpath('/run/current-system')!=SYSTEM:raise RuntimeError('requires reserved normal RISC-V board') if Path('/sys/firmware/devicetree/base/model').read_bytes().rstrip(b'\0')!=b'LILYGO T-Display-K230':raise RuntimeError('wrong model') here=Path(__file__).resolve().parent session_module=load('session',here/'card-shell-board-session.py') analyzer=load('budget',here/'card-shell-benchmark.py') for artifact in (PACKAGE+'/bin/card-shell',SWAY,CONFIG,CLIENT): if not Path(artifact).is_file():raise RuntimeError('declared store artifact is not installed: '+artifact) os.umask(0o077) with open('/run/k230-card-scaled-cache.lock','a') as lock: fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB) args.output.mkdir(mode=0o700) boot=Path('/proc/sys/kernel/random/boot_id').read_text().strip() result={'status':'INCOMPLETE','revision':args.revision,'boot_id':boot,'system':SYSTEM,'package':PACKAGE,'sway':SWAY,'client':CLIENT,'config':CONFIG,'evidence_class':'board-injected','workload':'same 13 acceptance cases and 24 drags per card count in each arm','runs':[], 'producer_sha256':{name:hashlib.sha256((here/name).read_bytes()).hexdigest() for name in ('run-board.py','card-shell-board-session.py','card-shell-acceptance.py','card-shell-benchmark.py')}} try: for index,cache_policy in enumerate(('off','on'),1): write(args.output/'progress.json',{'state':'running','run':index,'scaled_cache':cache_policy,'finished_runs':len(result['runs'])}) target=args.output/('run-'+str(index)+'-'+cache_policy);target.mkdir();public=target/'public';public.mkdir() runtime=runtime_directory(args.output,index) if not session_module.RUNTIME_PATH.fullmatch(str(runtime)): raise RuntimeError('runtime must satisfy the session guard before arming') if runtime.exists():raise RuntimeError('preserve previous runtime directory') runtime.mkdir(mode=0o711);runtime.chmod(0o711) system=session_module.System();session=session_module.Session(runtime,system) observation={'run':index,'scaled_cache':cache_policy,'started_at_utc':datetime.datetime.now(datetime.timezone.utc).isoformat()} token=None try: token=session.arm({'package':PACKAGE,'normal_config':CONFIG,'client':CLIENT,'source_revision':args.revision,'duration':540,'source_device':'/dev/input/event0','pixman_policy':'auto','scaled_cache':cache_policy}) session.start();session.input_device() deadline=time.monotonic()+30 while time.monotonic()