from ops import *
from datetime import datetime

images=json.loads((HERE/'images-6355edabd6.json').read_text())
assert all(hashlib.sha256((REPO/p).read_bytes()).hexdigest()==v for p,v in images['hashes'].items())
cmd(['git','-C',str(REPO),'diff','--check'])
assert not cmd(['git','-C',str(REPO),'diff','--cached','--name-only']).strip()
rows=api('/api/v1/streams')['streams']
assert all(r['state']=='stopped' and not r.get('child_pid') and not r.get('cleanup_pending') and
    not any(v.get('child_pid') for v in r.get('provider_processes',{}).values()) for r in rows)
assert not inspect('contextra-ai-gateway-1')['State']['Running']
for key,name in [('analytics','contextra-analytics-1'),('sidecar','contextra-ai-control-sidecar-1')]:
    assert inspect(name)['Image']==images['images'][key]['id']
    assert inspect(name)['State']['Running']
baseline=json.loads((HERE/'baseline.json').read_text())
non_target=[]
for name,old in baseline['containers'].items():
    if name in ('contextra-analytics-1','contextra-ai-control-sidecar-1','contextra-ai-gateway-1'):
        continue
    current=inspect(name)
    if current['Id']!=old['id'] or current['Image']!=old['image'] or current['State']['StartedAt']!=old['started']:
        non_target.append(name)
save('closeout-verification.json',dict(source_image_match=True,diff_check='PASS',staged=[],
    states={'stopped':len(rows)},children=0,gateway='stopped_by_user_after_repeated_start',
    non_target_drift=non_target,head=cmd(['git','-C',str(REPO),'rev-parse','HEAD']).strip(),
    tests=[dict(result='171 passed / 86 subtests',scope='crowd events/config/temporal/output, cooldown, linear, PR13'),
           dict(result='189 passed / 2 old FIGHT fixture failures',scope='bridge/start/artifact/process/det-result'),
           dict(result='74 passed',scope='secure bridge after current-contract fixture correction')],
    fixture_failures_resolved=True,python='3.12.3 existing image',test_resource_limit=dict(cpu=2,ram_gib=2,network=False),
    no_full_regression=True,web_acceptance=False,icerti_deployed=False,
    media_verification='artifact-media-verification.json',measurement='summary-v2.json'))
receipt=dict(schema_version='ltb1021.codex_hub.report_receipt.v1',receipt_id='20260928-contextra-crowd-events-r01',
    timestamp=datetime.now().astimezone().isoformat(),session_alias='work-session',
    task='Contextra 군중 상태/Event/heatmap 및 6종 cooldown test 구현',
    results=['필수5개 atomic 검증/Crowd만 Off/Medium fallback, DANGER episode와 gap 보호 구현',
             '복합2채널30초 Redis9.60~10.07FPS, Crowd2건READY/SENT와 전체decode/PTS확인',
             '원래시험설정 복귀, 외부Start재유입에 사용자승인Gateway중지/36stopped/child0'],
    verify=['Python3.12 표적171PASS/86subtests, 추가189PASS 후 기존fixture2건 수정영향74PASS(중복포함)',
            '2개image/source hash일치, HTML6JSON/offline/desktop/mobile/print PASS, 비대상변경없음'],
    commits=[],remaining=['test Web수용 확인','아이서티추가배포와현장확인','Gateway재개전Web희망run상태정리'])
save('report-receipt.json',receipt)
paths=cmd(['git','-C',str(REPO),'diff','--name-only']).splitlines()+cmd(['git','-C',str(REPO),'ls-files','--others','--exclude-standard']).splitlines()
assert all(p.startswith(('app/','control_sidecar/','tests/','docs/')) for p in paths)
args=['/root/.local/bin/codex-git-closeout','prepare','--repository',str(REPO),
    '--output',str(HERE/'git-manifest.json'),'--job-id','20260928-contextra-crowd-events-r01',
    '--profile','ltb1021','--session-alias','work-session',
    '--title','feat(crowd): 군중 위험 상태와 Event heatmap 및 시나리오 cooldown 연동',
    '--message','Why=군중 ROI 위험도를 Web 설정으로 판정하고 정확한 Frame의 Event 미디어를 전달해야 한다.',
    '--message','What=Crowd 필수값 fail-soft, 상태 집계와 DANGER episode, 6종 cooldown, 압축 cache와 heatmap을 연결했다.',
    '--message','Impact=test analytics/sidecar와 Crowd 출력만 확장한다. 기존 provider 계약과 전송/회수 보호를 유지한다.',
    '--message','Verify=Python3.12 표적검증, 복합2채널30초, Crowd2건 READY/SENT 및 clip 전체decode/PTS, HTML offline 검증 PASS. Web 수용과 현장 배포는 보류.',
    '--message','Rollback=승인 시 보존한 이전 image와 Compose/env로 복구한다. 자동 rollback은 하지 않았고 기존 원장/미디어는 보존한다.',
    '--verify','git diff --check PASS; source-image hash PASS; Python3.12 targeted PASS; real Crowd2 READY/SENT; six trial children reaped; final36 stopped/Gateway held',
    '--expires-in-seconds','604800']
for path in sorted(set(paths)):
    args += ['--path',path]
output=cmd(args,timeout=45)
print(output)
manifest=json.loads((HERE/'git-manifest.json').read_text())
print('MANIFEST_KEYS',list(manifest))
