#!/usr/bin/env python3 """ Migration: chat_backup.jsonl ts-Werte von Container-Uptime-ms auf UNIX-ms umstellen. Hintergrund: vor dem Fix nutzte _append_chat_backup() `asyncio.get_event_loop().time()`, was Container-Monotonic ist (bei Restart wieder 0). Mischte sich mit App-side `Date.now()` (echtes UNIX-ms) → falsche Sortierung in der App-History. Strategie: ts < 1e12 (keine UNIX-ms) werden umgeschrieben. Anker = file-mtime, decay 60 Sekunden pro Eintrag rueckwaerts. Datei-Reihenfolge bleibt erhalten (append-only war chronologisch korrekt, nur ts-Werte waren Unsinn). Vorhandene UNIX-ms-Eintraege (file_deleted-Marker, neue Eintraege ab Bridge-Fix) werden unveraendert gelassen. Idempotent: zweimal laufen lassen ist sicher — beim zweiten Mal sind alle ts schon UNIX-ms und werden nicht angefasst. Backup: schreibt erst chat_backup.jsonl.bak, dann atomar replace. """ import json import os import shutil import sys import time from pathlib import Path UNIX_MS_THRESHOLD = 10 ** 12 # < 1e12 ms = vor 2001 = unrealistisch fuer UNIX GAP_SECONDS = 60 # 1 Eintrag pro Minute rueckwaerts ab mtime def migrate(path: Path) -> None: if not path.exists(): print(f"Datei nicht da: {path}") sys.exit(1) raw = path.read_text(encoding="utf-8").splitlines() entries = [] for raw_line in raw: s = raw_line.strip() if not s: continue try: entries.append(json.loads(s)) except Exception as e: print(f" ueberspringe kaputte Zeile: {e}") continue if not entries: print("Datei leer") return file_mtime_ms = int(os.path.getmtime(path) * 1000) n = len(entries) fixed = 0 # Wir bauen einen Ersatz-ts (file_mtime - gap*minutes_back) nur fuer # Eintraege deren ts < UNIX_MS_THRESHOLD. file_deleted etc. mit echtem # UNIX-ms bleiben unangetastet. for i, entry in enumerate(entries): ts = entry.get("ts", 0) if not isinstance(ts, (int, float)) or ts < UNIX_MS_THRESHOLD: # Synth-ts vergeben: aelteste = mtime - n*gap, neueste = mtime new_ts = file_mtime_ms - (n - 1 - i) * GAP_SECONDS * 1000 entry["ts"] = new_ts fixed += 1 if fixed == 0: print(f"Nichts zu migrieren ({n} Eintraege, alle ts schon UNIX-ms)") return # Backup bak = path.with_suffix(path.suffix + ".bak") shutil.copy2(path, bak) print(f"Backup: {bak}") # Atomic rewrite tmp = path.with_suffix(path.suffix + ".tmp") with open(tmp, "w", encoding="utf-8") as f: for entry in entries: f.write(json.dumps(entry, ensure_ascii=False) + "\n") tmp.replace(path) print(f"Migration fertig: {fixed}/{n} ts umgeschrieben") print(f" aelteste neu : {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(entries[0]['ts'] / 1000))}") print(f" neueste neu : {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(entries[-1]['ts'] / 1000))}") if __name__ == "__main__": default = Path("/var/lib/docker/volumes/aria-agent_aria-shared/_data/config/chat_backup.jsonl") path = Path(sys.argv[1]) if len(sys.argv) > 1 else default migrate(path)