#!/usr/bin/env python3 """Record this dedicated guest build without copying VM credentials or user data.""" import argparse import ctypes import hashlib import json from pathlib import Path import platform import re import subprocess import xml.etree.ElementTree as ET def sha(path): with path.open('rb') as handle: return hashlib.file_digest(handle, 'sha256').hexdigest() parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--ctest-name', default='ctest-font-final') parser.add_argument('--output-name', default='build-record.json') parser.add_argument('--expected-groups', type=int, default=22) parser.add_argument('--scope-note', default='Full 22-group baseline; later focused results are recorded separately.') args = parser.parse_args() if (not re.fullmatch(r'[a-z0-9][a-z0-9-]{0,63}', args.ctest_name) or not re.fullmatch(r'[a-z0-9][a-z0-9-]{0,63}\.json', args.output_name) or not 1 <= args.expected_groups <= 100): parser.error('Use bounded local evidence names and 1–100 expected groups') home = Path.home() source, build = home / 'docview-source', home / 'docview-build' validation = home / 'validation' output = validation / args.output_name if output.exists(): raise SystemExit('Keep previous evidence; output already exists') ctest = validation / args.ctest_name cases = ET.parse(ctest / 'tests.xml').findall('.//testcase') if len(cases) != args.expected_groups or any(x.find('failure') is not None or x.find('skipped') is not None for x in cases): raise SystemExit('Expected all selected CTest groups to pass') names = ['docview', 'docview-pdf-worker', 'docview-archive-worker', 'test_app', 'test_core', 'test_pdf', 'test_pdf_canvas', 'test_web_qml', 'test_translations'] files = [source / n for n in ('CMakeLists.txt', 'resources.qrc')] for folder in ('src', 'qml', 'cmake', 'resources', 'tools'): files += [p for p in (source / folder).rglob('*') if p.is_file() and not p.is_symlink() and '__pycache__' not in p.parts] files += [p for p in (source / 'tests').rglob('*') if p.is_file() and not p.is_symlink() and 'results' not in p.parts and '__pycache__' not in p.parts and p.suffix in ('.cpp', '.h', '.py', '.qml')] libc = ctypes.CDLL(None, use_errno=True) landlock_abi = libc.syscall(444, 0, 0, 1) record = { 'scope': 'Ubuntu 24.04 dedicated KVM guest; Xvfb/Sway software rendering, no physical GPU or IME acceptance', 'os': platform.freedesktop_os_release(), 'kernel': platform.release(), 'compiler': subprocess.check_output(['c++', '--version'], text=True).splitlines()[0], 'cmake': subprocess.check_output(['cmake', '--version'], text=True).splitlines()[0], 'qt': subprocess.check_output(['/opt/docview-qt/6.11.2/gcc_64/bin/qtpaths', '--query', 'QT_VERSION'], text=True).strip(), 'landlockABI': landlock_abi, 'apparmorRestrictUnprivilegedUserns': Path('/proc/sys/kernel/apparmor_restrict_unprivileged_userns').read_text().strip(), 'qtWebEngineApparmorProfileSha256': sha(Path('/etc/apparmor.d/docview-qtwebengine')), 'binaries': {n: sha(build / n) for n in names}, 'installedBinaries': {n: sha(home / 'docview-install/bin' / n) for n in names[:3]}, 'sourceSha256': {str(p.relative_to(source)): sha(p) for p in sorted(set(files))}, 'ctest': {'directory': args.ctest_name, 'groups': len(cases), 'failures': 0, 'junitSha256': sha(ctest / 'tests.xml'), 'logSha256': sha(ctest / 'LastTest.log')}, 'focusedTestScope': args.scope_note, 'pythonProvenanceScope': '30 passed and 5 explicit zstd skips with Python 3.12; Arch Python 3.14 executes all 35 cases.', 'productionBinariesChangedByTestFixes': False, } with output.open('x') as handle: handle.write(json.dumps(record, ensure_ascii=False, indent=2) + '\n') print(json.dumps({'ctestGroups': len(cases), 'sourceFiles': len(record['sourceSha256']), 'productionBinarySha256': record['binaries']['docview']}))