Files

50 lines
6.2 KiB
Python

import pathlib,json,gzip,hashlib,subprocess,stat,shutil,sqlite3,datetime,time
R=pathlib.Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');E=R/'docs/plans/original-completion-2026-10-02/execution/drive-offload-completed-checkouts-r14-20261016';prep=json.loads((E/'PREPARATION.json').read_text());m=json.loads(gzip.decompress((E/'MANIFEST.json.gz').read_bytes()));cloud=json.loads((E/'CLOUD_ALL_BYTES_VERIFIED.json').read_text());assert cloud['all_eight_cloud_files_full_readback_verified'] and cloud['whole_archive_sha256']==prep['archive']['sha256']
C=sqlite3.connect('file:/Users/korizatou114/Library/Application Support/agent-control/the-tree-p04-01/state.sqlite?mode=ro',uri=True);running=C.execute("SELECT id,task_id,role,pid FROM runs WHERE status='running'").fetchall()
# Only this separately scoped implementation may run; none of the five ended
# validation paths is an input to it. Recheck each target with lsof below.
for run_id,task_id,role,pid in running:
assert run_id=='24d43100b6dc47d784ed6bac550c501a' and task_id=='P04-01' and role=='implementer'
assert C.execute('SELECT worktree FROM tasks WHERE id=?',(task_id,)).fetchone()[0]=='/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement'
subprocess.run(['/bin/kill','-0',str(pid)],check=True,capture_output=True)
cwd=subprocess.check_output(['/usr/sbin/lsof','-a','-p',str(pid),'-d','cwd','-Fn'],text=True)
assert '\nn/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement\n' in cwd
pg=subprocess.run(['/usr/bin/pgrep','-fl','/Applications/Godot.app|Godot.*headless'],capture_output=True,text=True);assert pg.returncode==1 and not pg.stdout.strip(),'wait for all owned engines'
def git(root,*args):return subprocess.check_output(['git','-C',str(root),*args],text=True).strip()
candidates=json.loads((E/'CANDIDATES.json').read_text())
wp=json.loads((R/'docs/plans/original-completion-2026-10-02/work_packages.json').read_text())
states={u['id']:u['state'] for p in wp for u in p['remaining_deliverables']}
for project in candidates['Runner_projects']:
assert states[project['original_unit']]=='completed'
db=pathlib.Path('/Users/korizatou114/Library/Application Support/agent-control')/project['project']/'state.sqlite'
connection=sqlite3.connect('file:'+str(db)+'?mode=ro',uri=True)
assert not connection.execute("SELECT 1 FROM runs WHERE status='running'").fetchone()
checks=[]
for row in m['roots']:
root=pathlib.Path(row['path']);assert str(root) in candidates['roots'] and root.parent.parent==pathlib.Path('/Users/korizatou114/Projects/TheTree-managed-worktrees') and root.parent.name in {'p08-02','p08-03-code','p24-03-code'} and root.name==row['name'] and not root.name.endswith('-implement')
assert git(root,'rev-parse','HEAD')==row['head'];assert not git(root,'status','--porcelain','--untracked-files=all');assert not git(root,'clean','-ndx')
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 source validation files'
actual={p.relative_to(root).as_posix() for p in root.rglob('*') if p.is_file() and p.relative_to(root).as_posix()!='.git'};assert actual==set(row['files'])
for name,v in row['files'].items():
p=root/name;s=p.stat();assert not p.is_symlink() and s.st_size==v['bytes'] and stat.S_IMODE(s.st_mode)==v['mode'] and s.st_mtime_ns==v['mtime_ns'];assert hashlib.sha256(p.read_bytes()).hexdigest()==v['sha256']
checks.append({'path':str(root),'head':row['head'],'all_files_rehashed':len(actual),'clean':True,'open_files':False})
(E/'SOURCE_RECHECK.json').write_text(json.dumps({'checks':checks,'cloud_full_bytes_verified':True,'all_sources_retained_at_check':True},ensure_ascii=False,indent=2)+'\n')
before=shutil.disk_usage(R).free;started=time.time();removed=[]
for row in m['roots']:
root=pathlib.Path(row['path']);assert git(root,'rev-parse','HEAD')==row['head'];subprocess.run(['git','-C',str(R),'worktree','remove',str(root)],check=True,capture_output=True);assert not root.exists();removed.append({'path':str(root),'head':row['head']})
after_sources=shutil.disk_usage(R).free
T=pathlib.Path(prep['archive']['path']).parent;gitmeta=json.loads((E/'GIT_RESTORE.json').read_text());kit=json.loads((E/'RESTORE_KIT.json').read_text());expected={prep['archive']['path']:prep['archive']['sha256'],kit['path']:kit['sha256'],gitmeta['archive']['path']:gitmeta['archive']['sha256']}
for part in prep['upload_parts']:expected[part['path']]=part['sha256']
for v in gitmeta['commits']:
if v.get('file'):expected[str(T/'git-bundles'/v['file'])]=v['sha256']
actual={str(p) for p in T.rglob('*') if p.is_file()};assert actual==set(expected),'unknown temporary data must not be removed'
temporary_bytes=0
for name,h in expected.items():
p=pathlib.Path(name);assert p.is_relative_to(T) and not p.is_symlink() and hashlib.sha256(p.read_bytes()).hexdigest()==h;temporary_bytes+=p.stat().st_size
for name in expected:pathlib.Path(name).unlink()
for p in sorted([p for p in T.rglob('*') if p.is_dir()],key=lambda p:len(p.parts),reverse=True):p.rmdir()
T.rmdir();after_all=shutil.disk_usage(R).free
report={'removed_ended_completed_unit_checkouts':removed,'source_file_count':m['files'],'source_logical_bytes':m['logical_bytes'],'cloud_all_bytes_verified':True,'source_files_all_rehashed_before_normal_git_worktree_remove':True,'before_sources_free_bytes':before,'after_sources_free_bytes':after_sources,'observed_source_retirement_free_delta_bytes':after_sources-before,'temporary_pack_logical_bytes_removed':temporary_bytes,'after_own_temporary_cleanup_free_bytes':after_all,'own_temporary_cleanup_observed_delta_bytes':after_all-after_sources,'measurement_note':'Source retirement interval excludes cleanup of this run own archive/parts/bundles/kit. Free-space deltas are observed intervals and may include concurrent OS activity, not logical byte totals. Current implementation/build/native raw/cache/default saves are retained.','concurrent_isolated_implementation':running,'elapsed_seconds':time.time()-started,'recorded_at':datetime.datetime.now(datetime.timezone.utc).isoformat()}
(E/'RETIREMENT.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');(E/'retire.py').write_bytes(pathlib.Path(__file__).read_bytes());print(json.dumps(report,ensure_ascii=False),flush=True)