from pathlib import Path
import hashlib,json,os,subprocess,sys
from datetime import datetime
from zoneinfo import ZoneInfo
ROOT=Path(__file__).resolve().parents[1];REPO=Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
os.umask(0o077)
sys.path.insert(0,str(ROOT.parent/'2026-10-29_contextra-fight-b-refine_r01/helpers'))
import control_refine as c
remote=c.old.RB+'/'+ROOT.name
code="from pathlib import Path;import hashlib,json;p=Path("+repr(remote+"/download")+");print(json.dumps({str(x.relative_to(p)):hashlib.sha256(x.read_bytes()).hexdigest() for x in p.rglob('*') if x.is_file()}))"
hashes=json.loads(c.old.checked(['python3','-c',code]))
assert all(hashlib.sha256((ROOT/'results'/p).read_bytes()).hexdigest()==h for p,h in hashes.items())
paths=['app/det_result_path.py','app/fight_r5/runtime.py','app/fight_runtime.py','app/multi_provider_results.py','docs/integration/FIGHT_R5_WEB_CONFIG.ko.md','docs/profiles/ltb1021/topics/runtime-lifecycle/README.ko.md','docs/profiles/ltb1021/topics/runtime-lifecycle/run/20261101T150750+0900-run-fight-notification-integration.md','tests/test_fight_notification_runtime.py']
manifest=dict(created_at=datetime.now(ZoneInfo('Asia/Seoul')).isoformat(),result_sha256=hashes,source_sha256={p:hashlib.sha256((REPO/p).read_bytes()).hexdigest() for p in paths},deployed=False)
(ROOT/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
temp=Path('/tmp/codex/ltb1021/git-closeout/20261101-r5-notification');temp.mkdir(parents=True,exist_ok=True)
receipt=dict(schema_version='ltb1021.codex_hub.report_receipt.v1',receipt_id='20261101-r5-notification-integration',timestamp=manifest['created_at'],session_alias='work-session',task='R5 Event 재무장 및 지속 재알림 소스 통합',results=['2초 마지막hit 재무장, 현재hit의 기존cooldown 재알림, bounded 진단 연결. 미배포.',str(ROOT)],verify=['32입력18045표시관측 hit/display_orders 일치;27검사 PASS,DB fixture2개 skip.','전송 SHA256 대조;운영 컨테이너 유지·GPU시험0·운영제어0. GPU PID 변화는 관측.'],commits=[],remaining=['실제 PostgreSQL 동시성 및 브라우저 E2E 미실시.','camera24 실제 반영 별도 승인;camera6 제외.'])
(temp/'receipt.json').write_text(json.dumps(receipt,ensure_ascii=False)+'\n');(temp/'receipt.json').chmod(0o600)
p=subprocess.run(['codex-report-receipt','--profile','ltb1021','--session-alias','work-session','--receipt',str(temp/'receipt.json')],capture_output=True,text=True)
assert p.returncode==0,p.stderr
args=['codex-git-closeout','prepare','--repository',str(REPO),'--output',str(temp/'manifest.json'),'--job-id','20261101-r5-notification','--profile','ltb1021','--session-alias','work-session']
for path in paths:args+=['--path',path]
args+=['--title','fix(fight): 판정과 분리한 재무장 및 지속 재알림 연결','--message','Why=장기 episode 잠금으로 후속 Event가 발급되지 않는 경로를 제거한다.','--message','What=현재hit 기반 알림 상태와 기존cooldown을 연결하고 판정 및 표시 계산은 보존한다.','--message','Impact=소스 후보만 변경하며 운영 배포·채널제어·camera6 비교는 하지 않는다.','--message','Verify=32입력18045표시관측 불변,27검사 통과. DB fixture2개 및 현장E2E 미실시.','--message','Rollback=별도 승인 배포 전 이전image/source/settings 보존 및 camera24 우선 확인 절차를 따른다.','--verify','32입력 현재hit/display_orders 저장기준 일치','--verify','27 unit/mock PASS;2 DB fixture skip','--verify','git diff --check 및 전송 SHA256 PASS']
result=json.loads(subprocess.check_output(args,text=True))
batch=json.loads(subprocess.check_output(['codex-git-closeout','batch-prepare','--output',str(temp/'batch.json'),'--batch-id','20261101-r5-notification','--profile','ltb1021','--session-alias','work-session','--expected-group-count','1','--repository-label','Contextra','--manifest',str(temp/'manifest.json'),'--expect-plan-hash',result['plan_hash']],text=True))
print(subprocess.check_output(['codex-git-closeout','batch-present','--batch-manifest',str(temp/'batch.json'),'--expect-batch-plan-hash',batch['plan_hash']],text=True))
print('TRANSFER_HASH_PASS',len(hashes),'REPORT_RECEIPT_PASS')
