from __future__ import annotations import json import sqlite3 from pathlib import Path from app.core.contracts import RuntimeEvent class SQLiteEventStore: """Append-only event store with per-task ordered history.""" def __init__(self, db_path: str | Path) -> None: self._db_path = Path(db_path) self._db_path.parent.mkdir(parents=True, exist_ok=True) self._initialize() def append(self, event: RuntimeEvent) -> None: with sqlite3.connect(self._db_path) as conn: conn.execute( """ INSERT INTO events ( event_id, task_id, session_id, sequence, type, timestamp, payload_json, causation_id, correlation_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( event.event_id, event.task_id, event.session_id, event.sequence, event.type, event.timestamp.isoformat(), json.dumps(event.payload), event.causation_id, event.correlation_id, ), ) conn.commit() def list_for_task(self, task_id: str) -> list[RuntimeEvent]: with sqlite3.connect(self._db_path) as conn: rows = conn.execute( """ SELECT event_id, task_id, session_id, sequence, type, timestamp, payload_json, causation_id, correlation_id FROM events WHERE task_id = ? ORDER BY sequence ASC """, (task_id,), ).fetchall() return [ RuntimeEvent( event_id=row[0], task_id=row[1], session_id=row[2], sequence=row[3], type=row[4], timestamp=row[5], payload=json.loads(row[6]), causation_id=row[7], correlation_id=row[8], ) for row in rows ] def get_latest_sequence(self, task_id: str) -> int: with sqlite3.connect(self._db_path) as conn: row = conn.execute( "SELECT COALESCE(MAX(sequence), 0) FROM events WHERE task_id = ?", (task_id,), ).fetchone() return int(row[0]) if row else 0 def _initialize(self) -> None: with sqlite3.connect(self._db_path) as conn: conn.execute( """ CREATE TABLE IF NOT EXISTS events ( event_id TEXT PRIMARY KEY, task_id TEXT NOT NULL, session_id TEXT NOT NULL, sequence INTEGER NOT NULL, type TEXT NOT NULL, timestamp TEXT NOT NULL, payload_json TEXT NOT NULL, causation_id TEXT, correlation_id TEXT NOT NULL, UNIQUE(task_id, sequence) ) """ ) conn.commit()