Reconcile delayed hardcore deaths with native population registry
This commit is contained in:
@@ -9,6 +9,7 @@ local TARGET_PER_FACTION = 25
|
|||||||
local gates = {}
|
local gates = {}
|
||||||
local gateAt = {}
|
local gateAt = {}
|
||||||
local arrivals = {}
|
local arrivals = {}
|
||||||
|
local reconciledAt = 0
|
||||||
local alliance = {[1]=true,[3]=true,[4]=true,[7]=true,[11]=true}
|
local alliance = {[1]=true,[3]=true,[4]=true,[7]=true,[11]=true}
|
||||||
local horde = {[2]=true,[5]=true,[6]=true,[8]=true,[10]=true}
|
local horde = {[2]=true,[5]=true,[6]=true,[8]=true,[10]=true}
|
||||||
local function faction(row)
|
local function faction(row)
|
||||||
@@ -77,6 +78,17 @@ end
|
|||||||
|
|
||||||
local function enforce()
|
local function enforce()
|
||||||
if not ready then reload() end
|
if not ready then reload() end
|
||||||
|
-- The dashboard may receive a verified death from an older transport backlog
|
||||||
|
-- after this process seeded its registry. Import terminal states only; never
|
||||||
|
-- overwrite a locally observed death with an older active database row.
|
||||||
|
if os.time()-reconciledAt>=10 then
|
||||||
|
local q=AuthDBQuery("SELECT guid,name,account_id FROM azerothai_lifecycle WHERE state='dead' AND protected=0 AND LOWER(name)<>'zaku'")
|
||||||
|
if q then repeat
|
||||||
|
local row=residents[q:GetUInt32(0)]
|
||||||
|
if row and not row.protected and row.name==q:GetString(1) and row.account==q:GetUInt32(2) then row.state="dead" end
|
||||||
|
until not q:NextRow() end
|
||||||
|
reconciledAt=os.time()
|
||||||
|
end
|
||||||
local mgr=GetRandomPlayerbotMgr()
|
local mgr=GetRandomPlayerbotMgr()
|
||||||
-- Disallow every retired identity before the native manager fills vacancies.
|
-- Disallow every retired identity before the native manager fills vacancies.
|
||||||
for guid,row in pairs(residents) do
|
for guid,row in pairs(residents) do
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ already does. Archived snapshots and terminal application commands use the chose
|
|||||||
state and memorials are preserved. Current application log files rotate at 10 MB
|
state and memorials are preserved. Current application log files rotate at 10 MB
|
||||||
with three copies. Database files may retain freed SQLite pages for reuse.
|
with three copies. Database files may retain freed SQLite pages for reuse.
|
||||||
|
|
||||||
|
The minute operations check also reconciles verified AzerothCore death records
|
||||||
|
with the native hardcore registry using character GUID, account and exact name.
|
||||||
|
Protected residents are excluded. Each correction keeps the old registry row
|
||||||
|
and supporting death in `lifecycle-reconciliation.jsonl`. The Lua guard imports
|
||||||
|
terminal registry states every ten seconds, removes the retired identity, and
|
||||||
|
allows its faction's replacement slot to fill under the existing 25/25 cap.
|
||||||
|
This handles delayed death events received after the registry was first seeded.
|
||||||
|
|
||||||
Install `deploy/playable/install-user-recovery.py` as the realm owner. It registers
|
Install `deploy/playable/install-user-recovery.py` as the realm owner. It registers
|
||||||
`azerothai-runtime.service` with Restart=always and a minute operations timer in
|
`azerothai-runtime.service` with Restart=always and a minute operations timer in
|
||||||
the user's systemd manager. Enable user lingering for reboot startup. Telemetry
|
the user's systemd manager. Enable user lingering for reboot startup. Telemetry
|
||||||
|
|||||||
@@ -44,6 +44,27 @@ try:
|
|||||||
state=json.load(urllib.request.urlopen(base+'/api/state',timeout=10));days=max(3,min(30,int(state.get('opsRetentionDays',7))))
|
state=json.load(urllib.request.urlopen(base+'/api/state',timeout=10));days=max(3,min(30,int(state.get('opsRetentionDays',7))))
|
||||||
env=os.environ.copy();env['MYSQL_PWD']=cfg['DOCKER_DB_ROOT_PASSWORD']
|
env=os.environ.copy();env['MYSQL_PWD']=cfg['DOCKER_DB_ROOT_PASSWORD']
|
||||||
def sql(q):return subprocess.check_output(['docker','exec','-e','MYSQL_PWD','ac-database','mysql','-uroot','-N','-B','-e',q],env=env,text=True)
|
def sql(q):return subprocess.check_output(['docker','exec','-e','MYSQL_PWD','ac-database','mysql','-uroot','-N','-B','-e',q],env=env,text=True)
|
||||||
|
# Reconcile delayed verified deaths by the full native identity. This never
|
||||||
|
# revives an identity, changes a protected resident, or counts a human as a bot.
|
||||||
|
if state.get('settings',{}).get('hardcore',{}).get('enabled'):
|
||||||
|
retired=0
|
||||||
|
registry={int(line.split('\t')[0]):line for line in sql("SELECT guid,name,account_id,state,died_at FROM acore_auth.azerothai_lifecycle WHERE state<>'dead' AND protected=0 AND LOWER(name)<>'zaku'").splitlines() if line}
|
||||||
|
for bot in state.get('bots',[]):
|
||||||
|
identity=bot.get('autonomy') or {};death=bot.get('death') or {}
|
||||||
|
if bot.get('status')!='dead' or not death.get('verified') or death.get('source')!='azerothcore' or not identity.get('managed'):continue
|
||||||
|
if not bot.get('mortal') or bot.get('plotArmorHpFloor',0)>0 or bot.get('name','').lower()=='zaku':continue
|
||||||
|
guid=identity.get('characterGuid');account=identity.get('accountId')
|
||||||
|
if type(guid) is not int or type(account) is not int or guid<=0 or account<=0:continue
|
||||||
|
before=registry.get(guid,'');fields=before.split('\t')
|
||||||
|
if len(fields)!=5 or fields[1]!=bot['name'] or int(fields[2])!=account:continue
|
||||||
|
name=bot['name'].encode().hex()
|
||||||
|
where=f"guid={guid} AND account_id={account} AND BINARY name=UNHEX('{name}') AND protected=0 AND LOWER(name)<>'zaku' AND state<>'dead'"
|
||||||
|
# Keep an audit of the old registry row and supporting death before changing it.
|
||||||
|
audit=lab/'lifecycle-reconciliation.jsonl'
|
||||||
|
with audit.open('a') as f:f.write(json.dumps({'at':time.time(),'before':before.strip(),'death':death})+'\n')
|
||||||
|
changed=sql("UPDATE acore_auth.azerothai_lifecycle SET state='dead',died_at=COALESCE(died_at,NOW()) WHERE "+where+'; SELECT ROW_COUNT()')
|
||||||
|
retired+=int(changed.strip())
|
||||||
|
maintenance.append(f'Hardcore registry checked; {retired} delayed deaths reconciled')
|
||||||
for table,column in [('azerothai_event_outbox','delivered_at'),('azerothai_result_outbox','delivered_at')]:
|
for table,column in [('azerothai_event_outbox','delivered_at'),('azerothai_result_outbox','delivered_at')]:
|
||||||
sql(f'DELETE FROM acore_auth.{table} WHERE {column} IS NOT NULL AND {column}<NOW()-INTERVAL 1 DAY LIMIT 2000')
|
sql(f'DELETE FROM acore_auth.{table} WHERE {column} IS NOT NULL AND {column}<NOW()-INTERVAL 1 DAY LIMIT 2000')
|
||||||
sql(f'DELETE FROM acore_auth.azerothai_snapshot_archive WHERE created_at<NOW()-INTERVAL {days} DAY LIMIT 2000')
|
sql(f'DELETE FROM acore_auth.azerothai_snapshot_archive WHERE created_at<NOW()-INTERVAL {days} DAY LIMIT 2000')
|
||||||
|
|||||||
Reference in New Issue
Block a user