import importlib.util,json,os,pathlib,runpy,shutil,subprocess,sys,tarfile,hashlib,signal,time
os.umask(0o077)
OLD=pathlib.Path('/tmp/codex/ltb1021/2026-09-10_contextra-det-window_r01')
spec=importlib.util.spec_from_file_location('gate',OLD/'gate.py');g=importlib.util.module_from_spec(spec)
sys.modules['gate']=g;spec.loader.exec_module(g)
g.R=pathlib.Path(__file__).parent;g.HEAD='344164ebf9be70fb0c330e4e72e97203573bb6d5'
EDITED=['app/multi_pipeline.py','tests/test_pipeline_message_callback.py','tests/sdk_message_callback_probe.py']
def fingerprint():return {p:g.sha(g.REPO/p) for p in EDITED}
def assert_source():
    assert g.shell(['git','-C',str(g.REPO),'rev-parse','HEAD']).strip()==g.HEAD
    assert fingerprint()==json.loads((g.R/'source-manifest.json').read_text())['edited']
g.assert_source=assert_source
if (g.R/'gate.json').exists():g.TAG=json.loads((g.R/'gate.json').read_text())['tag']

def gate():
    assert not (g.R/'gate.json').exists()
    assert g.shell(['git','-C',str(g.REPO),'rev-parse','HEAD']).strip()==g.HEAD
    status=g.shell(['git','-C',str(g.REPO),'status','--porcelain']).splitlines()
    assert sorted(l[3:] for l in status)==sorted(EDITED)
    assert not g.shell(['git','-C',str(g.REPO),'diff','--cached','--name-only']).strip()
    docker_root=g.shell(['docker','info','--format','{{.DockerRootDir}}']).strip()
    free=min(shutil.disk_usage('/').free,shutil.disk_usage(docker_root).free);assert free>=15*1024**3
    edited=fingerprint();digest=hashlib.sha256(json.dumps(edited,sort_keys=True).encode()).hexdigest()
    g.TAG='contextra-analytics:callback-344164e-'+digest[:8]
    assert subprocess.run(['pgrep','-f',r'(^|/)docker (build|buildx build|pull)'],capture_output=True).returncode==1
    assert subprocess.run(['docker','image','inspect',g.TAG],capture_output=True).returncode!=0
    data=g.inspect_all();c=next(x for x in data if x['Name']=='/contextra-analytics-1')
    assert c['State']['Status']=='exited' and not c['State']['OOMKilled'] and c['RestartCount']==1
    assert c['Image']=='sha256:8523767ab9717efe954f378600189aafb312eef35cb9650a26a2596ed7a9aed8'
    assert c['HostConfig']['RestartPolicy']=={'Name':'on-failure','MaximumRetryCount':3}
    env=g.load_env_file(str(g.ENV));assert env['CONTEXTRA_IMAGE']=='contextra-analytics:start-window-43e1923'
    assert g.sha(g.CFG)=='5e5a4d22786f8b5218ac821379fbec091d9e3006f62c880ee3e3da5ced2f0e0b'
    assert g.sha(g.CFG.parent/'2/model.plan')=='5fdf1e9c55ab947a3b7d5f7f6bdceaf1e36a9e52efdf86f5a15d3b99b8631d1e'
    overlay=pathlib.Path(env['CONTEXTRA_RUNTIME_STATE_ROOT'])/'streams_runtime.yaml'
    specs=g.yaml.safe_load(overlay.read_text())['added'];assert len(specs)==1;s=specs[0]
    assert all(s[x] is False for x in ['store','active_learning','autostart']) and s['scenarios']=='off'
    assert s['inference_profile']['roles']==['det'] and s['inference_profile']['det_model']=='RFDETRMedium'
    g.validate_deployment_inputs(str(g.ENV))
    gpu=int(g.shell(['nvidia-smi','--id=0','--query-gpu=memory.used','--format=csv,noheader,nounits']).strip());assert gpu<16384
    snap=g.R/'snapshot';snap.mkdir(mode=0o700)
    p=subprocess.Popen(['git','-C',str(g.REPO),'archive',g.HEAD],stdout=subprocess.PIPE)
    with tarfile.open(fileobj=p.stdout,mode='r|') as archive:archive.extractall(snap,filter='data')
    assert p.wait()==0
    for path in EDITED:shutil.copyfile(g.REPO/path,snap/path)
    assert fingerprint()==edited
    (g.R/'source-manifest.json').write_text(json.dumps({'base':g.HEAD,'edited':edited,'digest':digest},indent=2))
    out=dict(status='PASS',head=g.HEAD,tag=g.TAG,env_hash=g.sha(g.ENV),config_hash=g.sha(g.CFG),
      overlay_hash=g.sha(overlay),plan_hash=g.sha(g.CFG.parent/'2/model.plan'),old_image=c['Image'],
      old_tag=env['CONTEXTRA_IMAGE'],other_containers=g.other_baseline(data),disk_free_bytes=free,gpu_used_mib=gpu,
      original_restart=c['HostConfig']['RestartPolicy'],source_digest=digest)
    (g.R/'gate.json').write_text(json.dumps(out,indent=2));print(json.dumps({k:v for k,v in out.items() if k!='other_containers'}))

def build():
    assert_source();assert not (g.R/'build.json').exists()
    assert json.loads((g.R/'source-verify.json').read_text())['status']=='PASS'
    start=time.monotonic();reason=None
    with (g.R/'build.log').open('w') as log:
        p=subprocess.Popen(['docker','build','-f','docker/Dockerfile','--target','app','-t',g.TAG,'.'],cwd=g.R/'snapshot',stdout=log,stderr=subprocess.STDOUT,start_new_session=True,env=dict(os.environ,DOCKER_BUILDKIT='1'))
        while p.poll() is None:
            if time.monotonic()-start>1200 or shutil.disk_usage('/').free<15*1024**3:
                reason='TIME_OR_DISK_CAP';os.killpg(p.pid,signal.SIGTERM)
                try:p.wait(15)
                except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL)
                break
            time.sleep(2)
        code=p.wait()
    out=dict(returncode=code,abort_reason=reason,elapsed_seconds=round(time.monotonic()-start,2),tag=g.TAG,source=g.HEAD)
    if code==0 and reason is None:
        data=json.loads(g.shell(['docker','image','inspect',g.TAG]))[0];out.update(image_id=data['Id'],size=data['Size'])
    (g.R/'build.json').write_text(json.dumps(out,indent=2));print(json.dumps(out));assert code==0 and reason is None

if __name__=='__main__':
    if sys.argv[1]=='gate':gate()
    elif sys.argv[1]=='build':build()
    elif sys.argv[1]=='verify':runpy.run_path(str(OLD/'verify_image.py'),run_name='__main__')
