89 lines
7.6 KiB
Python
89 lines
7.6 KiB
Python
"""Final identified State-bend gates. PREPARED ONLY; parent authorizes execution."""
|
|
from pathlib import Path
|
|
import argparse, datetime, hashlib, json, os, re, shutil, subprocess, sys
|
|
ROOT=Path(__file__).resolve().parent
|
|
REPO=ROOT.parent/'state-bends-20260923'
|
|
WORKSPACE=ROOT.parent.parent
|
|
ENV_SCRIPT=ROOT.parent/'toolchain/env.ps1'
|
|
RUNTIME=ROOT.parent/'toolchain/llvm-mingw-20260908-ucrt-x86_64/bin/libunwind.dll'
|
|
RUNTIME_SHA='aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a'
|
|
def record(path):
|
|
p=Path(path).resolve();data=p.read_bytes();return {'path':str(p),'bytes':len(data),'sha256':hashlib.sha256(data).hexdigest()}
|
|
def save(path,value):
|
|
with path.open('x',encoding='utf-8',newline='\n') as f:json.dump(value,f,indent=2,ensure_ascii=False,allow_nan=False);f.write('\n')
|
|
def git(*args):return subprocess.check_output(['git','-c','core.longpaths=true','-C',str(REPO),*args])
|
|
def inputs():
|
|
return [record(REPO/p.decode()) for p in sorted(git('ls-files','--cached','--others','--exclude-standard','-z').split(b'\0')) if p and (REPO/p.decode()).is_file()]
|
|
def now():return datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
def main():
|
|
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--source-commit',required=True);p.add_argument('--attempt',required=True);a=p.parse_args()
|
|
pin=a.source_commit
|
|
if not re.fullmatch('[0-9a-f]{40}',pin) or not re.fullmatch(r'final-[0-9]{2}',a.attempt):raise RuntimeError('Explicit full source pin and fresh final-NN attempt required')
|
|
def clean():
|
|
if git('rev-parse','HEAD').decode().strip()!=pin or git('status','--porcelain'):raise RuntimeError('Final gates require exact clean committed HEAD')
|
|
clean()
|
|
expected_cargo=(ROOT.parent/'toolchain/cargo').resolve();target=(WORKSPACE/'target').resolve()
|
|
if Path(os.environ.get('CARGO_HOME','')).resolve()!=expected_cargo or Path(os.environ.get('CARGO_TARGET_DIR','')).resolve()!=target or os.environ.get('RUSTUP_TOOLCHAIN')!='stable-x86_64-pc-windows-gnullvm':raise RuntimeError('Source parent env.ps1 and explicitly select shared target before invocation')
|
|
if record(RUNTIME)['sha256']!=RUNTIME_SHA:raise RuntimeError('Admitted runtime differs')
|
|
out=ROOT/a.attempt;out.mkdir(exist_ok=False);(out/'artifacts').mkdir()
|
|
before=inputs();save(out/'inputs-before.json',before)
|
|
environment={k:os.environ.get(k) for k in ['PATH','CARGO_HOME','RUSTUP_HOME','RUSTUP_TOOLCHAIN','CARGO_TARGET_DIR','CARGO_BUILD_JOBS','CARGO_INCREMENTAL','CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_LINKER','RC','RUSTFLAGS','CARGO_ENCODED_RUSTFLAGS']}
|
|
configs=[];dirs=[REPO,*REPO.parents,expected_cargo]
|
|
for directory in dict.fromkeys(dirs):
|
|
for name in ('config','config.toml'):
|
|
path=directory/name if directory==expected_cargo else directory/'.cargo'/name
|
|
configs.append({'path':str(path),'present':path.is_file(),'record':record(path) if path.is_file() else None})
|
|
save(out/'environment.json',{'environment':environment,'env_script':record(ENV_SCRIPT),'cargo_configs':configs,'cargo_resolved':shutil.which('cargo'),'rustc_resolved':shutil.which('rustc'),'runtime':record(RUNTIME),'orchestrator':record(Path(__file__)),'command_runner':record(ROOT/'run.py')})
|
|
steps=[];outputs=[];relocations=[];failure=None;after=None;unchanged=False
|
|
def preserve(path):
|
|
original=record(path);dest=out/'artifacts'/path.name
|
|
with path.open('rb') as src,dest.open('xb') as dst:shutil.copyfileobj(src,dst)
|
|
copied=record(dest)
|
|
if record(path)!=original or any(original[k]!=copied[k] for k in ('bytes','sha256')):raise RuntimeError('Artifact changed during preservation')
|
|
outputs.append(copied);relocations.append({'original':original,'preserved':copied})
|
|
commands=[
|
|
('toolchain',['rustc','--version','--verbose'],[]),
|
|
('cargo-version',['cargo','--version','--verbose'],[]),
|
|
('workspace-tests',['cargo','test','--workspace','--locked','--offline'],[]),
|
|
('workspace-clippy',['cargo','clippy','--workspace','--all-targets','--locked','--offline','--','-D','warnings'],[]),
|
|
('desktop-build',['cargo','build','-p','oa-desktop','--locked','--offline'],[target/'debug/arcadia-studio.exe']),
|
|
('evaluators-build',['cargo','build','-p','oa-core','--example','attachment_snapshot','--example','state_bend_routing_snapshot','--locked','--offline'],[target/'debug/examples/attachment_snapshot.exe',target/'debug/examples/state_bend_routing_snapshot.exe'])]
|
|
started=now()
|
|
try:
|
|
for name,argv,artifacts in commands:
|
|
clean()
|
|
wrapper_log=out/(name+'.orchestration.log')
|
|
with wrapper_log.open('xb') as log:
|
|
proc=subprocess.run([sys.executable,str(ROOT/'run.py'),a.attempt+'/'+name,*argv],cwd=WORKSPACE,stdout=log,stderr=subprocess.STDOUT)
|
|
result_path=out/name/'result.json';result=json.loads(result_path.read_text(encoding='utf-8')) if result_path.is_file() else None
|
|
step={'name':name,'argv':argv,'runner_exit_code':proc.returncode,'orchestration_log':record(wrapper_log),'result':record(result_path) if result else None}
|
|
steps.append(step)
|
|
if proc.returncode or result is None or result['exit_code']!=0 or result['inputs_unchanged'] is not True or result['source_commit']!=pin or result['working_changes']:raise RuntimeError('Final gate failed: '+name)
|
|
for phase in ('inputs-before.json','inputs-after.json'):
|
|
if json.loads((out/name/phase).read_text(encoding='utf-8'))!=before:raise RuntimeError('Source inventory differs in '+name+'/'+phase)
|
|
for path in artifacts:preserve(path)
|
|
preserve(RUNTIME)
|
|
except Exception as error:failure=str(error)
|
|
finally:
|
|
verification_errors=[]
|
|
try:
|
|
after=inputs();save(out/'inputs-after.json',after);unchanged=before==after;clean()
|
|
if not unchanged:verification_errors.append('Source inventories changed')
|
|
frozen_environment=json.loads((out/'environment.json').read_text(encoding='utf-8'))
|
|
for key in ('env_script','runtime','orchestrator','command_runner'):
|
|
if record(frozen_environment[key]['path'])!=frozen_environment[key]:verification_errors.append('Bound gate input changed: '+key)
|
|
for cfg in configs:
|
|
path=Path(cfg['path'])
|
|
if path.is_file()!=cfg['present'] or (cfg['present'] and record(path)!=cfg['record']):verification_errors.append('Cargo config changed: '+str(path))
|
|
for entry in outputs:
|
|
if record(entry['path'])!=entry:verification_errors.append('Preserved artifact changed')
|
|
except Exception as error:verification_errors.append('Final capture/verification failed: '+str(error));unchanged=False
|
|
if verification_errors:failure='; '.join(([failure] if failure else [])+verification_errors)
|
|
save(out/'artifact-relocation.json',{'source_commit':pin,'artifacts':relocations,'scope':'Original shared-target locations are historical; native stage must bind preserved copies'})
|
|
passed=failure is None and unchanged and len(steps)==len(commands) and len(outputs)==4
|
|
save(out/'summary.json',{'source_commit':pin,'passed':passed,'inputs_unchanged':unchanged,'started_utc':started,'finished_utc':now(),'failure':failure,'steps':steps,'outputs':outputs,'source_manifest':record(out/'inputs-before.json'),'environment':record(out/'environment.json'),'relocation':record(out/'artifact-relocation.json'),'scope':'Recorded command exit codes and exact input inventories; no native GUI acceptance'})
|
|
print(json.dumps({'summary':str(out/'summary.json'),'passed':passed,'failure':failure},indent=2))
|
|
if not passed:raise SystemExit(1)
|
|
if __name__=='__main__':main()
|
|
|