Files
ARCADIA-Studio/harness/experiments/canvas-repair-package-33e30c1-20260912/payload/r2/preflight.py
T

195 lines
11 KiB
Python

"""Read-only exact source/build preflight. No metadata, compiler or application run."""
from pathlib import Path, PurePosixPath
import argparse
import datetime
import hashlib
import json
import os
import re
import subprocess
import tomllib
ROOT = Path(__file__).resolve().parents[2]
WORK = ROOT / '.local/canvas-union-ytr83r-20260911'
GATES = ROOT / '.local/repair-union-windows-20260911'
SOURCE = '33e30c1cd7a6c99513a0ebd02ba68fccbad8f58d'
SERVER_SOURCE = '321897fd55af9e07afa2c6ebf1e50be49e86f7d9'
FREEZE = '0f21b9a852cf44699bf29fce0a35ec460744c85f5e26f0ea4920e8568211e312'
SERVER = 'aba87a5fb795deb995029a737c2ebb9d2b44c8d274edaa2295363b226d9af6be'
DLL = 'aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a'
TREES = {'crates/oa-core': 'a9c71e2c3fc9260d06f191c0d65b1e63e41edbec',
'crates/oa-server': '99dbbe3a17d45d7030fc14a31fad2c07332d8d86'}
PACKAGING = {
'scripts/package-windows.ps1': '82d690683a76f1e9eb6b35eaecf97d59b0f781f9a71ae9aec1434b4a84298142',
'scripts/package-common.ps1': '22d60b19df0f6d5ed37b743f5175678f72f6a9b24658f8bcd90a20bb9d98a931',
'scripts/package-artifacts.py': '655711b051a3cd2d478d9b15ba27be9b63c5b056be80ade8da4dabc6bb46a91c',
'scripts/windows-notices.ps1': 'ac70b61937076416c50b104fd9f7e127880748764fa59c21105312cb291369ed',
}
def require(condition, message):
if not condition:
raise RuntimeError(message)
def digest(data):
return hashlib.sha256(data).hexdigest()
def canonical_gate_status(status):
"""Normalize only UTC timestamp spelling, preserving all seven 100 ns digits."""
normalized = dict(status)
for field in ('started_utc', 'completed_utc'):
value = status.get(field)
require(isinstance(value, str), f'Missing/string-invalid gate timestamp: {field}')
match = re.fullmatch(r'([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})(?:\.([0-9]{1,7}))?Z', value)
require(match is not None, f'Gate timestamp requires strict UTC Z format: {field}')
# Validate calendar/time without parsing (or truncating) the fractional digits.
datetime.datetime.strptime(match.group(1), '%Y-%m-%dT%H:%M:%S')
normalized[field] = match.group(1) + '.' + (match.group(2) or '').ljust(7, '0') + 'Z'
return normalized
def ordinary(path):
require(path.is_file(), f'Missing file: {path}')
for current in [path, *path.parents]:
require(not current.is_symlink() and not (getattr(current.stat(), 'st_file_attributes', 0) & 0x400),
f'Reparse input is unsupported: {current}')
return path
def read_json(path):
return json.loads(ordinary(path).read_text(encoding='utf-8-sig'))
def file_record(path):
data = ordinary(path).read_bytes()
return {'path': str(path), 'bytes': len(data), 'sha256': digest(data)}
def git(*args, data=None):
options = {'creationflags': subprocess.CREATE_NO_WINDOW} if os.name == 'nt' else {}
return subprocess.run(['git', '--no-optional-locks', '-C', str(WORK), *args],
input=data, capture_output=True, check=True, **options).stdout
def verify_source():
for path, expected in PACKAGING.items():
require(file_record(WORK / path)['sha256'] == expected, f'Reviewed packaging script changed: {path}')
manifest_path = GATES / 'inputs.json'
require(digest(ordinary(manifest_path).read_bytes()) == FREEZE, 'Unexpected input manifest')
manifest = read_json(manifest_path)
files = manifest['files']
require(len(files) == 530 and sum(f['bytes'] for f in files) == 9727025,
'Unexpected frozen input count/size')
names = [f['path'] for f in files]
require(names == sorted(set(names)), 'Duplicate or unordered frozen input paths')
paths = git('ls-files', '-co', '--exclude-standard', '--', 'crates', 'assets', 'build',
'.cargo', 'examples', 'docs', 'harness/fixtures', 'Cargo.toml', 'Cargo.lock',
'rust-toolchain', 'rust-toolchain.toml').decode().splitlines()
require(sorted(set(paths)) == names, 'Current input inventory differs from freeze')
for item in files:
path = PurePosixPath(item['path'])
require(not path.is_absolute() and '..' not in path.parts and '\\' not in item['path']
and ':' not in item['path'], 'Invalid manifest path')
actual = file_record(WORK.joinpath(*path.parts))
require(actual['bytes'] == item['bytes'] and actual['sha256'] == item['sha256'],
f'Working source differs from freeze: {path}')
for ref in (SOURCE, 'HEAD'):
response = git('cat-file', '--batch', data=''.join(f'{ref}:{n}\n' for n in names).encode())
offset = 0
for item in files:
end = response.index(b'\n', offset)
header = response[offset:end].split()
require(len(header) == 3 and header[1] == b'blob', f'Missing {ref}:{item["path"]}')
size = int(header[2])
data = response[end + 1:end + 1 + size]
require(size == item['bytes'] and digest(data) == item['sha256'],
f'Raw committed source differs: {ref}:{item["path"]}')
offset = end + size + 2
require(offset == len(response), 'Unexpected Git batch output')
for path, expected in TREES.items():
for ref in (SOURCE, SERVER_SOURCE):
require(git('rev-parse', f'{ref}:{path}').decode().strip() == expected,
f'Server component tree mismatch: {ref}:{path}')
require(git('show', f'{SOURCE}:Cargo.toml') == git('show', f'{SERVER_SOURCE}:Cargo.toml'),
'Workspace manifest changed since server build')
old_lock = tomllib.loads(git('show', f'{SERVER_SOURCE}:Cargo.lock').decode())
new_lock = tomllib.loads(git('show', f'{SOURCE}:Cargo.lock').decode())
old_desktop = [p for p in old_lock['package'] if p['name'] == 'oa-desktop']
new_desktop = [p for p in new_lock['package'] if p['name'] == 'oa-desktop']
require(len(old_desktop) == len(new_desktop) == 1, 'Ambiguous desktop lock record')
require('unicode-segmentation' not in old_desktop[0]['dependencies'], 'Unexpected old lock')
old_desktop[0]['dependencies'].append('unicode-segmentation')
old_desktop[0]['dependencies'].sort()
require(old_lock == new_lock, 'Cargo.lock differs beyond desktop-only segmentation edge')
server_manifest = read_json(ROOT / '.local/server-windows-321897f/manifest.json')
require(server_manifest['source_commit'] == SERVER_SOURCE
and server_manifest['core_tree'] == TREES['crates/oa-core']
and server_manifest['server_tree'] == TREES['crates/oa-server']
and server_manifest['profile'] == 'debug', 'Server provenance mismatch')
server_file = file_record(ROOT / '.local/server-windows-321897f/arcadia-server.exe')
require(server_file['sha256'] == SERVER and server_file['bytes'] == 39621120,
'Immutable server changed')
return {'source_commit': SOURCE, 'head': git('rev-parse', 'HEAD').decode().strip(),
'manifest_sha256': FREEZE, 'files': len(files), 'bytes': 9727025,
'server_manifest': server_manifest, 'server_file': server_file,
'server_reuse': 'Identical core/server trees and workspace manifest; sole lock delta is oa-desktop unicode-segmentation edge.'}
def verify_build(result):
statuses = {}
for name in ('focused', 'desktop-tests', 'desktop-clippy', 'studio-build'):
path = GATES / f'{name}-status.json'
status = read_json(path)
require(status.get('exit') == 0 and status.get('completed_utc')
and status.get('name') == f'repair-union-windows-20260911/{name}',
f'Missing, failed or mismatched terminal gate: {name}')
ordinary(Path(status['log']))
statuses[name] = status
manifest = read_json(GATES / 'binary-manifest.json')
require(manifest['source_identity'].split(';')[0] == SOURCE
and manifest['input_manifest_sha256'].lower() == FREEZE
and manifest['verified_inputs'] == 530 and manifest['verified_input_bytes'] == 9727025,
'Binary manifest is not the matching freeze')
require({name: canonical_gate_status(status) for name, status in manifest['gate_statuses'].items()}
== {name: canonical_gate_status(status) for name, status in statuses.items()},
'Preserved gate records differ from terminal statuses (UTC precision canonicalized)')
verified = read_json(GATES / 'verify-after-build.json')
require(verified.get('pass') is True and verified['mode'] == 'verify-head'
and verified['files'] == 530 and verified['bytes'] == 9727025
and verified['manifest_sha256'].lower() == FREEZE, 'Missing matching post-build verification')
binaries = {}
for name in ('arcadia-studio.exe', 'libunwind.dll'):
actual = file_record(GATES / 'bin' / name)
records = [r for r in manifest['binaries'] if Path(r['copy']).name == name]
require(len(records) == 1, f'Binary manifest missing/duplicates {name}')
rec = records[0]
require(Path(rec['copy']).resolve() == (GATES / 'bin' / name).resolve()
and rec['bytes'] == actual['bytes'], f'Wrong preserved binary: {name}')
require(all(rec[k].lower() == actual['sha256'] for k in
('source_sha256', 'copy_sha256', 'source_after_sha256')),
f'Preserved binary hash mismatch: {name}')
binaries[name] = actual
require(binaries['libunwind.dll']['sha256'] == DLL, 'Preserved runtime differs')
runtime = file_record(ROOT / '.local/toolchain/llvm-mingw-20260908-ucrt-x86_64/x86_64-w64-mingw32/bin/libunwind.dll')
require(runtime['sha256'] == DLL, 'Packaging runtime differs')
environment = read_json(GATES / 'environment.json')
require(environment['rustup_toolchain'] == 'stable-x86_64-pc-windows-gnullvm'
and environment['rustc_version'].startswith('rustc 1.98.1 '), 'Unexpected build compiler')
result.update(gate_statuses=statuses, binary_manifest=manifest, binaries=binaries,
environment=environment, packaging_runtime=runtime)
return result
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source-only', action='store_true', help='Verify existing source/server only; never sufficient to package.')
args = parser.parse_args()
# Refuse absent/failed gates before paying for the complete source inventory.
result = {} if args.source_only else verify_build({})
result.update(verify_source())
result.update(pass_preflight=True, scope='source-only' if args.source_only else 'source-and-build',
verified_utc=datetime.datetime.now(datetime.timezone.utc).isoformat())
print(json.dumps(result, indent=2))