234 lines
14 KiB
Python
234 lines
14 KiB
Python
"""Freeze existing #46 package inputs; no compilation, launch, or acceptance claim.
|
|
|
|
Run only after parent review. Failure preserves its partial fresh directory for
|
|
inspection; never delete/reuse that directory automatically.
|
|
"""
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
|
|
ROOT = Path('C:/ar-save-01a0d717')
|
|
QA = ROOT / '.local/package46'
|
|
CHECKPOINT = 'ef28f1784efe6717b0ca72c0eb3c97bc30ebfe47'
|
|
DESKTOP_SOURCE = '0e83157e09d52e6152df91208e41693b62da2d46'
|
|
SERVER_SOURCE = '715036e132677dd2bc2fb37ed316d93dc9987aec'
|
|
VERSION = '0.2.0-preview.save-diagnostics.20261001'
|
|
TARGET = 'x86_64-pc-windows-gnullvm'
|
|
COMPILER = 'rustc 1.98.1 (48a229cea 2026-09-01)'
|
|
DONOR = Path('C:/ar-compact-01a0d717/.local/package39/extracted/'
|
|
'ArcadiaStudio-0.2.0-preview.compact-help.20261001-x86_64-pc-windows-gnullvm')
|
|
NATIVE = ROOT / '.local/native46/bin'
|
|
PACKET = ROOT / 'harness/experiments/save-as-diagnostics-20261001'
|
|
RECOVERY = ROOT / '.local/package46-donor-recovery'
|
|
TOOLCHAIN = Path('C:/Users/User/Documents/Git/ARCADIA-Studio/.local/toolchain/'
|
|
'llvm-mingw-20260826-ucrt-x86_64')
|
|
EXPECTED = {
|
|
'arcadia-studio.exe': 'e9cce9355525ecdc59bfae12608b1e0272167acef5a86b25368ef547e08886ff',
|
|
'arcadia-server.exe': '6cebacc117e46fdab023c6402a5cebd6929fb55993ae8ff6b89cd1c6c1170237',
|
|
'libunwind.dll': 'f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4',
|
|
}
|
|
DESKTOP_PATHS = ['crates', 'Cargo.toml', 'Cargo.lock', 'examples', 'docs', 'scripts', 'LICENSE', 'README.md']
|
|
SERVER_PATHS = ['crates/oa-core', 'crates/oa-server', 'crates/vendor', 'Cargo.toml', 'Cargo.lock']
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise RuntimeError(message)
|
|
|
|
|
|
def git(*args):
|
|
return subprocess.check_output(['git', *args], cwd=ROOT, text=True).strip()
|
|
|
|
|
|
def receipt(path):
|
|
return {'bytes': path.stat().st_size, 'sha256': hashlib.sha256(path.read_bytes()).hexdigest()}
|
|
|
|
|
|
def read_json(path):
|
|
return json.loads(path.read_text(encoding='utf-8-sig'))
|
|
|
|
|
|
def write_json(path, value):
|
|
with path.open('x', encoding='utf-8', newline='\n') as stream:
|
|
json.dump(value, stream, indent=2)
|
|
stream.write('\n')
|
|
|
|
|
|
def main():
|
|
require(not QA.exists(), 'Fresh package46 directory required; do not overwrite a prior attempt')
|
|
require(not git('status', '--porcelain'), 'Source changes must be reviewed and committed first')
|
|
require(git('rev-parse', 'HEAD') == CHECKPOINT, 'Checkpoint changed; parent must review and repin helper')
|
|
comparisons = []
|
|
for origin, paths in [(DESKTOP_SOURCE, DESKTOP_PATHS), (SERVER_SOURCE, SERVER_PATHS)]:
|
|
command = ['diff', '--exit-code', origin, CHECKPOINT, '--', *paths]
|
|
subprocess.run(['git', *command], cwd=ROOT, check=True)
|
|
comparisons.append({'command': ['git', *command], 'exit_code': 0, 'unchanged': True})
|
|
|
|
sources = {name: (DONOR if name == 'arcadia-server.exe' else NATIVE) / name for name in EXPECTED}
|
|
for name, source in sources.items():
|
|
require(receipt(source)['sha256'] == EXPECTED[name], f'Frozen input mismatch: {name}')
|
|
require(sources['arcadia-studio.exe'].stat().st_size == 119452672, 'Desktop size mismatch')
|
|
require(sources['libunwind.dll'].stat().st_size == 190976, 'Runtime size mismatch')
|
|
require(receipt(TOOLCHAIN / 'x86_64-w64-mingw32/bin/libunwind.dll')['sha256'] == EXPECTED['libunwind.dll'],
|
|
'Installed LLVM-MinGW runtime mismatch')
|
|
|
|
donor_manifest = DONOR / 'package.json'
|
|
require(receipt(donor_manifest)['sha256'] == '36bcff8a760c08674f3fc017bb5affc8a5f4f39174ac9d5a29298f2423155832',
|
|
'Donor package manifest changed')
|
|
manifest = read_json(donor_manifest)
|
|
donor_evidence = manifest['build_evidence']['evidence']
|
|
server_origin = donor_evidence['binary_origins']['arcadia-server.exe']
|
|
original = server_origin['original_build_evidence']
|
|
require(server_origin['source'] == original['source_identity'] == SERVER_SOURCE, 'Server origin mismatch')
|
|
require(original['binary_sha256']['arcadia-server.exe'] == EXPECTED['arcadia-server.exe'], 'Original server receipt mismatch')
|
|
require(manifest['input_sha256']['arcadia-server.exe'] == EXPECTED['arcadia-server.exe'], 'Donor server mismatch')
|
|
require(donor_evidence['binary_sha256']['arcadia-server.exe'] == EXPECTED['arcadia-server.exe'], 'Donor evidence mismatch')
|
|
require(donor_evidence['target'] == original['target'] == TARGET, 'Donor target mismatch')
|
|
require(donor_evidence['compiler'] == original['compiler'] == COMPILER, 'Donor compiler mismatch')
|
|
require(read_json(PACKET / 'donor/build-evidence.json') == donor_evidence, 'Recorded donor evidence mismatch')
|
|
baseline = read_json(PACKET / 'native/baseline.json')
|
|
require(baseline['source'] == DESKTOP_SOURCE, 'Desktop native source mismatch')
|
|
require(baseline['binary'] == receipt(sources['arcadia-studio.exe']), 'Desktop native identity mismatch')
|
|
require(baseline['runtime'] == receipt(sources['libunwind.dll']), 'Runtime native identity mismatch')
|
|
commands = read_json(PACKET / 'commands.json')
|
|
require(commands['source'] == DESKTOP_SOURCE and commands['target'] == TARGET, 'Desktop build source/target mismatch')
|
|
recovery_receipt = RECOVERY / 'receipt.json'
|
|
require(receipt(recovery_receipt)['sha256'] == '5d630ff25489262188b8e35f1a2f0208bffd90eed6051d8845250509e9fac8ef',
|
|
'Pinned donor recovery receipt mismatch')
|
|
recovered = read_json(recovery_receipt)
|
|
require(recovered['publication_commit'] == '206f3b969482241e01e2dfc6be955caf31747b89'
|
|
and recovered['source_commit'] == SERVER_SOURCE and recovered['verified_members'] == 462,
|
|
'Recovered publication/source mismatch')
|
|
for name, item in recovered['selected_files'].items():
|
|
path = RECOVERY / 'selected' / name
|
|
require(path.resolve().is_relative_to((RECOVERY / 'selected').resolve()), 'Recovery path escapes root')
|
|
require(receipt(path) == {k: item[k] for k in ['bytes', 'sha256']}, 'Recovered file mismatch: ' + name)
|
|
for key, item in recovered['recovered_original_receipts'].items():
|
|
require(item['original_assertion'] == original[key], 'Recovered original assertion mismatch: ' + key)
|
|
server_result = read_json(RECOVERY / 'selected/final-04/arcadia-server-build/result.json')
|
|
require(server_result['source_commit'] == SERVER_SOURCE and server_result['target'] == TARGET
|
|
and server_result['profile'] == 'dev' and server_result['exit_code'] == 0
|
|
and server_result['inputs_unchanged'], 'Recovered server build result mismatch')
|
|
relocation = read_json(RECOVERY / 'selected/final-04/artifact-relocation.json')
|
|
server_artifacts = [a for a in relocation['artifacts'] if a.get('cargo_artifact')
|
|
and a['cargo_artifact']['target']['name'] == 'arcadia-server']
|
|
require(len(server_artifacts) == 1 and server_artifacts[0]['preserved']['sha256'] == EXPECTED['arcadia-server.exe'],
|
|
'Recovered server Cargo artifact mismatch')
|
|
|
|
# This is a byte snapshot, not an assertion of a reproducible compiler build.
|
|
tracked = git('ls-files', '--', *DESKTOP_PATHS).splitlines()
|
|
tree = subprocess.check_output(['git', 'ls-tree', '-rz', CHECKPOINT, '--', *DESKTOP_PATHS], cwd=ROOT)
|
|
blobs = {}
|
|
for entry in tree.split(b'\0'):
|
|
if not entry:
|
|
continue
|
|
metadata, name = entry.split(b'\t', 1)
|
|
mode, kind, blob = metadata.decode('ascii').split()
|
|
require(kind == 'blob' and mode in ['100644', '100755'], 'Unexpected tracked input type')
|
|
blobs[name.decode('utf-8')] = blob
|
|
require(set(blobs) == set(tracked), 'Frozen Git input set mismatch')
|
|
inputs = {name: {'git_blob': blobs[name], **receipt(ROOT / name)} for name in tracked}
|
|
QA.mkdir(exist_ok=False)
|
|
for directory in ['bin', 'provenance', 'dist', 'extracted', 'qa', 'release-assets']:
|
|
(QA / directory).mkdir()
|
|
copies = {}
|
|
|
|
def copy(source, relative):
|
|
dest = QA / relative
|
|
require(not dest.exists(), f'Refusing overwrite: {relative}')
|
|
shutil.copyfile(source, dest)
|
|
identity = receipt(source)
|
|
require(receipt(dest) == identity, f'Copy mismatch: {relative}')
|
|
copies[relative] = {'original_path': str(source), **identity}
|
|
|
|
for name, source in sources.items():
|
|
copy(source, 'bin/' + name)
|
|
copy(donor_manifest, 'provenance/donor-package.json')
|
|
copy(recovery_receipt, 'provenance/recovered-donor-receipt.json')
|
|
copy(RECOVERY / 'manifest.json', 'provenance/recovered-donor-manifest.json')
|
|
recovered_copies = {}
|
|
for name in recovered['selected_files']:
|
|
relative = 'provenance/recovered-' + name.replace('/', '--')
|
|
copy(RECOVERY / 'selected' / name, relative)
|
|
recovered_copies[name] = relative
|
|
for relative in ['donor/build-evidence.json', 'native/baseline.json', 'commands.json',
|
|
'gates/save-diagnostics-build.log', 'gates/save-diagnostics-clippy.log',
|
|
'gates/save-diagnostics-corrected-tests.log']:
|
|
copy(PACKET / relative, 'provenance/' + relative.replace('/', '--'))
|
|
|
|
# Keep original assertions untouched, including their historical paths and
|
|
# hashes. Missing historical files are recorded, never silently re-created.
|
|
historical = {}
|
|
for key in ['mandatory_gate', 'source_manifest', 'environment', 'artifact_relocation']:
|
|
assertion = original[key]
|
|
path = Path(assertion['path'])
|
|
item = {'original_assertion': assertion, 'original_path_available': path.is_file()}
|
|
if path.is_file():
|
|
require(receipt(path) == {k: assertion[k] for k in ['bytes', 'sha256']}, f'Original receipt mismatch: {key}')
|
|
copy(path, 'provenance/original-' + key + '.json')
|
|
item['verified_copy'] = 'provenance/original-' + key + '.json'
|
|
else:
|
|
item['historical_path_note'] = 'Original path unavailable; exact bytes recovered from immutable published archive.'
|
|
portable = recovered['recovered_original_receipts'][key]
|
|
item['recovered_verified_copy'] = recovered_copies[portable['member']]
|
|
item['recovered_sha256'] = portable['sha256']
|
|
item['publication_commit'] = recovered['publication_commit']
|
|
historical[key] = item
|
|
|
|
evidence = {
|
|
'source_identity': f'Desktop: {DESKTOP_SOURCE}; unchanged reused server: {SERVER_SOURCE}; package documentation: {CHECKPOINT}',
|
|
'profile': 'dev, unoptimized, debug assertions enabled',
|
|
'compiler': COMPILER,
|
|
'target': TARGET,
|
|
'binary_sha256': {name: EXPECTED[name] for name in ['arcadia-studio.exe', 'arcadia-server.exe']},
|
|
'binary_origins': {
|
|
'arcadia-studio.exe': {
|
|
'source': DESKTOP_SOURCE, 'reuse': True,
|
|
'build_command': 'cargo build --locked -p oa-desktop --bin arcadia-studio',
|
|
'build_jobs': 1, 'incremental': False,
|
|
'evidence': 'harness/experiments/save-as-diagnostics-20261001/commands.json',
|
|
'build_log': 'harness/experiments/save-as-diagnostics-20261001/gates/save-diagnostics-build.log',
|
|
'native_evidence': 'harness/save-as-diagnostics-20261001.md',
|
|
'native_freeze_receipt': 'harness/experiments/save-as-diagnostics-20261001/native/baseline.json',
|
|
'unchanged_input_check': comparisons[0],
|
|
},
|
|
'arcadia-server.exe': {
|
|
'source': SERVER_SOURCE, 'reuse': True,
|
|
'release': server_origin['release'],
|
|
'immediate_donor_version': manifest['version'],
|
|
'immediate_donor_manifest': copies['provenance/donor-package.json'],
|
|
'original_build_evidence': original,
|
|
'historical_receipt_availability': historical,
|
|
'recovered_publication': recovered,
|
|
'recovered_portable_copies': recovered_copies,
|
|
'original_toolchain_note': 'Historical server build receipt names LLVM-MinGW 20260908; current packaging runtime distribution is separately recorded. No rebuild or environment rewrite.',
|
|
'unchanged_input_check': comparisons[1],
|
|
},
|
|
},
|
|
'package_documentation_source': CHECKPOINT,
|
|
'planned_version': VERSION,
|
|
'runtime_sha256': EXPECTED['libunwind.dll'],
|
|
'runtime_origin': {'distribution': 'llvm-mingw-20260826-ucrt-x86_64', 'path': str(TOOLCHAIN),
|
|
'note': 'Packaging script must include and verify the full distribution license.'},
|
|
'input_snapshot': 'source-inputs.json',
|
|
'preserved_provenance': copies,
|
|
'acceptance_scope': 'Preparation of existing frozen binaries only; no compilation or extracted-package/native acceptance. Contribution gates and inherited donor gates remain separate; no summed/full-suite, peer-union, new-platform, or complete OA/SA acceptance claim.',
|
|
}
|
|
write_json(QA / 'source-inputs.json', {'source': CHECKPOINT, 'files': inputs, 'unchanged_input_comparisons': comparisons})
|
|
write_json(QA / 'build-evidence.json', evidence)
|
|
require(git('rev-parse', 'HEAD') == CHECKPOINT and not git('status', '--porcelain'), 'Source changed during preparation')
|
|
for name, expected in inputs.items():
|
|
require(receipt(ROOT / name) == {k: expected[k] for k in ['bytes', 'sha256']}, f'Input changed during preparation: {name}')
|
|
for name in EXPECTED:
|
|
require(receipt(QA / 'bin' / name)['sha256'] == EXPECTED[name], f'Frozen copy changed: {name}')
|
|
write_json(QA / 'preparation-receipt.json', {'source': CHECKPOINT, 'version': VERSION,
|
|
'binary_sha256': EXPECTED, 'build_evidence': receipt(QA / 'build-evidence.json'),
|
|
'source_inputs': receipt(QA / 'source-inputs.json'), 'scope': 'Input preparation only; package and runtime checks pending.'})
|
|
print(json.dumps({'source': CHECKPOINT, 'prepared': str(QA), 'binary_sha256': EXPECTED}, indent=2))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|