55 lines
3.3 KiB
Python
55 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Archive an isolated native run and its verified launch-time addon snapshot; never overwrite evidence.
|
|
Run only after stopping the test and before editing/copying its source tree again.
|
|
"""
|
|
from pathlib import Path
|
|
import argparse,json,shutil,subprocess,sys
|
|
from native_provenance import archive_source, encoded, MANIFEST, METADATA, PROVENANCE
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
p=argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument('session',type=Path)
|
|
p.add_argument('name',help='New evidence directory name, e.g. native-2026-09-19-003231')
|
|
p.add_argument('--allow-legacy-archive-time-source', action='store_true', help='Explicit historical-only import for sessions without a pre-launch snapshot')
|
|
a=p.parse_args()
|
|
if Path(a.name).name!=a.name or a.name in {'.','..'}:p.error('Name must be one directory component')
|
|
source=a.session.resolve()
|
|
if not (source/'console.log').is_file():p.error('Session has no console.log')
|
|
try:
|
|
manifest_bytes, provenance, launched = archive_source(source, ROOT, a.allow_legacy_archive_time_source)
|
|
except (OSError, ValueError) as error:
|
|
p.error(str(error))
|
|
dest=ROOT/'docs/evidence'/a.name
|
|
try:dest.mkdir(parents=True,exist_ok=False)
|
|
except FileExistsError:p.error('Evidence already exists; refusing to overwrite')
|
|
for name in ['console.log','script.log','error.log','crash.log','rereintroduced-hq.log','headless-launch.log','runner.json','game-startup-report.json']:
|
|
if (source/name).is_file():shutil.copyfile(source/name,dest/(name+'.txt'))
|
|
for capture in sorted([*source.glob('err-*.png'), *source.glob('err-*.bmp')]):
|
|
if capture.is_file():shutil.copyfile(capture,dest/capture.name)
|
|
(dest/'source-sha256.json').write_bytes(manifest_bytes)
|
|
(dest/PROVENANCE).write_bytes(encoded(provenance))
|
|
if launched:
|
|
for name in (MANIFEST, METADATA): shutil.copyfile(source/name, dest/name)
|
|
if (source/'game-startup-report.json').is_file():
|
|
# Recompute from the sealed console rather than trusting the saved summary.
|
|
# Ordinary startup has no scripted completion and cannot qualify as an escape.
|
|
from headless_game import game_evidence, CONTROL
|
|
runner=json.loads((source/'runner.json').read_text())
|
|
report=game_evidence((dest/'console.log.txt').read_text(errors='replace'),runner.get('project'))
|
|
report['log']=str((dest/'console.log.txt').relative_to(ROOT))
|
|
control=runner.get('project')==CONTROL
|
|
expected_result='vanilla-control-started' if control else 'game-started'
|
|
if control:
|
|
consistent=runner.get('control_only') is True and report.get('control_only') is True
|
|
report['qualification']='vanilla startup control only; no successor mission or release qualification'
|
|
else:
|
|
consistent=runner.get('control_only',False) is False and report.get('control_only',False) is False
|
|
report['control_metadata_consistent']=consistent
|
|
(dest/'report.json').write_bytes(encoded(report))
|
|
status=0 if report['ready'] is True and consistent and runner.get('result')==expected_result and runner.get('cleanup_confirmed') is True else 1
|
|
else:
|
|
with (dest/'report.json').open('w') as report:
|
|
result=subprocess.run([sys.executable,str(ROOT/'tools/native_report.py'),str((dest/'console.log.txt').relative_to(ROOT))],cwd=ROOT,stdout=report)
|
|
status=result.returncode
|
|
print(f'Archived {dest.relative_to(ROOT)}; native report exit={status} (nonzero retains failures)')
|
|
sys.exit(status)
|