import json
from pathlib import Path
import subprocess
from datetime import datetime
import sys

from process_ops import R, REPO, p

DOCS=Path('/workspace/users/root/codex-ops-docs')
JOB='20260916-channel-process'
OWNER='docs/profiles/ltb1021/topics/runtime-lifecycle/'
RUN=OWNER+'runs/20260916T155256+0900-run-channel-process.md'
DECISION=OWNER+'decision/20260916T155256+0900-decision-channel-process.md'


def command(argv,label):
    result=subprocess.run(argv,capture_output=True,text=True,timeout=60)
    p.save(label+'.json',dict(argv=argv,returncode=result.returncode,stdout=result.stdout,stderr=result.stderr))
    if result.returncode:
        raise RuntimeError(label+': '+result.stderr[:300]+result.stdout[:300])
    print(label,result.stdout[:1600],flush=True)
    return result.stdout


def reports():
    assert p.read('final.json')['success'] and p.read('readback.json')['success']
    assert not subprocess.check_output(['git','diff','--name-only'],cwd=DOCS,text=True).strip()
    append=DOCS/'servers/test/users/ltb1021/portfolio_logs/scripts/append_portfolio_log.py'
    args=['python3',str(append),'--repo','contextra-analytics',
        '--track-key','contextra-analytics:pipeline:runtime:runtime-lifecycle',
        '--log-type','milestone','--change-type','refactor',
        '--title','채널별 프로세스 격리와 collector 포화 이후 처리량 수용',
        '--before','동일 프로세스의 세 채널에서 포화 이후 결과 약7FPS와 stale 증가가 관측됐다.',
        '--change','API 제어는 부모에 두고 SDK부터 결과 처리와 Metadata 게시까지 spawn 자식으로 분리했다. Redis transaction fence와 실제 회수를 연결했다.',
        '--after','필수45개 테스트와 세 채널 포화 이후31.54초 관측에서 결과와 게시가 각각 약10FPS였다. 정상 Stop과 새 프로세스 실행을 확인했다.',
        '--portfolio-angle','공유 서비스는 유지하면서 실행 소유권과 쓰기 수명을 분리하고 실제 제한 관측으로 수용 범위를 검증했다.',
        '--evidence-state','runtime_validated','--confidentiality','internal','--closeout-job',JOB,
        '--tradeoff','독립 프로세스의 메모리 상주 비용은 늘었으며 실제16채널과 Tracking 포함 성능은 미검증이다.',
        '--evidence-ref',RUN,'--evidence-ref',DECISION,
        '--file','app/channel_process.py','--file','app/channel_child.py','--file','app/process_metadata.py',
        '--file','tests/test_channel_process.py','--file','tests/test_process_metadata.py',
        '--tag','process-isolation','--tag','lifecycle','--skill','multiprocessing','--skill','Redis transactions',
        '--metrics',json.dumps(dict(targeted_tests=45,verified_runtime_channels=3,saturation_seconds=31.54,
            saturated_result_fps=[10.018,10.018,9.955],saturated_redis_fps=[10.018,10.022,9.989],
            saturated_stale_delta=0,saturated_publisher_replaced_delta=0))]
    command(args+['--dry-run'],'portfolio-dry-run')
    command(args,'portfolio-append')
    p.save('report-receipt.json',dict(schema_version='ltb1021.codex_hub.report_receipt.v1',
        receipt_id=JOB+'-r01',timestamp=datetime.now().astimezone().isoformat(),session_alias='work-session',
        task='Contextra 채널별 process 격리와 포화 이후 Medium3 수용',
        results=['API 제어 부모와 SDK·결과·Metadata 직접전송 자식을 분리하고 최대16개 수명과 Redis 쓰기 인계를 구현했다.',
            '승인된 publisher GET/WATCH/UNWATCH만 actual/persistent에 반영했다. reader·키범위·인증·허용 IP는 보존했다.',
            '실제3채널의 collector1000개 이후31.54초 결과9.96~10.02FPS, Redis9.99~10.02FPS와 queue/stale/drop0을 확인했다.',
            '정상 child 회수와 새 PID 재기동 뒤 새 image/process 설정 및 기존3채널 running을 유지했다. Owner·decision/run evidence를 작성했다.'],
        verify=['source/image 필수45tests PASS: spawn16/17번째 거부, CAS, 정상·강제 회수, 부모사망, Redis WATCH 경합과 stale writer 차단.',
            '실제 child 소유권/SDK 로드/image hash, 진단Off, helper종료, sender·Redis·모델·등록설정 보존 확인.'],
        commits=[],remaining=['실제16채널·Tracking/Scenario On·Pose·장시간과 Web 동기화는 후속이다.',
            '근본 GIL/lock 원인은 확정하지 않으며 이번 관측에서 저하 미재발 범위로 수용한다.']))
    command(['codex-report-receipt','--profile','ltb1021','--session-alias','work-session',
        '--receipt',str(R/'report-receipt.json')],'report-result')


