#!/usr/bin/env python3
from pathlib import Path
import datetime,hashlib,json,os
ROOT=Path('/data/video-pipeline/HaTramAudio/project/029-Chiec-O-De-Lai-O-Tram-Xe-Buyt-Cuoi');PID=ROOT.name;RUN='run-20260722T162951Z-87a939ad';OWNER='Levy'
def now():return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def guard():
 d=json.loads((ROOT/'.ownership-lock.json').read_text())
 for k,v in {'project_id':PID,'run_id':RUN,'owner':OWNER}.items():
  if d.get(k)!=v:raise RuntimeError('ownership mismatch '+k)
def atomic_text(p,s):
 guard();p.parent.mkdir(parents=True,exist_ok=True);t=p.with_name('.'+p.name+'.tmp');t.write_text(s);os.replace(t,p)
def atomic_json(p,d):atomic_text(p,json.dumps(d,ensure_ascii=False,indent=2)+'\n')
def main():
 guard();m=json.loads((ROOT/'script/project-manifest.json').read_text());canon=m.get('canon_sha256')
 story=m.get('gate_events',{}).get('story',{})
 if m.get('steps',{}).get('story')!='completed' or story.get('verified') is not True or story.get('canon_sha256')!=canon:raise RuntimeError('Story Gate is not committed/current')
 promo=json.loads((ROOT/'script/promotion-report.json').read_text());brief=ROOT/'script/story-brief.json';brief_data=json.loads(brief.read_text())
 if not promo.get('verified') or promo.get('canon_sha256')!=canon or sha(ROOT/'story/story-canon.txt')!=canon:raise RuntimeError('canon promotion invalid')
 if brief_data.get('project_id')!=PID or brief_data.get('run_id')!=RUN:raise RuntimeError('story brief identity mismatch')
 title=brief_data['canonical_title']
 family=brief_data['story_family'];genres=[family['primary'],family['secondary']]
 summary='Hạ Vãn Tình, biên tập thông báo tiếp cận cho tuyến xe buýt đêm N7, cùng Cố Minh Dã, họa sĩ thiết kế bản đồ nổi, xây lại hệ thống bằng tiếng nói của chính hành khách. Một chiếc ô vàng ở trạm cuối trở thành dấu hiệu cho mối quan tâm thầm lặng; nhưng để dự án sống được, họ phải trao micro, quyền thiết kế và quyền dừng cho cộng đồng sử dụng tuyến xe.'
 yt=f'Truyện Audio | {title} (Full) | Hạ Trâm Audio'
 if not 2<=len(yt)<=100:raise RuntimeError('youtube title length invalid')
 info=f'{yt}\n\n{summary}\n\nThể loại: '+', '.join(genres)+'\n\nTruyện được phát độc quyền tại Hạ Trâm Audio, nghiêm cấm sao chép dưới mọi hình thức\n'
 ip=ROOT/'output/info.txt';atomic_text(ip,info)
 metadata={'schema_version':1,'source_project_id':PID,'project_id':PID,'run_id':RUN,'status':'completed','verified':True,'source_canon_sha256':canon,'source_story_brief_sha256':sha(brief),'canonical_title':title,'youtube_title':yt,'title_length':len(yt),'description':summary,'summary':summary,'genres':genres,'tags':[],'thumbnail_text':title,'copyright_line':'Truyện được phát độc quyền tại Hạ Trâm Audio, nghiêm cấm sao chép dưới mọi hình thức','authority_sha256':{'story_canon':canon,'story_brief':sha(brief)},'contains_spoiler_reveal':False}
 mp=ROOT/'script/metadata.json';atomic_json(mp,metadata);receipt={**metadata,'artifact_path':str(ip),'artifact_sha256':sha(ip),'metadata_json_path':str(mp),'metadata_json_sha256':sha(mp),'completed_at':now()};atomic_json(ROOT/'log/metadata.json',receipt);print(json.dumps({'status':'completed','verified':True,'youtube_title':yt,'artifact_sha256':receipt['artifact_sha256']},ensure_ascii=False))
if __name__=='__main__':main()
