- API versioning: all REST endpoints prefixed with /api/v1/ - Structured logging: replaced stdlib logging with structlog (console/JSON modes) - Alembic migrations: versioned DB schema with initial migration - Error standardization: global exception handlers for consistent envelope format - Interrupt cleanup: asyncio background task for expired interrupt removal - Integration tests: +30 tests (analytics, replay, openapi, error, session APIs) - Frontend tests: +57 tests (all components, pages, useWebSocket hook) - Backend: 557 tests, 89.75% coverage | Frontend: 80 tests, 16 test files
93 lines
2.7 KiB
Python
93 lines
2.7 KiB
Python
"""Initial schema -- all application tables.
|
|
|
|
Revision ID: a1b2c3d4e5f6
|
|
Revises:
|
|
Create Date: 2026-04-06
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "a1b2c3d4e5f6"
|
|
down_revision: str | None = None
|
|
branch_labels: tuple[str, ...] | None = None
|
|
depends_on: tuple[str, ...] | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
thread_id TEXT PRIMARY KEY,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
total_tokens INTEGER NOT NULL DEFAULT 0,
|
|
total_cost_usd DOUBLE PRECISION NOT NULL DEFAULT 0.0,
|
|
status TEXT NOT NULL DEFAULT 'active'
|
|
)
|
|
"""
|
|
)
|
|
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS active_interrupts (
|
|
interrupt_id TEXT PRIMARY KEY,
|
|
thread_id TEXT NOT NULL REFERENCES conversations(thread_id),
|
|
action TEXT NOT NULL,
|
|
params JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
resolved_at TIMESTAMPTZ,
|
|
resolution TEXT
|
|
)
|
|
"""
|
|
)
|
|
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
thread_id TEXT PRIMARY KEY,
|
|
last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
has_pending_interrupt BOOLEAN NOT NULL DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)
|
|
"""
|
|
)
|
|
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS analytics_events (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
thread_id TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
agent_name TEXT,
|
|
tool_name TEXT,
|
|
tokens_used INTEGER NOT NULL DEFAULT 0,
|
|
cost_usd DOUBLE PRECISION NOT NULL DEFAULT 0.0,
|
|
duration_ms INTEGER,
|
|
success BOOLEAN,
|
|
error_message TEXT,
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)
|
|
"""
|
|
)
|
|
|
|
# Migration columns added in Phase 4
|
|
op.execute(
|
|
"""
|
|
ALTER TABLE conversations
|
|
ADD COLUMN IF NOT EXISTS resolution_type TEXT,
|
|
ADD COLUMN IF NOT EXISTS agents_used TEXT[],
|
|
ADD COLUMN IF NOT EXISTS turn_count INTEGER NOT NULL DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS ended_at TIMESTAMPTZ
|
|
"""
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute("DROP TABLE IF EXISTS analytics_events")
|
|
op.execute("DROP TABLE IF EXISTS sessions")
|
|
op.execute("DROP TABLE IF EXISTS active_interrupts")
|
|
op.execute("DROP TABLE IF EXISTS conversations")
|