from ops import *
import datetime,re
job='20260927-contextra-sender-training-r01'
assert cmd(['git','-C',str(REPO),'rev-parse','HEAD']).strip()=='10ddca4635378fadb2effa70f4f3faa2f906ae4e'
assert not cmd(['git','-C',str(REPO),'diff','--cached','--name-only']).strip()
paths=sorted(set(cmd(['git','-C',str(REPO),'diff','--name-only']).splitlines()+cmd(['git','-C',str(REPO),'ls-files','--others','--exclude-standard']).splitlines()))
cmd(['git','-C',str(REPO),'diff','--check'])
for p in paths:
    if not p.endswith('.md'):continue
    for target in re.findall(r'\]\(([^)]+)\)',(REPO/p).read_text()):
        if '://' not in target and not target.startswith('#'):assert ((REPO/p).parent/target.split('#')[0]).exists(),target
image=json.loads((HERE/'image-ca219d35ee.json').read_text())
assert inspect('contextra-analytics-1')['Image']==image['id']
for p,h in image['hashes'].items():
    assert hashlib.sha256((REPO/p).read_bytes()).hexdigest()==h
    assert cmd(['docker','exec','contextra-analytics-1','sha256sum','/opt/contextra/'+p]).split()[0]==h
trial=json.loads((HERE/'scale-ready/trial-result.json').read_text())
assert [s['level'] for s in trial['stages'] if s['status']=='PASS']==[1,8]
assert not trial['errors'] and trial['cleanup']['reaped']
assert all(not p['last_cleanup']['forced'] and p['last_cleanup']['child_reaped'] and p['last_cleanup']['diagnostic']['exit_code']==0
    for r in trial['cleanup']['states'].values() for p in r['providers'].values())
final=json.loads((HERE/'final-state-v2.json').read_text())
assert final['states']=={'stopped':36} and final['children']==0 and final['wss']
assert final['training']['configured']==dict(enabled=True,minimum_persons=8,interval_seconds=900,width=1280,height=720,max_bytes=200000000000,min_free_bytes=100000000000)
assert all(r['state']=='stopped' and not r.get('cleanup_pending') for r in api('/api/v1/streams')['streams'])
env=dict(v.split('=',1) for v in inspect('contextra-analytics-1')['Config']['Env'])
assert env['CONTEXTRA_EVENT_COOLDOWN_SECONDS']=='10' and env['CONTEXTRA_METADATA_STREAM_LENGTH']=='300'
code='''import json,urllib.request
out={}
for m in ('RFDETRMedium','RFDETRKeypointPreview','RFDETRFireSmoke','Awiros_person_and_head_detection'):
 c=json.load(urllib.request.urlopen('http://localhost:8000/v2/models/'+m+'/config',timeout=3))
 assert c['max_batch_size']==8 and c['version_policy']['specific']['versions']==[8] and c['dynamic_batching']['preferred_batch_size']==[8]
 assert all(g['count']==8 for g in c['instance_group'])
 out[m]=dict(version=8,max_batch_size=8,preferred_batch_size=[8],instances=8)
print(json.dumps(out))'''
models=json.loads(cmd(['docker','exec','contextra-analytics-1','python3','-B','-c',code],timeout=20))
learning=[]
for p in Path('/data/contextra/training').glob('*/camera-*/*/metadata.json'):
    m=json.loads(p.read_text());assert m['decodedPtsNs']==m['snapshot']['ptsNs'] and m['storedSize']==[1280,720]
    assert sorted(f.name for f in p.parent.iterdir())==['metadata.json','overlay.jpg','raw.jpg']
    learning.append(dict(camera=m['snapshot']['cameraId'],people=m['personCount'],exact_pts=True,bytes=sum(f.stat().st_size for f in p.parent.iterdir())))
assert len(learning)==6
save('verification.json',dict(python='3.12',targeted_passes=[50,13,19,6],overlap=True,subtests=4,
    stages=[1,8],reaped_children=24,models=models,final=final,learning=learning,files=paths,source_image_hashes_match=True,
    comparison_image='sender-training-6d1173df6e',final_image=image['tag'],post_trial_change='Training disk reservation96MiB and output size check only'))
