"""Read back the produced ZIP/stage/source without launching either executable.""" from datetime import datetime, timezone import hashlib import json from pathlib import Path import stat import sys import zipfile from prepare import BASE, SOURCE, VERSION, TARGET, COMMIT, SERVER_SHA, DLL_SHA, ordinary, sha, load, record, git, relative, historical_inputs, HISTORY def main(): assert not (BASE / 'package-readback.json').exists() preparation = load(BASE / 'preparation.json') terminal = load(BASE / 'package2-result.json') assert terminal['success'] is True and terminal['metadata_calls'] == 1 assert preparation['base_commit'] == COMMIT and preparation['version'] == VERSION and preparation['target'] == TARGET name = f'ArcadiaStudio-{VERSION}-{TARGET}' archive_path = BASE / 'packages' / (name + '.zip') stage = BASE / 'packages' / name assert Path(terminal['archive']).resolve() == archive_path.resolve() assert terminal['archive_sha256'] == sha(archive_path) and terminal['archive_bytes'] == archive_path.stat().st_size actual = {} with zipfile.ZipFile(archive_path) as archive: entries = archive.infolist() assert entries and len({e.filename.casefold() for e in entries}) == len(entries) for entry in entries: assert entry.filename.startswith(name + '/') and not entry.is_dir() path = relative(entry.filename[len(name) + 1:]) assert not (entry.flag_bits & 1) and not stat.S_ISLNK(entry.external_attr >> 16) assert entry.date_time == (2000, 1, 1, 0, 0, 0) with archive.open(entry) as stream: checksum = hashlib.file_digest(stream, 'sha256').hexdigest() assert sha(stage / path) == checksum and (stage / path).stat().st_size == entry.file_size actual[path] = {'sha256': checksum, 'bytes': entry.file_size} files = {ordinary(p).relative_to(stage).as_posix() for p in stage.rglob('*') if p.is_file()} assert set(actual) == files sums = {} for line in (stage / 'SHA256SUMS.txt').read_text(encoding='utf-8').splitlines(): checksum, path = line.split(' ', 1) assert relative(path) not in sums sums[path] = checksum assert set(sums) == set(actual) - {'SHA256SUMS.txt'} assert all(actual[p]['sha256'] == h for p, h in sums.items()) companion = (archive_path.with_suffix('.zip.sha256')).read_text(encoding='utf-8').strip().split() assert companion == [terminal['archive_sha256'], archive_path.name] for item in preparation['artifacts']: p = Path(item['destination']).name assert actual[p]['sha256'] == sha(BASE / item['destination']) == item['sha256'] assert actual['arcadia-server.exe']['sha256'] == SERVER_SHA and actual['libunwind.dll']['sha256'] == DLL_SHA assert git(SOURCE, 'rev-parse', 'HEAD').decode().strip() == preparation['reconstruction_commit'] assert not git(SOURCE, 'status', '--porcelain').strip() for mapping in (preparation['gui_inputs'], preparation['shared_non_cargo_inputs']): for path, checksum in mapping.items(): assert sha(SOURCE / path) == checksum # This comparison deliberately uses the old server tar, never GUI source. assert historical_inputs() == preparation['historical_server_inputs'] for item in load(BASE / 'evidence-manifest.json'): assert sha(BASE / item['destination']) == item['sha256'] and (BASE / item['destination']).stat().st_size == item['bytes'] package = load(stage / 'package.json') assert package['version'] == VERSION and package['target'] == TARGET assert package['build_evidence']['evidence'] == load(BASE / 'build-evidence.json') for path, checksum in package['input_sha256'].items(): assert actual[path]['sha256'] == checksum notices = load(stage / 'dependency-notices.json') previous = load(HISTORY / 'prior-dependency-notices.json') def registry_key(item): return item['name'], item['version'], item['source'], item['registry_checksum'] collected = {registry_key(x): x for x in notices['packages'] if x['source']} covered = 0 for old in previous['packages']: if not old['source']: continue current = collected[registry_key(old)] assert old['license'] == current['license'] assert set(old['features']).issubset(current['features']) # Exact recorded notice files must remain in the actual ZIP. Current # metadata does not substitute for the historical server's provenance. new_notices = {(n['path'], n['sha256']) for n in current['notices']} for item in old['notices']: assert (item['path'], item['sha256']) in new_notices assert actual[item['path']]['sha256'] == item['sha256'] assert old['embedded_fonts'] == current['embedded_fonts'] for font in old['embedded_fonts']: for item in font['notices']: assert actual[item['path']]['sha256'] == item['sha256'] covered += 1 assert covered == preparation['historical_registry_count'] docs = load(stage / 'documentation-check.json') result = {'atUtc': datetime.now(timezone.utc).isoformat(), 'status': 'PASS', 'archive_sha256': sha(archive_path), 'archive_bytes': archive_path.stat().st_size, 'file_count': len(actual), 'files': actual, 'manifest_entries': len(sums), 'source_commit': preparation['reconstruction_commit'], 'gui_inputs_verified': len(preparation['gui_inputs']), 'historical_server_inputs_verified': len(preparation['historical_server_inputs']), 'historical_server_comparison': preparation['historical_server_comparison'], 'historical_registry_packages_covered': covered, 'notice_roots': notices['root_packages'], 'notice_package_count': len(notices['packages']), 'documentation_check': docs, 'binary_imports': package['imports'], 'scope': 'Producer packaging readback only; no application launch or independent/native acceptance.'} record(BASE / 'package-readback.json', result) print(json.dumps({k: v for k, v in result.items() if k != 'files'}, ensure_ascii=False, indent=2)) if __name__ == '__main__': sys.stdout.reconfigure(encoding='utf-8') sys.stderr.reconfigure(encoding='utf-8') main()