Stage guarded Classic migration and native bot adapter
Core tests / core (push) Successful in 29s

This commit is contained in:
Codex
2026-09-25 22:57:11 +02:00
parent 58b2019015
commit 02d21abcbd
17 changed files with 616 additions and 11 deletions
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Prepare the isolated dashboard database after native identities are verified."""
import argparse
import copy
import datetime
import json
import pathlib
import sqlite3
import urllib.request
import uuid
def main(root,live):
stage=root/'app-staging'
bound=json.loads((root/'classic-roster-bound.json').read_text())
assert len(bound)==50 and len({b['characterGuid'] for b in bound})==50
with urllib.request.urlopen('http://127.0.0.1:8787/api/state',timeout=10) as response:state=json.load(response)
# This archive captures memories accumulated since the initial migration export.
(root/'wrath-state-before-dashboard-copy.json').write_text(json.dumps(state))
old={b['id']:b for b in state['bots']}
now=datetime.datetime.now(datetime.timezone.utc).isoformat()
bots=[]
for identity in bound:
bot=copy.deepcopy(old[identity['id']])
bot.update(name=identity['name'],race=identity['race'],role=identity['role'],level=1,zone='Awaiting Classic observation',status='offline',simulationBand='dormant',decisions=0)
for key in ['world','lastDecision','deathMemorySeenId','lastObservation','death']:bot.pop(key,None)
bot['autonomy']={'managed':True,'characterGuid':identity['characterGuid'],'accountId':identity['accountId']}
for memory in bot.get('memories',[]):memory['text']='[Historical memory from the previous Wrath realm; not current Classic progress] '+memory['text']
bot.setdefault('memories',[]).append({'id':str(uuid.uuid4()),'text':f"I am starting a new life at level 1 as a {identity['race']} {identity['role']} in Classic Azeroth. Older memories describe my previous realm.",'importance':10,'source':'observation','at':now})
bot['memories']=bot['memories'][-100:]
bots.append(bot)
state['bots']=bots;state['aleRosterAt']=0;state['revision']=state.get('revision',0)+1
state['events']=[];state.pop('operations',None);state['population']={}
state['settings']['population']['serverMax']=50
state['settings']['hardcore']['enabled']=False
state['community']={'feed':[],'evidence':[],'guilds':[],'partiesEnabled':True,'guildsEnabled':True}
ids={b['id'] for b in bots}
for home in state.get('homes',[]):home['residentIds']=[id for id in home.get('residentIds',[]) if id in ids]
source=list((live/'.wrangler/state/v3/d1').rglob('*.sqlite'))
candidates=[]
for path in source:
with sqlite3.connect(f'file:{path}?mode=ro',uri=True) as db:
if db.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='realm_state'").fetchone():candidates.append(path)
if len(candidates)!=1:raise RuntimeError('Expected exactly one live realm database')
source=candidates[0];target=stage/source.relative_to(live)
if target.exists():raise RuntimeError('Staging database already exists; refusing to overwrite')
target.parent.mkdir(parents=True,exist_ok=True)
with sqlite3.connect(f'file:{source}?mode=ro',uri=True) as src,sqlite3.connect(target) as dst:
src.backup(dst)
tables={row[0] for row in dst.execute("SELECT name FROM sqlite_master WHERE type='table'")}
for table in ['ale_commands','ale_events','community_actions','community_clock','conversation_requests','realm_state_chunks']:
if table in tables:dst.execute('DELETE FROM '+table)
text=json.dumps(state,ensure_ascii=False,separators=(',',':'))
parts=[text[i:i+20000] for i in range(0,len(text),20000)];token=str(uuid.uuid4())
dst.execute('CREATE TABLE IF NOT EXISTS realm_state_chunks (token TEXT,part INTEGER,payload TEXT,PRIMARY KEY(token,part))')
dst.executemany('INSERT INTO realm_state_chunks VALUES (?,?,?)',[(token,i,p) for i,p in enumerate(parts)])
dst.execute("UPDATE realm_state SET revision=?,payload=? WHERE id='realm'",(state['revision'],json.dumps({'chunkToken':token,'parts':len(parts)})))
(root/'classic-initial-state.json').write_text(json.dumps(state))
print('Isolated dashboard prepared: 50 preserved personalities, native GUIDs rebound, historical memories labeled, live observations and queues reset')
if __name__=='__main__':
parser=argparse.ArgumentParser();parser.add_argument('--root',type=pathlib.Path,required=True);parser.add_argument('--live',type=pathlib.Path,required=True)
args=parser.parse_args();main(args.root.resolve(),args.live.resolve())