titan-iac/services/hermes/scripts/migrate_api_session_lineage.py
jenkins 37ac5ff11f
All checks were successful
Tests / Declarative: Post Actions passed: 265
hermes: group automated triage sessions
2026-08-13 00:50:34 -03:00

129 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Nest known legacy agent API workers under their originating objective."""
from __future__ import annotations
import os
import re
import sqlite3
import time
from pathlib import Path
STATE_DB = Path("/opt/data/state.db")
LEGACY_CASSANDRA_PARENT = "20260812_042739_d6ee37"
LEGACY_CASSANDRA_WORKERS = {
"api-d947ebb51d09eb97": "Cassandra handoff existence check",
"api-bd98c6145593e9e9": "Cassandra handoff heading check",
"api-cb00bdf2f7cd3889": "Cassandra worktree file check",
"api-91b5c2de1759380b": "Agent hosted route check",
"api-0d1d15e79c619f7b": "Cassandra file-tool check",
"api-5550b3556424fbdc": "Cassandra handoff summary",
"api-c288b9f3024a91b8": "Cassandra stale-worktree check",
"api-058e51802b58ee3f": "Cassandra verification worker",
}
LEGACY_ORPHANED_SMOKE_SESSIONS = {
"e17f2d888689": "Archived agent workspace smoke test",
}
TRIAGE_PARENT = "automated-triage"
TRIAGE_PARENT_TITLE = "Automated triage"
TRIAGE_MESSAGE_PREFIX = "A static-analysis finding, not a build failure."
def _triage_title(message: str) -> str:
"""Create a concise label from Ariadne's stable incident contract."""
match = re.search(r"for incident ([^\s]+)\.", message)
if not match:
return "Automated triage run"
parts = match.group(1).split("/")
label = " · ".join(parts[1:3]) if len(parts) >= 3 else match.group(1)
return f"Sonar · {label}"
def migrate_triage_group(connection: sqlite3.Connection) -> int:
"""Nest only recognized automated Sonar runs under one durable parent."""
rows = connection.execute(
"""
SELECT s.id, s.started_at, m.content
FROM sessions AS s
JOIN messages AS m ON m.session_id = s.id
WHERE s.source = 'api_server'
AND s.parent_session_id IS NULL
AND s.archived = 0
AND m.role = 'user'
AND m.content LIKE ?
ORDER BY s.started_at
""",
(f"{TRIAGE_MESSAGE_PREFIX}%",),
).fetchall()
if not rows:
return 0
started_at = min(float(row[1] or time.time()) for row in rows)
connection.execute(
"""
INSERT OR IGNORE INTO sessions (id, source, started_at, title, archived)
VALUES (?, 'api_server', ?, ?, 0)
""",
(TRIAGE_PARENT, started_at, TRIAGE_PARENT_TITLE),
)
changed = 0
for session_id, _, message in rows:
cursor = connection.execute(
"""
UPDATE sessions
SET parent_session_id = ?, title = ?
WHERE id = ?
AND parent_session_id IS NULL
""",
(TRIAGE_PARENT, _triage_title(str(message or "")), session_id),
)
changed += cursor.rowcount
return changed
def migrate(path: Path = STATE_DB, *, group_triage: bool = False) -> int:
"""Apply idempotent, transcript-preserving lineage corrections."""
if not path.is_file():
return 0
changed = 0
with sqlite3.connect(path) as connection:
parent = connection.execute(
"SELECT id FROM sessions WHERE id = ?", (LEGACY_CASSANDRA_PARENT,)
).fetchone()
if parent:
for session_id, title in LEGACY_CASSANDRA_WORKERS.items():
cursor = connection.execute(
"""
UPDATE sessions
SET parent_session_id = ?,
title = ?
WHERE id = ?
AND source = 'api_server'
AND parent_session_id IS NULL
""",
(LEGACY_CASSANDRA_PARENT, title, session_id),
)
changed += cursor.rowcount
for session_id, title in LEGACY_ORPHANED_SMOKE_SESSIONS.items():
cursor = connection.execute(
"""
UPDATE sessions
SET archived = 1,
title = ?
WHERE id = ?
AND source = 'api_server'
AND parent_session_id IS NULL
AND archived = 0
""",
(title, session_id),
)
changed += cursor.rowcount
if group_triage:
changed += migrate_triage_group(connection)
return changed
if __name__ == "__main__":
group_triage = os.environ.get("HERMES_API_DEFAULT_PARENT_SESSION_ID") == TRIAGE_PARENT
print(f"migrated {migrate(group_triage=group_triage)} legacy API sessions")