Add canonical event log and renderer skeleton (A10)
CI / checks (push) Successful in 9s
CI / checks (pull_request) Successful in 8s

Build-order step 4, sequenced before the LLM client (step 3) because
the client interface consumes the canonical log.

- canonical.py: typed events for the full A10 table (user_message
  through compaction); canonical tool-call IDs minted locally,
  provider IDs never a join key; strict payload validation on load
- StateStore: canonical_events table via schema v2 — the first real
  migration, exercised in a test against a hand-built v1 database —
  with append_event/read_events on the same fsync-per-commit path
- render.py: request bodies rendered from the log at call time;
  first adapter targets the OpenAI-compatible shape of LiteLLM's
  normalized route; compaction events replace their id range at
  render time while staying in the durable log
- Crash integration test now also verifies transcript appends
  survive process death

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dongarandClaude Fable 5 committed 2026-09-06 19:51:58 +00:00
1 parent b05a402c02
commit ac81dd6234
6 files changed
+501 -2

No files matched your search

+47 -2
View File
@@ -18,14 +18,35 @@ from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Self
from nisse_dev.canonical import (
CanonicalEvent,
StoredEvent,
event_type,
from_payload,
to_payload,
)
from nisse_dev.oplog import OpLog
CURRENT_SCHEMA_VERSION = 1
CURRENT_SCHEMA_VERSION = 2
Migration = Callable[[sqlite3.Connection], None]
_CANONICAL_EVENTS_DDL = """
CREATE TABLE canonical_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
type TEXT NOT NULL,
payload TEXT NOT NULL
)
"""
def _migrate_v1_add_canonical_events(conn: sqlite3.Connection) -> None:
conn.execute(_CANONICAL_EVENTS_DDL)
# Maps version N to the migration that takes the schema from N to N+1.
MIGRATIONS: Mapping[int, Migration] = {}
MIGRATIONS: Mapping[int, Migration] = {1: _migrate_v1_add_canonical_events}
class StateSchemaError(RuntimeError):
@@ -71,6 +92,7 @@ _SCHEMA = [
resolved_at TEXT
)
""",
_CANONICAL_EVENTS_DDL,
]
@@ -249,6 +271,29 @@ class StateStore:
for row in rows
]
def append_event(self, event: CanonicalEvent) -> int:
"""Append to the canonical transcript (A10); committed and fsynced
before return, like every other durable write."""
cursor = self._conn.execute(
"INSERT INTO canonical_events (ts, type, payload) VALUES (?, ?, ?)",
(_now(), event_type(event), json.dumps(to_payload(event))),
)
assert cursor.lastrowid is not None
return cursor.lastrowid
def read_events(self, after: int = 0) -> list[StoredEvent]:
rows = self._conn.execute(
"SELECT * FROM canonical_events WHERE id > ? ORDER BY id", (after,)
).fetchall()
return [
StoredEvent(
id=row["id"],
ts=row["ts"],
event=from_payload(row["type"], json.loads(row["payload"])),
)
for row in rows
]
def close(self) -> None:
self._conn.close()