import pathlib,subprocess,json,hashlib,tarfile,gzip,stat,shutil,os,time,io R=pathlib.Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');BASE=pathlib.Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01');E=R/'docs/plans/original-completion-2026-10-02/execution/drive-offload-completed-checkouts-r14-20261016';TEMP=BASE/'.runtime/offload-validation-r14-20261016';CANDIDATES=json.loads(pathlib.Path('/private/tmp/p04-completed-checkouts-r14-candidates.json').read_text()) def git(root,*args):return subprocess.check_output(['git','-C',str(root),*args],text=True).strip() def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest() assert not E.exists() and not TEMP.exists() initial=shutil.disk_usage(R).free;started=time.time();roots=[];unique={};total=0 for root_text in CANDIDATES['roots']: root=pathlib.Path(root_text);name=root.name;assert root.is_dir() and not root.is_symlink();assert not git(root,'status','--porcelain','--untracked-files=all');assert not git(root,'clean','-ndx'),'ignored/untracked content must be classified first' ps=subprocess.run(['/usr/sbin/lsof','-t','+D',str(root)],capture_output=True,text=True);assert ps.returncode==1 and not ps.stdout.strip(),('open files',name,ps.stdout) files={};dirs={} for p in sorted(root.rglob('*')): rel=p.relative_to(root).as_posix();st=p.lstat();assert not stat.S_ISLNK(st.st_mode),str(p) if rel=='.git':continue if p.is_dir():dirs[rel]={'mode':stat.S_IMODE(st.st_mode),'mtime_ns':st.st_mtime_ns};continue assert p.is_file(),str(p);v=p.read_bytes();h=hashlib.sha256(v).hexdigest();assert len(v)==st.st_size files[rel]={'bytes':len(v),'sha256':h,'mode':stat.S_IMODE(st.st_mode),'mtime_ns':st.st_mtime_ns};total+=len(v) unique.setdefault(h,{'source':str(p),'bytes':len(v)}) roots.append({'path':str(root),'name':name,'head':git(root,'rev-parse','HEAD'),'git_pointer':(root/'.git').read_text(),'files':files,'directories':dirs}) unique_bytes=sum(v['bytes'] for v in unique.values());assert shutil.disk_usage(R).free>=1024**3+unique_bytes+64*1024**2 E.mkdir(parents=True);TEMP.mkdir(parents=True) (E/'CANDIDATES.json').write_text(json.dumps(CANDIDATES,ensure_ascii=False,indent=2)+'\n') manifest={'protocol':'p04-ended-validation-cas-v1','production_repo':str(R),'scope':'Only 34 ended clean validation/review/integration/final checkouts from completed original units P08-02/P08-03/P24-03; current implementation/build/native raw/cache are excluded. All file bytes and metadata retained; .git administrative pointers are recorded for normal git worktree restoration.','roots':roots,'files':sum(len(v['files']) for v in roots),'logical_bytes':total,'unique_content_bytes':unique_bytes,'unique_blobs':len(unique)} raw=json.dumps(manifest,ensure_ascii=False,sort_keys=True,separators=(',',':')).encode();mp=E/'MANIFEST.json.gz';mp.write_bytes(gzip.compress(raw,compresslevel=9,mtime=0)) archive=TEMP/'completed-checkouts-r14.cas.tar.gz' with tarfile.open(archive,'w:gz',compresslevel=6) as tar: ti=tarfile.TarInfo('manifest.json');ti.size=len(raw);ti.mtime=0;ti.mode=0o644;tar.addfile(ti,io.BytesIO(raw)) for h,v in sorted(unique.items()): p=pathlib.Path(v['source']);assert sha(p)==h;ti=tarfile.TarInfo('blobs/'+h);ti.size=v['bytes'];ti.mtime=0;ti.mode=0o644 with p.open('rb') as f:tar.addfile(ti,f) with tarfile.open(archive,'r:gz') as tar: seen=set();m=tar.extractfile('manifest.json').read();assert m==raw for member in tar: if member.name=='manifest.json':continue assert member.name.startswith('blobs/');h=member.name.split('/')[1];v=tar.extractfile(member).read();assert h in unique and len(v)==unique[h]['bytes'] and hashlib.sha256(v).hexdigest()==h;seen.add(h) assert seen==set(unique) parts=[];limit=16*1024**2 if archive.stat().st_size>limit: with archive.open('rb') as f: i=0 while block:=f.read(limit): p=TEMP/(archive.name+'.part%03d'%i);p.write_bytes(block);parts.append({'path':str(p),'bytes':len(block),'sha256':hashlib.sha256(block).hexdigest()});i+=1 else:parts=[{'path':str(archive),'bytes':archive.stat().st_size,'sha256':sha(archive)}] prep={'source_initial_free_bytes':initial,'source_logical_bytes':total,'unique_content_bytes':unique_bytes,'file_count':manifest['files'],'roots':[{k:v[k] for k in ['name','path','head']} for v in roots],'archive':{'path':str(archive),'bytes':archive.stat().st_size,'sha256':sha(archive)},'manifest':{'path':str(mp),'bytes':mp.stat().st_size,'sha256':sha(mp)},'upload_parts':parts,'local_tar_all_blobs_roundtrip':True,'prepared_seconds':time.time()-started,'free_after_preparation':shutil.disk_usage(R).free,'source_removed':False,'retirement_requires_cloud_full_readback_and_source_recheck':True} (E/'PREPARATION.json').write_text(json.dumps(prep,ensure_ascii=False,indent=2)+'\n');(E/'prepare.py').write_bytes(pathlib.Path(__file__).read_bytes());print(json.dumps({k:prep[k] for k in ['source_logical_bytes','unique_content_bytes','file_count','archive','manifest','upload_parts','prepared_seconds','free_after_preparation']},ensure_ascii=False),flush=True)