Files
ARCADIA-Studio/harness/experiments/navigation-prerelease-20260911/initial-failed/prepare.py
T

317 lines
19 KiB
Python

"""Prepare isolated exact sources/binaries only after recorded GUI/full gates.
Do not execute without root's preparation authorization. Never invokes Cargo,
launches applications, publishes, or writes the main repository's index/refs.
"""
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path, PurePosixPath
import re
import shutil
import subprocess
import tarfile
import traceback
import sys
import tomllib
import zipfile
BASE = Path(__file__).resolve().parent
ROOT = BASE.parents[1]
SOURCE = BASE / 'source'
COMMIT = '33c6d83e9ba9a5b1310b1b627d7c40affcbcf15c'
BRANCH = 'codex/navigation-repair-prerelease-source-20260911'
VERSION = '0.2.0-preview.navigation.20260911'
TARGET = 'x86_64-pc-windows-gnullvm'
GATES = ROOT / '.local/text-repair-save-20260911'
HISTORY = ROOT / '.local/contextual-prerelease-20260911/evidence'
INPUT_SHA = '75a282fd3d4cb2dec6763046ea45d0caa015b998281fbdd6f222d8e3a175b13f'
ZIP_SHA = '213fdb39ea7c3601b4baac674c3ab3ecc863ac44db1b092d922d3e24f26887b7'
SERVER_SHA = 'efeeb89c0d2e0f93df4472cb3e9f48b2b722a37ae1043c90a5a56f2e845fe665'
DLL_SHA = 'f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4'
HISTORICAL = {
'server-original-gates.json': 'b7fdfc55ed4c964d276f20f1c57c81c1e924b8da315171089740ab8335ac9f0d',
'server-original-binary-hashes.json': '110a03e323133efb48d50ad065d41414599aa85e0ac420e7d622aa04df4d54f8',
'server-prior-input-comparisons.json': '3c78dea4e93dae186fd24ab826c6680178a2c7849c270a22deae2ff732f1857b',
'prior-dependency-notices.json': '98fab15c1a2bb3aa727c6d52d5c69435b15efa56b6668ed717e7a04cf86f9b52',
'server-source-snapshot.tar.gz': 'cbb371a93a562e1e641d7629fe351e016d63e66fc9112499b3fed6d5cd171f78',
}
SCRIPT_HASHES = {
'scripts/package-windows.ps1': '28997ff78565619ed9c9b185f66dc922808f1f0be17f522b430c6c415448663d',
'scripts/package-common.ps1': 'd859453a8230b4f305d822c8742b371a71e35df698109a5d55c948f6dfaf0640',
'scripts/windows-notices.ps1': 'a5328c05579e1b9ca09142ee99e87c12994d12764f7c69448e392883d718ef01',
}
COMMANDS = {
'desktop-tests': ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', '--', '--test-threads=1'],
'desktop-clippy': ['cargo', 'clippy', '--locked', '--offline', '-p', 'oa-desktop', '--all-targets', '--', '-D', 'warnings'],
'scoped-fmt': ['rustfmt', '--check', '--edition', '2024', '--config', 'skip_children=true', 'crates/oa-desktop/src/app.rs', 'crates/oa-desktop/src/document.rs', 'crates/oa-desktop/src/app/repair_save_state_tests.rs'],
'diff-check': ['git', 'diff', '--check'],
'gui-build': ['cargo', 'build', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', '--message-format=json-render-diagnostics'],
}
def now():
return datetime.now(timezone.utc).isoformat()
def ordinary(path):
path = Path(path).absolute()
for part in (path, *path.parents):
if part.exists():
assert not part.is_symlink() and not (getattr(part.lstat(), 'st_file_attributes', 0) & 0x400), str(part)
return path
def sha(path):
with ordinary(path).open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def load(path):
return json.loads(ordinary(path).read_text(encoding='utf-8-sig'))
def record(path, value):
ordinary(path)
with Path(path).open('x', encoding='utf-8', newline='\n') as stream:
json.dump(value, stream, ensure_ascii=False, indent=2)
stream.write('\n')
def git(cwd, *args):
return subprocess.run(['git', *args], cwd=cwd, check=True, capture_output=True).stdout
def relative(name):
p = PurePosixPath(name)
assert name == p.as_posix() and not p.is_absolute() and p.parts and all(x not in ('..', '.') for x in p.parts)
assert '\\' not in name and ':' not in name and '\n' not in name and '\r' not in name
return name
def blobs(cwd, revision, paths):
paths = list(paths)
raw = subprocess.run(['git', 'cat-file', '--batch'], cwd=cwd, check=True, capture_output=True,
input=''.join(f'{revision}:{p}\n' for p in paths).encode()).stdout
cursor, out = 0, {}
for path in paths:
end = raw.index(b'\n', cursor)
header = raw[cursor:end].split()
assert len(header) == 3 and header[1] == b'blob', path
size = int(header[2])
out[path] = raw[end + 1:end + 1 + size]
cursor = end + size + 2
assert cursor == len(raw)
return out
def root_identity():
index = Path(git(ROOT, 'rev-parse', '--git-path', 'index').decode().strip())
if not index.is_absolute():
index = ROOT / index
return {'head': git(ROOT, 'rev-parse', 'HEAD').decode().strip(), 'index_sha256': sha(index),
'tracked_status': git(ROOT, 'status', '--porcelain', '-uno').decode()}
def historical_inputs():
for name, checksum in HISTORICAL.items():
assert sha(HISTORY / name) == checksum, name
inventory = load(HISTORY / 'server-prior-input-comparisons.json')
entries = inventory['server_source_file_comparisons']
expected = {relative(e['path']): e['recorded_sha256'] for e in entries}
assert len(expected) == len(entries) and expected
found = {}
with tarfile.open(HISTORY / 'server-source-snapshot.tar.gz', 'r:gz') as archive:
for member in archive.getmembers():
name = member.name.removeprefix('./')
if name in expected:
assert member.isfile() and name not in found, name
with archive.extractfile(member) as stream:
found[name] = hashlib.file_digest(stream, 'sha256').hexdigest()
assert found == expected
assert any(e['Hash'].lower() == SERVER_SHA and Path(e['Path']).name == 'arcadia-server.exe'
for e in load(HISTORY / 'server-original-binary-hashes.json'))
return expected
def admission():
assert sha(GATES / 'inputs1.json') == INPUT_SHA
assert sha(GATES / 'inputs1.zip') == ZIP_SHA
manifest = load(GATES / 'inputs1.json')
assert manifest['archiveSha256'] == ZIP_SHA
expected = {relative(e['path']): e for e in manifest['files']}
assert len(expected) == len(manifest['files']) and expected
assert len({p.casefold() for p in expected}) == len(expected)
with zipfile.ZipFile(GATES / 'inputs1.zip') as archive:
assert len(archive.namelist()) == len(expected) and set(archive.namelist()) == set(expected)
frozen = {name: archive.read(name) for name in expected}
for name, raw in frozen.items():
assert len(raw) == expected[name]['bytes'] and hashlib.sha256(raw).hexdigest() == expected[name]['sha256'], name
assert len(expected) == 500
complete = load(GATES / 'complete.json')
assert complete['archiveSha256'] == ZIP_SHA and complete['inputsUnchanged'] is True
assert complete['gates'] == list(COMMANDS) and complete['sourceHead'] == manifest['head']
# The gates captured eeeaa126 plus the repair delta; COMMIT commits those
# exact raw inputs. Never relabel the captured sourceHead as a clean build.
assert manifest['head'] == 'eeeaa1262748800c4c9aaa1eca79a7de96757358'
for name, command in COMMANDS.items():
receipt = load(GATES / (name + '-result.json'))
assert receipt['exitCode'] == 0 and receipt['inputsUnchanged'] is True and receipt['command'] == command
assert sha(GATES / (name + '-stdout.log')) == receipt['stdoutSha256']
assert sha(GATES / (name + '-stderr.log')) == receipt['stderrSha256']
build = load(GATES / 'gui-build-result.json')['binary']
artifact = build['artifact']
assert artifact['target']['name'] == 'arcadia-studio' and artifact['profile']['test'] is False
assert artifact['profile']['opt_level'] == '0' and artifact['profile']['debug_assertions'] is True
assert Path(artifact['executable']).resolve() == Path(build['path']).resolve()
assert Path(build['path']).resolve().is_relative_to(ROOT / '.local/target-gnullvm')
gui = GATES / 'gui-repair.exe'
assert sha(gui) == build['sha256'] == 'b183ef31f16b3986538b3f2aba8435eab45978635c47f99ca01be6e72b168848'
assert gui.stat().st_size == build['bytes'] == 113206272
summaries = re.findall(r'test result: ok\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;',
(GATES / 'desktop-tests-stdout.log').read_text(encoding='utf-8'))
assert summaries == [('755', '0', '1', '0', '0')]
counts = dict(zip(('passed', 'failed', 'ignored', 'measured', 'filtered'), map(int, summaries[0])))
server = ROOT / '.local/contextual-prerelease-20260911/bin/arcadia-server.exe'
dll = ROOT / '.local/contextual-prerelease-20260911/bin/libunwind.dll'
assert sha(server) == SERVER_SHA and sha(dll) == DLL_SHA
old = historical_inputs()
return manifest, frozen, build, gui, counts, server, dll, old
def main():
ordinary(BASE)
for name in ('source', 'source-roundtrip', 'bin', 'evidence', 'preparation.json', 'committed-roundtrip.json', 'build-evidence.json', 'evidence-manifest.json'):
assert not (BASE / name).exists(), f'Refusing existing output: {name}'
before = root_identity()
assert not before['tracked_status'], 'Root tracked edits must be frozen before preparation.'
assert git(ROOT, 'rev-parse', COMMIT).decode().strip() == COMMIT
# Later root commits may record evidence only. Do not silently package later
# production/docs/dependency or instruction changes under the tested source.
git(ROOT, 'merge-base', '--is-ancestor', COMMIT, before['head'])
later = git(ROOT, 'diff', '--name-only', '-z', COMMIT, before['head']).decode().split('\0')[:-1]
assert all(p.startswith('harness/') and not p.startswith('harness/fixtures/') for p in later), later
manifest, frozen, build, gui, counts, server, dll, old = admission()
for path, raw in frozen.items():
assert ordinary(ROOT / path).read_bytes() == raw, f'Current frozen input changed: {path}'
selectors = ['crates', '.cargo', 'Cargo.toml', 'Cargo.lock', 'docs', 'examples', 'harness/fixtures']
current_paths = git(ROOT, 'ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', *selectors).decode().split('\0')[:-1]
assert sorted(current_paths) == sorted(frozen), 'Current input file set differs from the successful freeze.'
committed_paths = git(ROOT, 'ls-tree', '-r', '--name-only', '-z', COMMIT, '--', *selectors).decode().split('\0')[:-1]
assert sorted(committed_paths) == sorted(frozen), 'Committed input file set differs from the successful gate freeze.'
assert shutil.disk_usage(BASE).free >= 2 * 1024 ** 3
original = blobs(ROOT, COMMIT, frozen)
for path, raw in frozen.items():
assert original[path].replace(b'\r\n', b'\n') == raw.replace(b'\r\n', b'\n'), f'Non-newline source mismatch: {path}'
notice = load(HISTORY / 'prior-dependency-notices.json')
shared = {p: h for p, h in notice['workspace_build_inputs_sha256'].items() if p not in ('Cargo.toml', 'Cargo.lock')}
for p, raw in blobs(ROOT, COMMIT, shared).items():
assert hashlib.sha256(raw).hexdigest() == shared[p] or raw.replace(b'\r\n', b'\n') == (ROOT / p).read_bytes().replace(b'\r\n', b'\n')
assert sha(ROOT / p) == shared[p], p
if p in frozen:
assert frozen[p] == (ROOT / p).read_bytes(), p
frozen[p] = (ROOT / p).read_bytes()
registry = {(x['name'], x['version'], x['source']): x['registry_checksum']
for x in notice['packages'] if x['source']}
locked = {(x['name'], x['version'], x.get('source')): x.get('checksum')
for x in tomllib.loads(frozen['Cargo.lock'].decode())['package']}
assert registry and all(locked.get(k) == v for k, v in registry.items()), 'Historical registry coverage differs.'
# Local clone has its own index/refs and does not create a worktree in ROOT.
git(ROOT, 'clone', '--no-hardlinks', '--no-checkout', str(ROOT), str(SOURCE))
git(SOURCE, 'checkout', '-b', BRANCH, COMMIT)
checks = {p: hashlib.sha256(raw).hexdigest() for p, raw in frozen.items()}
original = blobs(SOURCE, COMMIT, checks)
restores = []
for p, raw in frozen.items():
assert original[p].replace(b'\r\n', b'\n') == raw.replace(b'\r\n', b'\n'), p
if hashlib.sha256(original[p]).hexdigest() != checks[p] or sha(SOURCE / p) != checks[p]:
assert re.fullmatch(r'[A-Za-z0-9_./-]+', p), 'Attribute path needs explicit quoting review.'
ordinary(SOURCE / p).write_bytes(raw)
restores.append({'path': p, 'sha256': checks[p], 'base_git_sha256': hashlib.sha256(original[p]).hexdigest(),
'bytes': len(raw), 'crlf': raw.count(b'\r\n'), 'lf': raw.count(b'\n')})
if restores:
with (SOURCE / '.gitattributes').open('ab') as out:
out.write(b'\n# Exact frozen navigation GUI input bytes; historical server source is separate.\n')
for item in restores:
out.write((item['path'] + ' -text\n').encode())
git(SOURCE, 'add', '--renormalize', '--', '.gitattributes', *[x['path'] for x in restores])
git(SOURCE, '-c', 'user.name=Codex-9e8r6t', '-c', 'user.email=codex-9e8r6t@local.invalid',
'commit', '-m', 'Preserve exact navigation prerelease GUI inputs')
reconstruction = git(SOURCE, 'rev-parse', 'HEAD').decode().strip()
committed = blobs(SOURCE, 'HEAD', checks)
fresh = BASE / 'source-roundtrip'
fresh.mkdir()
git(SOURCE, 'checkout-index', '--all', '--prefix=' + fresh.as_posix() + '/')
for p, checksum in checks.items():
assert hashlib.sha256(committed[p]).hexdigest() == sha(SOURCE / p) == sha(fresh / p) == checksum, p
for p, checksum in SCRIPT_HASHES.items():
assert sha(SOURCE / p) == checksum, p
assert not git(SOURCE, 'status', '--porcelain').strip()
comparison = {'historical_file_count': len(old), 'changed_current_inputs': [p for p, h in old.items() if sha(SOURCE / p) != h],
'added_current_files': sorted(p.relative_to(SOURCE).as_posix() for crate in ('oa-core', 'oa-server')
for p in (SOURCE / 'crates' / crate).rglob('*') if p.is_file() and p.relative_to(SOURCE).as_posix() not in old),
'scope': 'Historical server inputs verified against their own preserved tar, not required to equal the new GUI source.'}
assert comparison['changed_current_inputs'] == ['crates/oa-core/src/language.rs']
assert comparison['added_current_files'] == ['crates/oa-core/src/language_outline_tests.rs']
(BASE / 'bin').mkdir()
artifacts = []
for source, name, checksum in ((gui, 'arcadia-studio.exe', build['sha256']), (server, 'arcadia-server.exe', SERVER_SHA), (dll, 'libunwind.dll', DLL_SHA)):
assert sha(source) == checksum
target = BASE / 'bin' / name
with source.open('rb') as src, target.open('xb') as dst:
shutil.copyfileobj(src, dst)
assert sha(source) == sha(target) == checksum
artifacts.append({'source': str(source), 'destination': 'bin/' + name, 'sha256': checksum, 'bytes': target.stat().st_size})
provenance = {name: HISTORY / name for name in HISTORICAL}
provenance.update({'gui-inputs1.json': GATES / 'inputs1.json', 'gui-complete.json': GATES / 'complete.json',
'gui-gate-run.py': GATES / 'run.py'})
for name in COMMANDS:
for suffix in ('-result.json', '-stdout.log', '-stderr.log'):
provenance['gui-' + name + suffix] = GATES / (name + suffix)
(BASE / 'evidence').mkdir()
evidence = []
for name, source in provenance.items():
checksum = sha(source)
destination = BASE / 'evidence' / name
with source.open('rb') as src, destination.open('xb') as dst:
shutil.copyfileobj(src, dst)
assert sha(source) == sha(destination) == checksum
evidence.append({'source': str(source), 'destination': 'evidence/' + name, 'sha256': checksum, 'bytes': destination.stat().st_size})
record(BASE / 'evidence-manifest.json', evidence)
record(BASE / 'committed-roundtrip.json', {'base_commit': COMMIT, 'reconstruction_commit': reconstruction, 'checks': checks, 'clean': True})
record(BASE / 'build-evidence.json', {
'source_identity': f'GUI exact-byte reconstruction {reconstruction} from {COMMIT}; {len(manifest["files"])} frozen GUI gate inputs plus separately verified shared build resources. This is not the original build commit. EFEE server retains its separate September 7 source snapshot; its old language.rs differs from current GUI core. No current-source server claim.',
'profile': f'Prebuilt Windows development GUI, actual full desktop result {counts}. Retained EFEE server is the historical development artifact, not rebuilt. Native/package acceptance remains pending.',
'compiler': 'Original GUI Cargo artifact/profile and server build records retained separately; package collector records current compiler/sysroot facts. No reproducible build or new server build claimed.',
'target': TARGET, 'binary_sha256': {'arcadia-studio.exe': build['sha256'], 'arcadia-server.exe': SERVER_SHA},
'provenance_reference_base': 'evidence/ references are external preparation records, not files inside the ZIP.',
'per_binary_provenance': {
'arcadia-studio.exe': {'build': 'evidence/gui-gui-build-result.json', 'inputs': 'evidence/gui-inputs1.json', 'full_gate': 'evidence/gui-desktop-tests-result.json', 'complete': 'evidence/gui-complete.json', 'captured_head': manifest['head'], 'scope': 'Built from captured eeeaa126 plus exact repair input delta; those bytes are committed at base_commit, then reconstructed without changing code.'},
'arcadia-server.exe': {'build': 'evidence/server-original-gates.json', 'hashes': 'evidence/server-original-binary-hashes.json', 'source_tar': 'evidence/server-source-snapshot.tar.gz', 'source_inventory': 'evidence/server-prior-input-comparisons.json', 'notices': 'evidence/prior-dependency-notices.json', 'scope': 'Unchanged historical EFEE; separate source, not rebuilt from navigation source.'}}})
after = root_identity()
assert after == before, 'Root HEAD/index/tracked state changed during preparation; inspect before proceeding.'
record(BASE / 'preparation.json', {'version': VERSION, 'target': TARGET, 'base_commit': COMMIT, 'reconstruction_commit': reconstruction,
'branch': BRANCH, 'record_only_root_paths_after_base': later, 'historical_registry_count': len(registry), 'source_repository': str(SOURCE), 'restorations': restores, 'gui_inputs': {e['path']: e['sha256'] for e in manifest['files']},
'shared_non_cargo_inputs': shared, 'historical_server_inputs': old, 'historical_server_comparison': comparison,
'artifacts': artifacts, 'full_test_counts': counts, 'root_before': before, 'root_after': after, 'prepared': now(),
'scope': 'Isolated preparation only; package integrity and native acceptance pending.'})
print(json.dumps({'success': True, 'reconstruction': reconstruction, 'gui_inputs': len(manifest['files']), 'restorations': len(restores), 'historical_server_inputs': len(old)}))
if __name__ == '__main__':
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
assert not (BASE / 'prepare-result.json').exists()
status = {'started': now(), 'scope': 'Preparation only; no application/Cargo/package execution.'}
try:
main()
status['success'] = True
except BaseException as error:
status.update(success=False, error=repr(error))
traceback.print_exc()
raise
finally:
status['finished'] = now()
record(BASE / 'prepare-result.json', status)