CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pg_trgm"; CREATE TABLE IF NOT EXISTS sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id VARCHAR(64) DEFAULT 'default', created_at TIMESTAMPTZ DEFAULT NOW(), last_active_at TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ DEFAULT NOW() + INTERVAL '7 days', budget_usd NUMERIC(10, 6) DEFAULT 10.0, spent_usd NUMERIC(10, 6) DEFAULT 0.0, status VARCHAR(32) DEFAULT 'active', metadata JSONB DEFAULT '{}', context_json JSONB DEFAULT '[]', model_name VARCHAR(128), reasoning_effort VARCHAR(32), max_iterations INTEGER DEFAULT 300, yolo_mode BOOLEAN DEFAULT FALSE, CONSTRAINT positive_budget CHECK (budget_usd >= 0), CONSTRAINT positive_spent CHECK (spent_usd >= 0) ); CREATE TABLE IF NOT EXISTS requests ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), session_id UUID REFERENCES sessions(id) ON DELETE CASCADE, tenant_id VARCHAR(64) DEFAULT 'default', trace_id VARCHAR(128), model VARCHAR(128) NOT NULL, provider VARCHAR(64) NOT NULL, operation_type VARCHAR(32) DEFAULT 'chat', input_tokens INTEGER DEFAULT 0, output_tokens INTEGER DEFAULT 0, total_tokens INTEGER GENERATED ALWAYS AS (input_tokens + output_tokens) STORED, cost_usd NUMERIC(10, 6) DEFAULT 0.0, cached BOOLEAN DEFAULT FALSE, latency_ms INTEGER, queue_time_ms INTEGER DEFAULT 0, request_payload JSONB, response_payload JSONB, tool_calls JSONB DEFAULT '[]', finish_reason VARCHAR(32), rate_limited BOOLEAN DEFAULT FALSE, circuit_open BOOLEAN DEFAULT FALSE, retry_count INTEGER DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT non_negative_tokens CHECK (input_tokens >= 0 AND output_tokens >= 0) ); CREATE TABLE IF NOT EXISTS circuit_events ( id SERIAL PRIMARY KEY, provider VARCHAR(64) NOT NULL, event_type VARCHAR(32) NOT NULL, failure_count INTEGER DEFAULT 0, details JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS budget_events ( id SERIAL PRIMARY KEY, session_id UUID REFERENCES sessions(id) ON DELETE CASCADE, event_type VARCHAR(32) NOT NULL, threshold_percent INTEGER, spent_before NUMERIC(10, 6), spent_after NUMERIC(10, 6), budget NUMERIC(10, 6), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS tool_executions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID REFERENCES requests(id) ON DELETE CASCADE, tool_name VARCHAR(128) NOT NULL, tool_args JSONB, result_json JSONB, success BOOLEAN DEFAULT TRUE, error_message TEXT, latency_ms INTEGER, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS cache_events ( id SERIAL PRIMARY KEY, cache_key VARCHAR(256) NOT NULL, cache_type VARCHAR(32) NOT NULL, hit BOOLEAN NOT NULL, ttl_seconds INTEGER, size_bytes INTEGER, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sessions_tenant ON sessions(tenant_id); CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status); CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at); CREATE INDEX IF NOT EXISTS idx_sessions_created ON sessions(created_at DESC); CREATE INDEX IF NOT EXISTS idx_sessions_active ON sessions(tenant_id, status) WHERE status = 'active'; CREATE INDEX IF NOT EXISTS idx_sessions_metadata ON sessions USING GIN (metadata); CREATE INDEX IF NOT EXISTS idx_requests_session ON requests(session_id); CREATE INDEX IF NOT EXISTS idx_requests_tenant ON requests(tenant_id); CREATE INDEX IF NOT EXISTS idx_requests_trace ON requests(trace_id); CREATE INDEX IF NOT EXISTS idx_requests_provider ON requests(provider); CREATE INDEX IF NOT EXISTS idx_requests_created ON requests(created_at DESC); CREATE INDEX IF NOT EXISTS idx_requests_model ON requests(model); CREATE INDEX IF NOT EXISTS idx_requests_payload ON requests USING GIN (request_payload); CREATE INDEX IF NOT EXISTS idx_circuit_provider ON circuit_events(provider); CREATE INDEX IF NOT EXISTS idx_circuit_created ON circuit_events(created_at DESC); CREATE INDEX IF NOT EXISTS idx_budget_session ON budget_events(session_id); CREATE INDEX IF NOT EXISTS idx_tool_request ON tool_executions(request_id); CREATE INDEX IF NOT EXISTS idx_tool_name ON tool_executions(tool_name); CREATE INDEX IF NOT EXISTS idx_cache_key ON cache_events(cache_key); CREATE INDEX IF NOT EXISTS idx_cache_created ON cache_events(created_at DESC); CREATE OR REPLACE FUNCTION update_last_active() RETURNS TRIGGER AS $$ BEGIN NEW.last_active_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_update_last_active ON sessions; CREATE TRIGGER trigger_update_last_active BEFORE UPDATE ON sessions FOR EACH ROW EXECUTE FUNCTION update_last_active(); CREATE MATERIALIZED VIEW IF NOT EXISTS mv_daily_costs AS SELECT tenant_id, DATE(created_at) as date, provider, model, COUNT(*) as request_count, SUM(input_tokens) as total_input_tokens, SUM(output_tokens) as total_output_tokens, SUM(cost_usd) as total_cost_usd, AVG(latency_ms)::NUMERIC(10,2) as avg_latency_ms, SUM(CASE WHEN cached THEN 1 ELSE 0 END) as cache_hits, SUM(CASE WHEN NOT cached THEN 1 ELSE 0 END) as cache_misses FROM requests GROUP BY tenant_id, DATE(created_at), provider, model; CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_daily_costs ON mv_daily_costs(tenant_id, date, provider, model); CREATE OR REPLACE FUNCTION refresh_daily_costs() RETURNS void AS $$ BEGIN REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_costs; END; $$ LANGUAGE plpgsql; ALTER TABLE sessions ENABLE ROW LEVEL SECURITY; ALTER TABLE requests ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_sessions ON sessions USING (tenant_id = current_setting('app.current_tenant', true)); CREATE POLICY tenant_isolation_requests ON requests USING (tenant_id = current_setting('app.current_tenant', true)); GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ml_intern; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ml_intern;