"""Reuse mature byte-only source, local/DM launch and immutable observation tools."""
from pathlib import Path
import json,hashlib,shutil,csv
HERE=Path(__file__).resolve().parent
OLD=HERE.parent/'parallel-ai-direct002-20261011'
RUN=Path('/tmp/cdx-ai-api-customer-read12-20261011')
PRIOR=Path('/tmp/cdx-ai-core-direct002-20261011')
MAIN=Path('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api')
assert not RUN.exists()
RUN.mkdir(mode=0o700);(RUN/'source').mkdir(mode=0o700);(HERE/'tools').mkdir(exist_ok=True)
pins=json.loads((OLD/'source-frozen-sha.json').read_text())
for n,h in pins['files'].items():
 src=MAIN/n;assert src.is_file() and not src.is_symlink() and hashlib.sha256(src.read_bytes()).hexdigest()==h
 dst=RUN/'source'/n;dst.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(src,dst)
assert {p.relative_to(RUN/'source').as_posix() for p in (RUN/'source').rglob('*') if p.is_file()}==set(pins['files'])
(HERE/'source-frozen-sha.json').write_text(json.dumps(pins,indent=2)+'\n');shutil.copyfile(HERE/'source-frozen-sha.json',RUN/'source-frozen-sha.json')
for n in ['native.php','app.php','launch.php','observer.php','cleanup-cache.php','source-state.php']:
 raw=(OLD/'tools'/n).read_text()
 if n=='launch.php':
  start=raw.index("!in_array($stage,[");end=raw.index("],true))exit(78)",start)
  raw=raw[:start]+"!in_array($stage,['preflight','guard','run','capture','cleanup','cleanup-cache','source-state','test-gate','start','stop'"+raw[end:]
  raw=raw.replace('18943:18944','18971:18972')
 if n=='native.php':
  raw=raw.replace("$s=nativePdo($db)->prepare($sql);$bind=[];","consumeSQL('independent.SELECT');$s=nativePdo($db)->prepare($sql);$bind=[];")
 for dst in [HERE/'tools'/n,RUN/n]:dst.write_text(raw)
for lane in ['B','C']:
 dst=RUN/f'seed-{lane}';dst.mkdir(mode=0o700);src=PRIOR/f'private-{lane}-final'
 for n in ['prepared.json',f'preflight-{lane}.json']:
  shutil.copyfile(src/n,dst/n);(dst/n).chmod(0o600)
 shutil.copyfile(src/'persistence-final.json',dst/'prior-persistence.json')
routes=[
 ('POINTS','/api/points.Log/list',{'page':'1'},'points-paginator'),
 ('REFUNDS','/api/Refund/list',{'state':'-1','page':'1'},'refund-paginator'),
 ('SIGN','/api/Sign/list',{'signId':'@latest-own-sign-action-or-0'},'sign-projection'),
 ('WEIGHT','/api/user.UserWeight/byDate',{'date':'2026-10-11'},'weight-last7'),
 ('WELL_LATEST','/api/WellandScale/latest',{},'welland-session'),
 ('WELL_DATES','/api/WellandScale/dates',{},'welland-dates'),
 ('WELL_DATE','/api/WellandScale/byDate',{'date':'@latest-own-body-session-day-or-2026-10-11'},'welland-session-day'),
 ('YKB_LATEST','/api/YunkangbaoScale/latest',{},'yunkangbao-session'),
 ('YKB_DATES','/api/YunkangbaoScale/dates',{},'yunkangbao-dates'),
 ('YKB_DATE','/api/YunkangbaoScale/byDate',{'date':'@latest-own-yunkangbao-session-day-or-2026-10-11'},'yunkangbao-session-day'),
 ('INDEX','/api/Index/index',{},'literal404-no-businessDB')]
matrix=HERE.parents[2]/'work/2026-09-28-smart-canteen-dm8-poc/http-cron-coverage-matrix.csv'
rows=list(csv.DictReader(matrix.open()));lookup={r['route_or_command']:r for r in rows if r['repo']=='ai_api'}
cases=[]
for label,route,params,oracle in routes:
 row=lookup[route];assert row['mysql_http_or_command']==row['dm_http_or_command']=='not-tested',route
 cases.append({'label':label,'method':'GET','path':route,'params':params,'oracle':oracle,'matrix_row':row,'business_DML':0,'SDK':0,'external_HTTP':0})
plan={'authorization':'Root direct message: autonomous minimum plan→tests→readonly preflight→HTTP→full DTO/original rows→seal; each side≤24HTTP, readonly total≤600,0business/prepareDMLDDL,original95/208immutable',
 'source_HEAD':pins['HEAD'],'source_files':6168,'source_bytecopy_no_xattrs_no_AppleDouble':True,
 'ports':{'B':18971,'C':18972},'actors':{'B':44676,'C':1836},'core_U1_protected':{'B':46916,'C':1931},'tenant':2147001111,'host':'core.scan-pay.invalid','platform':'POC','token_each_side_max':1,
 'cases':cases,'HTTP_each_side_planned':11,'HTTP_each_side_cap':24,'SQL_each_side_cap':600,'DML':0,'DDL':0,'new_fixtures':0,'replay_success':False,
 'skipped':{'/api/H5/bills':'list refreshExpiredBills→expireBill UPDATE+billing_record_log INSERT','/api/H5/billDetail':'detail same hidden expiry DML','/api/Order/express':'ExpressService.traces may external; yoshop_order IDs distinct from ydy_meal_order','/api/order.Comment/list':'requires yoshop_order and goods; cannot reuse Store meal paid IDs; no new fixtures','/api/Dishes/all':'store Dishes model has no getAll, forwards to missing Query method','/api/Dishes/skuInfo':'GoodsService undefined/unimported in controller'},
 'limits':'Index404 is noDB; own-empty returns empty-only; nonempty case proof must use per-side original tables, never cross-side equality or new fixture'}
for dst in [HERE/'machine-plan.json',RUN/'machine-plan.json']:dst.write_text(json.dumps(plan,ensure_ascii=False,indent=2)+'\n')
print(json.dumps({'source_files':6168,'planned_routes':len(cases),'SQL':0,'HTTP':0,'Auth':0}))
