import subprocess, json, pathlib, shutil, time, os, signal
R=pathlib.Path('/tmp/codex/ltb1021/2026-09-10_medium-runtime_r01')
repo='/workspace/contextra-analytics-runtime-lifecycle-refactor'
gate=json.loads((R/'gate.json').read_text())
assert subprocess.check_output(['git','-C',repo,'rev-parse','HEAD'],text=True).strip()==gate['source']
assert not subprocess.check_output(['git','-C',repo,'status','--porcelain'],text=True).strip()
assert shutil.disk_usage('/').free>=25*1024**3
os.umask(0o077)
with (R/'build.log').open('w') as log:
 p=subprocess.Popen(['docker','build','--progress=plain','-f','docker/Dockerfile','--target','app','-t',gate['new_tag'],'.'],cwd=repo,stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
 start=time.monotonic(); reason=None
 while p.poll() is None:
  if shutil.disk_usage('/').free<15*1024**3: reason='DISK_FLOOR'
  if time.monotonic()-start>1200: reason='BUILD_TIMEOUT'
  if reason:
   os.killpg(p.pid,signal.SIGTERM)
   try: p.wait(timeout=30)
   except subprocess.TimeoutExpired: os.killpg(p.pid,signal.SIGKILL);p.wait()
   break
  time.sleep(3)
 result={'exit':p.returncode,'reason':reason,'seconds':round(time.monotonic()-start,1),'remaining_gib':round(shutil.disk_usage('/').free/1024**3,2)}
 if p.returncode==0:
  img=json.loads(subprocess.check_output(['docker','image','inspect',gate['new_tag']],text=True))[0]
  result.update(image_id=img['Id'],size=img['Size'],architecture=img['Architecture'],layers=len(img['RootFS']['Layers']))
 (R/'build-result.json').write_text(json.dumps(result,indent=2)); print(json.dumps(result))
 raise SystemExit(p.returncode or (1 if reason else 0))
