"""Post-completion ONLY: fresh terminal inventory, fixed science, historical seals. No training, repair, retry or change to the registered model/experiment. Output goes to an independently provisioned EMPTY sibling directory. The real normal runner's final ledger report is retained unchanged, even though a subsequent full-tree audit sees its separately authenticated completion report as extra. """ from __future__ import annotations import argparse from datetime import datetime, timezone import hashlib import importlib.util import json import os from pathlib import Path import shlex import shutil import subprocess import sys import time from progress_normal import safe, read_json, require ROOT = Path('/data/digital-drosophila') MODEL = ROOT/'models/male-cns-lif/normal-conditioning-v1' EXP = ROOT/'experiments/phase4ar-normal-conditioning' CONTROL = ROOT/'logs/phase4ar-normal-conditioning' OUT = ROOT/'experiments/phase4ar-normal-conditioning-final-audit' TOOLS = Path(__file__).resolve().parent PYTHON = ROOT/'models/male-cns-lif/.venv/bin/python' REG_SHA = 'b63e6281b6505ca3b60acb467dd0b3fae274cf513dcbfd653649ba52e9ba9740' START_PINS = { 'launch-attempt.json': dict(bytes=229,sha256='93274a58b83a3e508e378f3972a62c2e43a461bc4a890314f6e38530fa05719c'), 'launch.json': dict(bytes=523,sha256='94c7dba031149451a0d653dd7c083fa5c918b9363fa1caef31b988d02d72672e'), 'supervisor-start.json': dict(bytes=187,sha256='3fa980cfcf0993bb7aa29de87eff31f5bc48be215a2cfbc03b715d4b84672839'), } def identity(path): p=safe(path);require(p.is_file(),'Missing regular artifact: '+str(p)) size=0;h=hashlib.sha256() with p.open('rb') as f: for part in iter(lambda:f.read(8<<20),b''):size+=len(part);h.update(part) return dict(bytes=size,sha256=h.hexdigest()) def read(path): return read_json(path)[0] def save(path,value): with safe(path).open('x',encoding='utf-8',newline='\n') as f: json.dump(value,f,sort_keys=True,indent=2,ensure_ascii=False,allow_nan=False);f.write('\n') def verify(pins): for p,sha in pins.items():require(identity(p)['sha256']==sha,'Actual dependency changed: '+str(p)) def live_runtime_rows(ps_text,model,exp): """Exact argv tokens, not substring matching or a missing-PID success rule.""" runtime={str(model/n) for n in ('run_normal.py','normal_runner.py','normal_inputs.py')} worker=str(model.parent/'conditioning-interface-v2/isolated_workers.py') matches=[] for line in ps_text.splitlines(): fields=line.strip().split(None,1) if len(fields)!=2:continue try:pid=int(fields[0]);argv=shlex.split(fields[1]) except (ValueError,TypeError):continue if not argv:continue owned=bool(runtime.intersection(argv)) if worker in argv and '--job' in argv: i=argv.index('--job')+1 owned=owned or (i=reserve+(4<<30),'No space for terminal audit while preserving registered disk reserve') sources=source_identity();save(OUT/'source-identity.json',sources);save(OUT/'readiness-before.json',ready) try: from terminal_ledger import terminal_inventory from normal_outcome import summarize_normal_analysis, analysis_digest terminal=terminal_inventory(EXP,REG_SHA,MODEL,supervisor_dir=CONTROL,supervisor_start_pins=START_PINS) inventory=terminal.pop('verified_inventory') save(OUT/'terminal-before.json',terminal);save(OUT/'verified-inventory.json',inventory) command=[str(PYTHON),'-B',str(TOOLS/'normal_finalize.py'),'analyze-child', '--inventory-sha256',identity(OUT/'verified-inventory.json')['sha256'], '--terminal-sha256',identity(OUT/'terminal-before.json')['sha256']] observed=child('analysis',command,43200) analysis_sha=identity(OUT/'analysis.json')['sha256'];analysis=json.loads((OUT/'analysis.json').read_text()) child('historical-integrity',[str(PYTHON),'-B',str(MODEL/'normal_prepare.py'),'integrity', '--output',str(OUT/'historical-integrity-after.json')],1800) historical=json.loads((OUT/'historical-integrity-after.json').read_text()) require(historical.get('complete') is True,'Historical integrity failed') ready_after=readiness();save(OUT/'readiness-after.json',ready_after) require(ready_after['exit_identity']==ready['exit_identity'] and ready_after['completion_identity']==ready['completion_identity'], 'Terminal control bytes changed while analyzing') final=terminal_inventory(EXP,REG_SHA,MODEL,supervisor_dir=CONTROL,supervisor_start_pins=START_PINS) require(final.pop('verified_inventory')==inventory,'Actual full inventory changed during science audit') require(final==terminal,'Terminal audit changed during science analysis') save(OUT/'terminal-after.json',final) for p,info in sources['files'].items():require(identity(p)==info,'Terminal source changed during audit') require(identity(OUT/'analysis.json')['sha256']==analysis_sha,'Actual analysis bytes changed') # This outer binding contains actual byte/exit verification, never only a caller's PASS Boolean. binding=dict(registration_sha256=REG_SHA,terminal_reconciliation_pass=True,historical_integrity_after_pass=True, analysis_sha256=analysis_digest(analysis),analysis_file_sha256=analysis_sha, actual_parent_exit_code=observed['exit_code'], analyzed_source_sha256=read(EXP/'protocol/preregistered.json')['source_sha256'][str(MODEL/'normal_metrics.py')]) outcome=summarize_normal_analysis(analysis,binding) save(OUT/'outer-evidence.json',binding);save(OUT/'final-status.json',outcome) return dict(outcome=outcome,seal=seal_output()) except BaseException as exc: if not (OUT/'audit-stopped.json').exists():save(OUT/'audit-stopped.json',dict(status='INCOMPLETE-AUDIT', error_type=type(exc).__name__,error=str(exc),learning_failure_inferred=False,automatic_retry=False)) raise if __name__=='__main__': parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('mode',choices=('check','audit','analyze-child')) parser.add_argument('--inventory-sha256');parser.add_argument('--terminal-sha256');args=parser.parse_args() if args.mode=='check':print(json.dumps(readiness(),indent=2)) elif args.mode=='audit':print(json.dumps(finalize(),indent=2)) else: require(args.inventory_sha256 and args.terminal_sha256,'Actual outer report hashes required') analyze_child(args.inventory_sha256,args.terminal_sha256)