def closeout():
    output=Path('/tmp/codex/ltb1021/git-closeout')/JOB
    source_paths=['app/api/streams.py','app/main.py','app/metadata_publisher.py','app/metadata_runtime.py',
        'app/stream_lifecycle.py','app/stream_manager.py','docker/entrypoint.sh','tests/test_det_result_path.py',
        'app/channel_child.py','app/channel_process.py','app/process_metadata.py','tests/test_channel_process.py',
        'tests/test_process_metadata.py',OWNER+'README.ko.md',RUN,DECISION]
    portfolio='servers/test/users/ltb1021/portfolio_logs/'
    docs_paths=[portfolio+'catalogs/track_index.jsonl',
        portfolio+'entries/contextra-analytics/pipeline__runtime__runtime-lifecycle/2026/2026-09.jsonl',
        portfolio+'entries/contextra-analytics/pipeline__runtime__runtime-lifecycle/track.json']
    groups=[(REPO,source_paths,'Contextra',JOB,
        'refactor(runtime): 채널별 프로세스와 Redis 쓰기 인계로 Medium3 포화 성능 확보',
        ['Why: 같은 프로세스의 다채널 결과 저하에 대해 추가 원인 탐색 대신 승인된 실행 격리 경로를 적용한다.',
         'What: spawn 자식이 SDK부터 Metadata 직접 전송까지 소유하고 부모는 CAS·실행 관리와 inactive 상태만 담당한다. Redis WATCH fence·bounded IPC·실제 회수와 지연 응답 차단을 연결한다.',
         'Impact: 기존 API·CLI·Metadata 스키마와 공유 Triton을 유지한다. test actual/persistent process 모드와 승인 publisher 명령3개를 반영했다. 상세collector/aggregate timeline 미지원과 메모리 증가를 명시했다.',
         'Verify: source/image 필수45tests PASS. 실제3채널 collector1000개 이후31.54초 결과9.96~10.02·Redis9.99~10.02FPS, queue/stale/drop0. 정상 child Stop/reap 및 새 PID/run 확인. 진단Off·helper회수·설정/입력/모델 보존.',
         'Rollback: 대상 child 정상 Stop/reap 후 private 원장의 기존 image와 active Compose/runtime.env를 CAS 복원한다. 이전 image/thread 경로를 보존하며16채널·Tracking/Pose·장시간 성능은 이번 수용 범위가 아니다.'],
        ['45 targeted tests on source and exact image PASS','Medium3 saturation window 31.54s accepted; actual Stop/reap/new run PASS','git diff --check PASS']),
        (DOCS,docs_paths,'Codex Docs',JOB+'-portfolio',
        'docs(portfolio): 채널 프로세스 격리의 제한 Runtime 수용 기록',
        ['Why: 검증된 실행 격리 결과와 수용 한계를 안정적인 milestone evidence로 연결한다.',
         'What: runtime-lifecycle Track에 process 소유권·Redis fence·필수검증·포화 이후3채널 결과를 append했다.',
         'Impact: 기술 계약은 Contextra decision/run이 소유한다. raw URI·인증·runtime 식별값을 성과 기록에 복제하지 않는다.',
         'Verify: Portfolio dry-run/security 검증 및 append PASS. source Closeout-Job·상대 evidence와 최종 변경 집합을 대조했다.',
         'Rollback: milestone append와 해당 track/catalog 변경만 후속 수정으로 정리하며 historical entry를 다시 쓰지 않는다.'],
        ['Portfolio dry-run and safe append PASS','Source/evidence scope verified; git diff --check PASS'])]
    manifests=[]
    for repo,paths,label,job,title,messages,verify in groups:
        assert not subprocess.check_output(['git','diff','--cached','--name-only'],cwd=repo,text=True).strip()
        changed=set(subprocess.check_output(['git','diff','--name-only'],cwd=repo,text=True).splitlines())
        changed.update(subprocess.check_output(['git','ls-files','--others','--exclude-standard'],cwd=repo,text=True).splitlines())
        assert changed==set(paths),(label,changed.symmetric_difference(paths))
        manifest=output/(job+'.json')
        args=['codex-git-closeout','prepare','--repository',str(repo),'--output',str(manifest),'--job-id',job,
            '--profile','ltb1021','--session-alias','work-session','--title',title]
        for path in paths:args+=['--path',path]
        for message in messages:args+=['--message',message.replace(': ','=',1)]
        for check in verify:args+=['--verify',check]
        command(args,job+'-prepare')
        data=json.loads(manifest.read_text())
        manifests.append((label,manifest,data['plan_hash']))
    batch=output/'batch.json'
    args=['codex-git-closeout','batch-prepare','--output',str(batch),'--batch-id',JOB+'-batch',
        '--profile','ltb1021','--session-alias','work-session','--expected-group-count','2']
    for label,manifest,digest in manifests:
        args+=['--repository-label',label,'--manifest',str(manifest),'--expect-plan-hash',digest]
    command(args,'batch-prepare')
    command(['codex-git-closeout','batch-plan','--batch-manifest',str(batch)],'batch-plan')
    command(['codex-git-closeout','batch-present','--batch-manifest',str(batch)],'batch-present')
    p.save('closeout-stage.json',dict(at=datetime.now().astimezone().isoformat(),stage='complete',
        reports_written=True,portfolio='HIGH',runtime='retained-process3',git_mutations=False,
        non_repository_paths=[str(p.ACTIVE),str(p.ENV),'/workspace/contextra-runtime-config/metadata-active/users.acl']))


if __name__=='__main__':
    closeout() if len(sys.argv)>1 and sys.argv[1]=='closeout' else reports()
