import os,sys,pathlib,json,time,subprocess,threading,signal,socket,datetime,secrets,re
# Arguments: checkout, seed root, output root, label. All state is synthetic.
checkout,seed,out,label=map(pathlib.Path,sys.argv[1:]); out.mkdir(parents=True,exist_ok=False)
(out/'driver-pid').write_text(str(os.getpid()))
state=checkout/'.t3/userdata'; state.mkdir(parents=True,exist_ok=True)
legacy=['usage-scan-cache-v10.json','usage-scan-cache-v9.json','usage-scan-cache-v8.json','usage-scan-cache-v7.json','usage-scan-cache-v6.json','usage-scan-cache-v5.json','usage-scan-cache.json']
for name in legacy: (state/name).write_text(json.dumps({'version':5,'models':[],'sessions':[],'files':{}}))
for name,age in [('usage-scan-cache-v11.json.OLD123',7200),('usage-scan-cache-v5.json.OLD123',7200),('usage-scan-cache-v11.json.NEW123',0)]:
 d=state/name; d.mkdir();
 if age: os.utime(d,(time.time()-age,time.time()-age))
settings={'providerInstances':{'claudeAgent':{'driver':'claudeAgent','displayName':'Synthetic transcripts','config':{'homePath':str(seed/'claude')}},'codex':{'driver':'codex','config':{'homePath':str(seed/'codex')}}}}
(state/'settings.json').write_text(json.dumps(settings))
home=seed/'empty-home';home.mkdir(exist_ok=True)
token=secrets.token_hex(32)
env={**os.environ,'T3CODE_DEV_SEED':'0','NODE_OPTIONS':'--max-old-space-size=1536','CLAUDE_CONFIG_DIR':str(seed/'claude'),'CODEX_HOME':str(seed/'codex'),'T3CODE_DEV_AUTH_TOKEN':token,'T3CODE_TELEMETRY_ENABLED':'false','HOME':str(home),'XDG_CONFIG_HOME':str(home/'config'),'XDG_DATA_HOME':str(home/'data')}
for key in list(env):
 if key.endswith(('_API_KEY','_AUTH_TOKEN','_ACCESS_TOKEN')): env.pop(key)
cli=['node','apps/server/dist/bin.mjs']
issued=subprocess.check_output(cli+['auth','session','issue','--base-dir',str(checkout/'.t3'),'--token-only'],cwd=checkout,env=env,stderr=subprocess.DEVNULL,text=True)
token=issued.strip().splitlines()[-1]
public=open(out/'public.txt','w',buffering=1)
def emit(line):
 line=datetime.datetime.now(datetime.timezone.utc).strftime('%H:%M:%S UTC ')+line
 print(line,flush=True);public.write(line+'\n')
def artifacts():
 for p in sorted(state.glob('usage-scan-cache*')):
  emit('DISK: '+p.name+(' / directory' if p.is_dir() else f' / {p.stat().st_size:,} bytes'))
emit('REAL T3 SERVER | '+str(label)+' | '+subprocess.check_output(['git','rev-parse','HEAD'],cwd=checkout,text=True).strip())
emit('Linux x64; Node '+subprocess.check_output(['node','--version'],text=True).strip()+'; V8 old-space limit 1536 MiB')
emit('Same 2,700 transcript files x 1,000 unique records; isolated fresh state; no live data')
emit('Seeded 7 superseded cache files, two 2-hour-old temp dirs, one recent temp dir')
artifacts()
raw=open(out/'server-private.log','w',buffering=1)
p=subprocess.Popen(cli+['serve','--port','18918','--base-dir',str(checkout/'.t3'),'--no-browser','--log-level','warn',str(checkout)],cwd=checkout,env=env,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,start_new_session=True)
(out/'pid').write_text(str(p.pid))
crashed=threading.Event()
def logs():
 for line in p.stdout:
  raw.write(line)
  # Only native fatal/GC/exit lines are shown publicly. Startup tokens and paths stay private.
  if any(s in line for s in ['FATAL ERROR','Mark-Compact','EncodeUtf8String','String::ValueView','Failed running','heap out of memory']):
   line=re.sub(r'\x1b\[[0-9;]*m','',line.strip())
   line=line.replace(str(checkout),'[checkout]').replace(str(out),'[evidence]')
   line=re.sub(r'\[/[^]]+\]', '[Node runtime]',line)
   emit('SERVER STDERR: '+line[:180])
  if 'FATAL ERROR' in line: crashed.set()
threading.Thread(target=logs,daemon=True).start()
try:
 ready=False
 for _ in range(240):
  if p.poll() is not None: break
  try:
   with socket.create_connection(('127.0.0.1',18918),timeout=1): ready=True;break
  except OSError: time.sleep(1)
 if not ready: raise RuntimeError('server never listened; inspect private log')
 emit('Server listening; triggering the same RPC used by Usage history')
 rpcenv={**env,'EVIDENCE_ORIGIN':'http://127.0.0.1:18918','EVIDENCE_TOKEN':token}
 rpc=subprocess.Popen(['node',str(pathlib.Path(__file__).with_name('rpc.mjs'))],cwd=checkout,env=rpcenv,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,start_new_session=True)
 def rpclogs():
  for line in rpc.stdout: emit(line.strip().replace(str(pathlib.Path(__file__).parent),'[evidence]'))
 threading.Thread(target=rpclogs,daemon=True).start()
 start=time.monotonic(); last_size=None; complete_at=None; next_progress=15
 while time.monotonic()-start<300:
  if rpc.poll() not in (None,0):
   emit('OBSERVED: RPC driver failed; capture is invalid');break
  if time.monotonic()-start>=next_progress:
   emit(f'DRIVER: observing cache persistence, elapsed {int(time.monotonic()-start)} s');next_progress+=15
  if crashed.is_set():
   time.sleep(4); emit('OBSERVED: server child aborted with V8 out-of-memory');break
  cache=state/'usage-scan-cache-v11.json'
  if cache.exists():
   size=cache.stat().st_size
   if size!=last_size:
    emit(f'DISK: completed cache now {size:,} bytes');last_size=size;complete_at=time.monotonic()
  if complete_at and time.monotonic()-complete_at>15 and rpc.poll() is not None:
   # Probe real server after persistence, rather than only relying on the RPC response.
   import urllib.request
   req=urllib.request.Request('http://127.0.0.1:18918/.well-known/t3/environment',headers={'Authorization':'Bearer '+token,'x-t3-orchestration-protocol':'2'})
   with urllib.request.urlopen(req,timeout=10) as r: emit('HTTP after save: '+str(r.status))
   emit('OBSERVED: cache save completed; server still responds'); break
  time.sleep(1)
 else: emit('OBSERVED: 300-second observation limit reached')
 artifacts()
 if rpc.poll() is None: os.killpg(rpc.pid,signal.SIGTERM)
finally:
 emit('Stopping this run before starting the other commit')
 if p.poll() is None: os.killpg(p.pid,signal.SIGTERM)
 try:p.wait(timeout=15)
 except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait()
 emit('Run stopped')
 (out/'done').touch()
 time.sleep(8)