receipt=dict(schema_version='ltb1021.codex_hub.report_receipt.v1',receipt_id=job,
 timestamp=datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).isoformat(),session_alias='work-session',
 task='Contextra provider 직접 송신, 조건부 학습 스냅샷과 Event별 ROI 반영',
 results=['provider child Frame 직접 송신과 부모 상태/run fencing/Stop 소유권 유지',
 '학습 person score 합집합8개/900초/1280x720/200GB/여유100GB, 내부 인증 CAS와 owned 정리',
 '복합1/8측정: 8채널 Redis40.87->99.27FPS,완료121.37->99.57FPS.비대상서비스재시작관측으로동등부하아님',
 '실영상 학습6READY/동일PTS/3파일,Event ROI분리,자연Artifact111READY/10FAILED는별도미해결'],
 verify=['Python3.12 표적50+13+19+6PASS(중복)와4subtest','24child exit0/unforced/reap,36stopped/WSS/모델4READY/설정일치'],commits=[],
 remaining=['Web표시수용','전체채널성능기준선및아이서티배포별도승인','기존Artifact renderer실패/대기'])
save('report-receipt.json',receipt)
print(cmd(['codex-report-receipt','--profile','ltb1021','--session-alias','work-session','--receipt',str(HERE/'report-receipt.json')]))
print(cmd(['python3','/workspace/users/root/codex-ops-docs/servers/test/users/ltb1021/portfolio_logs/scripts/create_portfolio_candidate.py',
 '--run-dir',str(HERE),'--confidence','MEDIUM','--repo','contextra-analytics',
 '--track-key','contextra-analytics:pipeline:runtime:gpu-first-inference','--closeout-job',job,
 '--evidence-state','runtime_validated','--confidentiality','internal',
 '--reason','송신 및 학습저장은 수용했지만 외부Web수용과 동등부하 전체성능 기준은 후속이며 기존renderer문제남음',
 '--verify','표적50/13/19/6PASS중복;복합1/8과24child회수;학습6실영상READY']))
out=Path('/tmp/codex/ltb1021/git-closeout')/job;out.mkdir(mode=0o700);mf=out/'manifest.json'
args=['codex-git-closeout','prepare','--repository',str(REPO),'--output',str(mf),'--job-id',job,
 '--profile','ltb1021','--session-alias','work-session','--title','feat(runtime): provider 송신 독립화와 조건부 학습 스냅샷 추가',
 '--message','Why=부모 공통송신에서 발생한 결과교체를 줄이고 실시간경로를 보호하며 조건부 학습자료를 저장한다.',
 '--message','What=child직접Frame송신/부모상태fencing,공유압축cache/학습worker와인증SettingsCAS/owned용량정리,Event별ROI를추가했다.',
 '--message','Impact=score만통과한person합집합8개/900초/1280x720/200GB/여유100GB가기본이다.BOff/batch8/instance8/Event10초/Redis300/전송계약은유지한다.외부Webroute는추가하지않았다.',
 '--message','Verify=Python3.12표적50+13+19+6PASS중복과4subtest.복합1/8시험,24child exit0/unforced/reap,학습6READY동일PTS,36stopped/WSS복구.8채널Redis40.87->99.27FPS이나완료량하락및비대상시작시각변경으로전체성능개선은미확정.',
 '--message','Rollback=이전image와전체privateCompose보존.정상Stop/reap뒤명시원복하며training자료나Event원장/원본은삭제하지않는다.이미자동정리된학습자료는복구불가다.아이서티/전체확장/Web수용은별도승인.',
 '--verify','Targeted Python3.12 50/13/19/6 PASS (overlap); 4 subtests; isolated Redis and codec fixture',
 '--verify','Composite1/8 measured; 6 exact-PTS training bundles; 24 children reaped; final model/config/image/WSS verified',
 '--expires-in-seconds','86400']
for p in paths:args+=['--path',p]
print(cmd(args));bp=out/'batch.json'
print(cmd(['codex-git-closeout','batch-prepare','--output',str(bp),'--batch-id',job,'--profile','ltb1021','--session-alias','work-session','--expected-group-count','1','--repository-label','contextra-analytics','--manifest',str(mf),'--expect-plan-hash',json.loads(mf.read_text())['plan_hash'],'--expires-in-seconds','86400']))
h=json.loads(bp.read_text())['plan_hash']
print(cmd(['codex-git-closeout','batch-plan','--batch-manifest',str(bp),'--expect-batch-plan-hash',h]))
print(cmd(['codex-git-closeout','batch-present','--batch-manifest',str(bp),'--expect-batch-plan-hash',h]))
