from probe import *
import yaml
import csv

plan=json.loads((R/'plan.json').read_text());trial=json.loads((R/'ai/live.json').read_text())
assert trial['retained_running']
current=prev.api(prev.P);send=prev.sender()
assert current['state']=='running' and current['run_id']==trial['run_id'] and not current['last_error']
assert current['metadata_path']['queued']==0 and not current['metadata_path']['performance']['detail']
assert send['executionId']==trial['sender_final']['executionId'] and send['anchor']['frame_time_anchor']==trial['settings']['frame_time_anchor']
analytics=ops.inspect('contextra-analytics-1');assert analytics['Id']==plan['analytics']['Id'] and analytics['Image']==plan['analytics']['Image']
assert analytics['State']['Health']['Status']=='healthy'
assert analytics['HostConfig']['RestartPolicy']==dict(Name='on-failure',MaximumRetryCount=3)
compose=yaml.safe_load((ops.C/'compose.yaml').read_text());assert compose['services']['analytics']['restart']=='on-failure:3'
for path,digest in plan['config_hashes'].items():
    if path!=str(ops.C/'compose.yaml'):assert ops.g.sha(Path(path))==digest
assert ops.inspect('contextra-redis-1')['Id']==plan['redis']['Id']
for name,info in plan['others'].items():
    if name=='/contextra-analytics-1' or name==plan['sender_container']['Name']:continue
    assert ops.inventory()[name]==info
for root in [R/'diag-registry',R/'fixed/diag-registry',R/'once-registry']:assert not Manager(root).containers()
assert len(Manager().containers())==1
key=json.loads((R/'ai/plan.json').read_text())['key']
samples=prev.query('reader','XREVRANGE',key,'+','-','COUNT','2');assert len(samples)==2
for _,f in samples:
    if isinstance(f,list):f=dict(zip(f[::2],f[1::2]))
    value=json.loads(f['json']);assert value['runId']==trial['run_id'] and value['frameTime']['syncStatus']=='ESTIMATED'
save('final-frames.json',samples)
pid=analytics['State']['Pid'];cg=Path('/proc/'+str(pid)+'/cgroup').read_text().strip().split('::')[1];base=Path('/sys/fs/cgroup'+cg)
memory={k:int((base/k).read_text()) for k in ['memory.current','memory.peak']}
assert memory['memory.peak']<12*1024**3
metrics=trial['after']['metadata_path'];counts=metrics['performance']['counts']
summary=dict(web_redis_ready=True,root_cause='Shared FFmpeg pipe advertised seekable. Late PLAY seek failed and poisoned media; original sender DESCRIBE503, exact original triggering client not known.',fix='GstBase.BaseSrc.set_live + truthful nonseekable SEEKING query; no protocol implementation or SDK patch',source_tests=6,non_ai_before='OPTIONS200/DESCRIBE503',isolated_before='PLAY503 seek failure',isolated_after='35 decoded/0 errors;20 output',production_after='OPTIONS/DESCRIBE/SETUP/PLAY200;35 decoded/0 errors;20 output',once='natural EOF/inactive Anchor null/repeated Stop PASS',result_fps=trial['result_fps'],redis_fps=trial['redis_observed_fps'],measurement_seconds=trial['elapsed_seconds'],frames_checked=trial['redis_frames_checked'],stored_frames=trial['redis_samples_stored'],redis_length=trial['redis_length_range'],ttl_ms=trial['ttl_ms_range'],queue=0,queue_drop_observed=metrics.get('queue_drop',0),publisher_rejected_observed=metrics.get('publisher_rejected',0),publisher_send_error=trial['publisher']['metrics'].get('send_error',0),rtp='226/226 Frame clock90000/Anchor arithmetic ESTIMATED',events_in_observation=trial['events_observed'],events_runtime_counter=metrics.get('events_created'),artifact='NOT_SUBMITTED/URI null',durations=trial['metrics']['durations'],memory_cgroup=memory,memory_note='Prior per-PID sample measured entrypoint, not full Analytics; use cgroup current/peak here',ai_state=current['state'],sender_state=send['state'],restart='on-failure:3 actual/persistent',ai_start=1,ai_stop=0,sender_restart=1,image_build=0,analytics_image_reused=True,temporary_senders_receivers=0,source_cleanup_error_path='Deferred: original stop could block at transition lock or SDK pipeline.stop; no precise marker to distinguish. No blind lifecycle patch.',current_sdk_stop_test='NOT_RUN; keep running approved',external_web='PENDING',windows='NOT_TESTED',capacity_errors=0)
save('summary.json',summary)
with (R/'summary.csv').open('w') as f:
    w=csv.writer(f);w.writerow(['item','value','scope'])
    for k in ['web_redis_ready','result_fps','redis_fps','measurement_seconds','frames_checked','queue','queue_drop_observed','events_in_observation','ai_start','ai_stop','image_build']:
        w.writerow([k,summary[k],'bounded internal trial'])
with (R/'latency.csv').open('w') as f:
    w=csv.writer(f);w.writerow(['stage','mean_ms','p95_ms','samples','scope'])
    for k,v in summary['durations'].items():w.writerow([k,v['mean_ms'],v['p95_ms'],v['samples'],'sequential boundary' if '_to_' in k else 'nested interval; do not add to boundary totals'])
save('report-receipt.json',dict(schema_version='ltb1021.codex_hub.report_receipt.v1',receipt_id='contextra-rtsp503-20260915-114800',timestamp='2026-09-15T11:48:00+09:00',session_alias='work-session',task='Contextra RTSP shared pipe seek 오류 교정과 Web Redis 시험 실행 유지',results=['비추론 수신으로 DESCRIBE503 및 격리 sender PLAY seek 실패 판별','public live/nonseekable 선언으로 source 최소 교정;동일 승인 영상 및 대표 URI 유지','새 sender Anchor CAS 적용 후 기존 Analytics image로 단일 INTRUSION 시험 통과','AI running과 sender continuous 유지;정상 restart 정책 복원'],verify=['worker6 tests,전후 비추론20 Frame 출력/디코딩,once EOF/정리 PASS','결과/Redis 약10.03FPS;226 Frame RTP/Anchor 정합;최근30개/TTL갱신/queue0','상태6 Event 표본은 NOT_SUBMITTED/Artifact URI null','임시 sender/receiver 회수;비대상 보존;image build0/AI Start1/Stop0'],commits=[],remaining=['503 실패 시 SDK Stop 대기 경계는 정확한 내부 marker 부족으로 별도 후속','실제 Web 수신/파싱/영상 동기화 및 Windows 재생은 사용자 확인 대기','사용자 시험 종료 시 대상 Stop 필요;현재 정상 실행 유지 중']))
for p in R.iterdir():
    if p.is_file():p.chmod(0o600)
print(json.dumps({k:summary[k] for k in ['web_redis_ready','result_fps','redis_fps','queue','ai_state','sender_state','restart','image_build','temporary_senders_receivers']},ensure_ascii=False))
