GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
41
scripts/migrations/006_add_tenant_frontend_url.sql
Normal file
41
scripts/migrations/006_add_tenant_frontend_url.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
-- Add frontend_url column to tenants table
|
||||
-- Migration: 006_add_tenant_frontend_url
|
||||
-- Date: October 6, 2025
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Add frontend_url column if it doesn't exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'tenants'
|
||||
AND column_name = 'frontend_url'
|
||||
) THEN
|
||||
ALTER TABLE tenants ADD COLUMN frontend_url VARCHAR(255);
|
||||
RAISE NOTICE 'Added frontend_url column to tenants table';
|
||||
ELSE
|
||||
RAISE NOTICE 'Column frontend_url already exists in tenants table';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Mark migration as applied in Alembic version table (if it exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'alembic_version') THEN
|
||||
INSERT INTO alembic_version (version_num)
|
||||
VALUES ('006_frontend_url')
|
||||
ON CONFLICT (version_num) DO NOTHING;
|
||||
RAISE NOTICE 'Marked migration in alembic_version table';
|
||||
ELSE
|
||||
RAISE NOTICE 'No alembic_version table found (skipping)';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- Verify column was added
|
||||
\echo 'Migration 006_add_tenant_frontend_url completed successfully'
|
||||
@@ -0,0 +1,54 @@
|
||||
-- Remove ip_address column from password_reset_rate_limits
|
||||
-- Migration: 008_remove_ip_address_from_rate_limits
|
||||
-- Date: October 7, 2025
|
||||
-- Database: gt2_admin (Control Panel)
|
||||
--
|
||||
-- Description:
|
||||
-- Removes ip_address column that was incorrectly added by Alembic auto-migration
|
||||
-- Application only uses email-based rate limiting, not IP-based
|
||||
--
|
||||
-- Usage:
|
||||
-- psql -U postgres -d gt2_admin -f 008_remove_ip_address_from_rate_limits.sql
|
||||
--
|
||||
-- OR via Docker:
|
||||
-- docker exec -i gentwo-controlpanel-postgres psql -U postgres -d gt2_admin < 008_remove_ip_address_from_rate_limits.sql
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Remove ip_address column if it exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'password_reset_rate_limits'
|
||||
AND column_name = 'ip_address'
|
||||
) THEN
|
||||
ALTER TABLE password_reset_rate_limits DROP COLUMN ip_address CASCADE;
|
||||
RAISE NOTICE 'Removed ip_address column from password_reset_rate_limits';
|
||||
ELSE
|
||||
RAISE NOTICE 'Column ip_address does not exist, skipping';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Mark migration as applied in Alembic version table (if it exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'alembic_version') THEN
|
||||
INSERT INTO alembic_version (version_num)
|
||||
VALUES ('008_remove_ip')
|
||||
ON CONFLICT (version_num) DO NOTHING;
|
||||
RAISE NOTICE 'Marked migration in alembic_version table';
|
||||
ELSE
|
||||
RAISE NOTICE 'No alembic_version table found (skipping)';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- Verify table structure
|
||||
\d password_reset_rate_limits
|
||||
|
||||
\echo 'Migration 008_remove_ip_address_from_rate_limits completed successfully'
|
||||
42
scripts/migrations/009_add_tfa_schema.sql
Normal file
42
scripts/migrations/009_add_tfa_schema.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
-- Migration 009: Add Two-Factor Authentication Schema
|
||||
-- Creates TFA fields in users table and supporting tables for rate limiting and token management
|
||||
|
||||
-- Add TFA fields to users table
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS tfa_enabled BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS tfa_secret TEXT;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS tfa_required BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- Add indexes for query optimization
|
||||
CREATE INDEX IF NOT EXISTS ix_users_tfa_enabled ON users(tfa_enabled);
|
||||
CREATE INDEX IF NOT EXISTS ix_users_tfa_required ON users(tfa_required);
|
||||
|
||||
-- Create TFA verification rate limits table
|
||||
CREATE TABLE IF NOT EXISTS tfa_verification_rate_limits (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
request_count INTEGER NOT NULL DEFAULT 1,
|
||||
window_start TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
window_end TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_tfa_verification_rate_limits_user_id ON tfa_verification_rate_limits(user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_tfa_verification_rate_limits_window_end ON tfa_verification_rate_limits(window_end);
|
||||
|
||||
-- Create used temp tokens table for replay prevention
|
||||
CREATE TABLE IF NOT EXISTS used_temp_tokens (
|
||||
id SERIAL PRIMARY KEY,
|
||||
token_id VARCHAR(255) NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
user_email VARCHAR(255),
|
||||
tfa_configured BOOLEAN,
|
||||
qr_code_uri TEXT,
|
||||
manual_entry_key VARCHAR(255),
|
||||
temp_token TEXT,
|
||||
used_at TIMESTAMP WITH TIME ZONE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ix_used_temp_tokens_token_id ON used_temp_tokens(token_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_used_temp_tokens_expires_at ON used_temp_tokens(expires_at);
|
||||
167
scripts/migrations/010_update_model_context_windows.sql
Normal file
167
scripts/migrations/010_update_model_context_windows.sql
Normal file
@@ -0,0 +1,167 @@
|
||||
-- Migration 010: Update Model Context Windows and Max Tokens
|
||||
-- Ensures all models in model_configs have proper context_window and max_tokens set
|
||||
|
||||
-- Update models with missing context_window and max_tokens based on deployment configs
|
||||
-- Reference: scripts/seed/groq-models.sql and actual Groq API specifications
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
updated_count INTEGER := 0;
|
||||
BEGIN
|
||||
-- LLaMA 3.1 8B Instant
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 131072,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'llama-3.1-8b-instant'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for llama-3.1-8b-instant', updated_count;
|
||||
END IF;
|
||||
|
||||
-- LLaMA 3.3 70B Versatile
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 32768,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'llama-3.3-70b-versatile'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for llama-3.3-70b-versatile', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Groq Compound
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 8192,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'groq/compound'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for groq/compound', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Groq Compound Mini
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 8192,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'groq/compound-mini'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for groq/compound-mini', updated_count;
|
||||
END IF;
|
||||
|
||||
-- GPT OSS 120B
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 65536,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'openai/gpt-oss-120b'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for openai/gpt-oss-120b', updated_count;
|
||||
END IF;
|
||||
|
||||
-- GPT OSS 20B
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 65536,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'openai/gpt-oss-20b'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for openai/gpt-oss-20b', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Meta LLaMA Guard 4 12B
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 1024,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'meta-llama/llama-guard-4-12b'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for meta-llama/llama-guard-4-12b', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Meta LLaMA 4 Maverick 17B
|
||||
UPDATE model_configs
|
||||
SET context_window = 131072,
|
||||
max_tokens = 8192,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'meta-llama/llama-4-maverick-17b-128e-instruct'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for meta-llama/llama-4-maverick-17b-128e-instruct', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Moonshot AI Kimi K2 (checking for common variations)
|
||||
UPDATE model_configs
|
||||
SET context_window = 262144,
|
||||
max_tokens = 16384,
|
||||
updated_at = NOW()
|
||||
WHERE model_id IN ('moonshotai/kimi-k2-instruct-0905', 'kimi-k2-instruct-0905', 'moonshotai/kimi-k2')
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for moonshotai/kimi-k2-instruct-0905', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Whisper Large v3
|
||||
UPDATE model_configs
|
||||
SET context_window = 0,
|
||||
max_tokens = 0,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'whisper-large-v3'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for whisper-large-v3', updated_count;
|
||||
END IF;
|
||||
|
||||
-- Whisper Large v3 Turbo
|
||||
UPDATE model_configs
|
||||
SET context_window = 0,
|
||||
max_tokens = 0,
|
||||
updated_at = NOW()
|
||||
WHERE model_id = 'whisper-large-v3-turbo'
|
||||
AND (context_window IS NULL OR max_tokens IS NULL);
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
IF updated_count > 0 THEN
|
||||
RAISE NOTICE 'Updated % records for whisper-large-v3-turbo', updated_count;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'Migration 010 completed: Updated model context windows and max tokens';
|
||||
END $$;
|
||||
|
||||
-- Display updated models
|
||||
SELECT
|
||||
model_id,
|
||||
name,
|
||||
provider,
|
||||
model_type,
|
||||
context_window,
|
||||
max_tokens
|
||||
FROM model_configs
|
||||
WHERE provider = 'groq' OR model_id LIKE '%moonshot%' OR model_id LIKE '%kimi%'
|
||||
ORDER BY model_id;
|
||||
70
scripts/migrations/011_add_system_management_tables.sql
Normal file
70
scripts/migrations/011_add_system_management_tables.sql
Normal file
@@ -0,0 +1,70 @@
|
||||
-- Migration 011: Add system management tables for version tracking, updates, and backups
|
||||
-- Idempotent: Uses CREATE TABLE IF NOT EXISTS and exception handling for enums
|
||||
|
||||
-- Create enum types (safe to recreate)
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE updatestatus AS ENUM ('pending', 'in_progress', 'completed', 'failed', 'rolled_back');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE backuptype AS ENUM ('manual', 'pre_update', 'scheduled');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- System versions table - tracks installed system versions
|
||||
CREATE TABLE IF NOT EXISTS system_versions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(36) NOT NULL UNIQUE DEFAULT gen_random_uuid()::text,
|
||||
version VARCHAR(50) NOT NULL,
|
||||
installed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
installed_by VARCHAR(255),
|
||||
is_current BOOLEAN NOT NULL DEFAULT true,
|
||||
release_notes TEXT,
|
||||
git_commit VARCHAR(40)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_system_versions_id ON system_versions(id);
|
||||
CREATE INDEX IF NOT EXISTS ix_system_versions_version ON system_versions(version);
|
||||
|
||||
-- Update jobs table - tracks update execution
|
||||
CREATE TABLE IF NOT EXISTS update_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(36) NOT NULL UNIQUE DEFAULT gen_random_uuid()::text,
|
||||
target_version VARCHAR(50) NOT NULL,
|
||||
status updatestatus NOT NULL DEFAULT 'pending',
|
||||
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
current_stage VARCHAR(100),
|
||||
logs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
error_message TEXT,
|
||||
backup_id INTEGER,
|
||||
started_by VARCHAR(255),
|
||||
rollback_reason TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_update_jobs_id ON update_jobs(id);
|
||||
CREATE INDEX IF NOT EXISTS ix_update_jobs_uuid ON update_jobs(uuid);
|
||||
CREATE INDEX IF NOT EXISTS ix_update_jobs_status ON update_jobs(status);
|
||||
|
||||
-- Backup records table - tracks system backups
|
||||
CREATE TABLE IF NOT EXISTS backup_records (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(36) NOT NULL UNIQUE DEFAULT gen_random_uuid()::text,
|
||||
backup_type backuptype NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
size_bytes BIGINT,
|
||||
location VARCHAR(500) NOT NULL,
|
||||
version VARCHAR(50),
|
||||
components JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
checksum VARCHAR(64),
|
||||
created_by VARCHAR(255),
|
||||
description TEXT,
|
||||
is_valid BOOLEAN NOT NULL DEFAULT true,
|
||||
expires_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_backup_records_id ON backup_records(id);
|
||||
CREATE INDEX IF NOT EXISTS ix_backup_records_uuid ON backup_records(uuid);
|
||||
|
||||
-- Seed initial version (idempotent - only inserts if no current version exists)
|
||||
INSERT INTO system_versions (uuid, version, installed_by, is_current)
|
||||
SELECT 'initial-version-uuid', 'v2.0.31', 'system', true
|
||||
WHERE NOT EXISTS (SELECT 1 FROM system_versions WHERE is_current = true);
|
||||
36
scripts/migrations/012_add_optics_enabled.sql
Normal file
36
scripts/migrations/012_add_optics_enabled.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- T008_optics_feature.sql
|
||||
-- Add Optics cost tracking feature toggle for tenants
|
||||
-- This enables the Optics tab in tenant observability for cost visibility
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Add optics_enabled column to tenants table in control panel database
|
||||
-- This column controls whether the Optics cost tracking tab is visible for a tenant
|
||||
ALTER TABLE public.tenants
|
||||
ADD COLUMN IF NOT EXISTS optics_enabled BOOLEAN DEFAULT FALSE;
|
||||
|
||||
-- Add comment for documentation
|
||||
COMMENT ON COLUMN public.tenants.optics_enabled IS
|
||||
'Enable Optics cost tracking tab in tenant observability dashboard';
|
||||
|
||||
-- Update existing test tenant to have optics enabled for demo purposes
|
||||
UPDATE public.tenants
|
||||
SET optics_enabled = TRUE
|
||||
WHERE domain = 'test-company';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- Log completion
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '=== T008 OPTICS FEATURE MIGRATION ===';
|
||||
RAISE NOTICE 'Added optics_enabled column to tenants table';
|
||||
RAISE NOTICE 'Default: FALSE (disabled)';
|
||||
RAISE NOTICE 'Test tenant (test-company): enabled';
|
||||
RAISE NOTICE '=====================================';
|
||||
END $$;
|
||||
|
||||
-- Rollback (if needed):
|
||||
-- BEGIN;
|
||||
-- ALTER TABLE public.tenants DROP COLUMN IF EXISTS optics_enabled;
|
||||
-- COMMIT;
|
||||
15
scripts/migrations/013_rename_cost_columns.sql
Normal file
15
scripts/migrations/013_rename_cost_columns.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Migration 013: Rename cost columns from per_1k to per_million
|
||||
-- This is idempotent - only runs if old columns exist
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'model_configs'
|
||||
AND column_name = 'cost_per_1k_input') THEN
|
||||
ALTER TABLE model_configs RENAME COLUMN cost_per_1k_input TO cost_per_million_input;
|
||||
ALTER TABLE model_configs RENAME COLUMN cost_per_1k_output TO cost_per_million_output;
|
||||
RAISE NOTICE 'Renamed cost columns from per_1k to per_million';
|
||||
ELSE
|
||||
RAISE NOTICE 'Cost columns already renamed or do not exist';
|
||||
END IF;
|
||||
END $$;
|
||||
108
scripts/migrations/014_backfill_groq_pricing.sql
Normal file
108
scripts/migrations/014_backfill_groq_pricing.sql
Normal file
@@ -0,0 +1,108 @@
|
||||
-- Migration 014: Backfill missing Groq model pricing
|
||||
-- Updates models with NULL or 0 pricing to use standard Groq rates
|
||||
-- Prices sourced from https://groq.com/pricing (verified Dec 2, 2025)
|
||||
-- Idempotent - only updates rows that need fixing
|
||||
|
||||
-- Groq Compound (estimated: includes underlying model + tool costs)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 2.50, cost_per_million_output = 6.00, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%compound'
|
||||
AND model_id NOT LIKE '%mini%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- Groq Compound Mini (estimated: includes underlying model + tool costs)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 1.00, cost_per_million_output = 2.50, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%compound-mini%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- LLaMA 3.1 8B Instant
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.05, cost_per_million_output = 0.08, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-3.1-8b-instant%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- LLaMA 3.3 70B Versatile
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.59, cost_per_million_output = 0.79, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-3.3-70b-versatile%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- Meta Llama 4 Maverick 17B (17Bx128E MoE)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.20, cost_per_million_output = 0.60, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-4-maverick%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- Meta Llama 4 Scout 17B (17Bx16E MoE)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.11, cost_per_million_output = 0.34, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-4-scout%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- LLaMA Guard 4 12B
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.20, cost_per_million_output = 0.20, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-guard%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- Moonshot AI Kimi K2 (1T params, 256k context)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 1.00, cost_per_million_output = 3.00, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%kimi-k2%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- OpenAI GPT OSS 120B 128k
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.15, cost_per_million_output = 0.60, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%gpt-oss-120b%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- OpenAI GPT OSS 20B 128k
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.075, cost_per_million_output = 0.30, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%gpt-oss-20b%'
|
||||
AND model_id NOT LIKE '%safeguard%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- OpenAI GPT OSS Safeguard 20B
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.075, cost_per_million_output = 0.30, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%gpt-oss-safeguard%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- Qwen3 32B 131k
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.29, cost_per_million_output = 0.59, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%qwen3-32b%'
|
||||
AND (cost_per_million_input IS NULL OR cost_per_million_input = 0
|
||||
OR cost_per_million_output IS NULL OR cost_per_million_output = 0);
|
||||
|
||||
-- Report results
|
||||
SELECT model_id, name, cost_per_million_input, cost_per_million_output
|
||||
FROM model_configs
|
||||
WHERE provider = 'groq'
|
||||
ORDER BY model_id;
|
||||
84
scripts/migrations/015_update_groq_pricing_dec_2025.sql
Normal file
84
scripts/migrations/015_update_groq_pricing_dec_2025.sql
Normal file
@@ -0,0 +1,84 @@
|
||||
-- Migration 015: Update Groq model pricing to December 2025 rates
|
||||
-- Source: https://groq.com/pricing (verified Dec 2, 2025)
|
||||
-- This migration updates ALL pricing values (not just NULL/0)
|
||||
|
||||
-- GPT OSS 120B 128k: Was $1.20/$1.20, now $0.15/$0.60
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.15, cost_per_million_output = 0.60, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%gpt-oss-120b%'
|
||||
AND model_id NOT LIKE '%safeguard%';
|
||||
|
||||
-- GPT OSS 20B 128k: Was $0.30/$0.30, now $0.075/$0.30
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.075, cost_per_million_output = 0.30, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%gpt-oss-20b%'
|
||||
AND model_id NOT LIKE '%safeguard%';
|
||||
|
||||
-- GPT OSS Safeguard 20B: $0.075/$0.30
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.075, cost_per_million_output = 0.30, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%gpt-oss-safeguard%';
|
||||
|
||||
-- Llama 4 Maverick (17Bx128E): Was $0.15/$0.25, now $0.20/$0.60
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.20, cost_per_million_output = 0.60, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-4-maverick%';
|
||||
|
||||
-- Llama 4 Scout (17Bx16E): $0.11/$0.34 (new model)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.11, cost_per_million_output = 0.34, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-4-scout%';
|
||||
|
||||
-- Kimi K2: Was $0.30/$0.50, now $1.00/$3.00
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 1.00, cost_per_million_output = 3.00, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%kimi-k2%';
|
||||
|
||||
-- Llama Guard 4 12B: $0.20/$0.20
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.20, cost_per_million_output = 0.20, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-guard%';
|
||||
|
||||
-- Groq Compound: Was $2.00/$2.00, now $2.50/$6.00 (estimated with tool costs)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 2.50, cost_per_million_output = 6.00, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%compound%'
|
||||
AND model_id NOT LIKE '%mini%';
|
||||
|
||||
-- Groq Compound Mini: Was $0.80/$0.80, now $1.00/$2.50 (estimated with tool costs)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 1.00, cost_per_million_output = 2.50, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%compound-mini%';
|
||||
|
||||
-- Qwen3 32B 131k: $0.29/$0.59 (new model)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.29, cost_per_million_output = 0.59, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%qwen3-32b%';
|
||||
|
||||
-- LLaMA 3.1 8B Instant: $0.05/$0.08 (unchanged, ensure consistency)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.05, cost_per_million_output = 0.08, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-3.1-8b-instant%';
|
||||
|
||||
-- LLaMA 3.3 70B Versatile: $0.59/$0.79 (unchanged, ensure consistency)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.59, cost_per_million_output = 0.79, updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%llama-3.3-70b-versatile%';
|
||||
|
||||
-- Report updated pricing
|
||||
SELECT model_id, name, cost_per_million_input as input_per_1m, cost_per_million_output as output_per_1m
|
||||
FROM model_configs
|
||||
WHERE provider = 'groq'
|
||||
ORDER BY cost_per_million_input DESC, model_id;
|
||||
24
scripts/migrations/016_add_is_compound_column.sql
Normal file
24
scripts/migrations/016_add_is_compound_column.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- Migration 016: Add is_compound column to model_configs
|
||||
-- Required for Compound model pass-through pricing
|
||||
-- Date: 2025-12-02
|
||||
|
||||
-- Add column if it doesn't exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'model_configs' AND column_name = 'is_compound'
|
||||
) THEN
|
||||
ALTER TABLE public.model_configs
|
||||
ADD COLUMN is_compound BOOLEAN DEFAULT FALSE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Mark compound models
|
||||
UPDATE public.model_configs
|
||||
SET is_compound = true
|
||||
WHERE model_id LIKE '%compound%'
|
||||
AND is_compound IS NOT TRUE;
|
||||
|
||||
-- Verify
|
||||
SELECT model_id, is_compound FROM public.model_configs WHERE model_id LIKE '%compound%';
|
||||
31
scripts/migrations/017_fix_compound_pricing.sql
Normal file
31
scripts/migrations/017_fix_compound_pricing.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Migration 017: Fix Compound model pricing with correct blended rates
|
||||
-- Source: https://groq.com/pricing (Dec 2025) + actual API response analysis
|
||||
--
|
||||
-- Compound uses GPT-OSS-120B ($0.15/$0.60) + Llama 4 Scout ($0.11/$0.34)
|
||||
-- Blended 50/50: ($0.15+$0.11)/2 = $0.13 input, ($0.60+$0.34)/2 = $0.47 output
|
||||
--
|
||||
-- Compound Mini uses GPT-OSS-120B ($0.15/$0.60) + Llama 3.3 70B ($0.59/$0.79)
|
||||
-- Blended 50/50: ($0.15+$0.59)/2 = $0.37 input, ($0.60+$0.79)/2 = $0.695 output
|
||||
|
||||
-- Fix Compound pricing (was incorrectly set to $2.50/$6.00)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.13,
|
||||
cost_per_million_output = 0.47,
|
||||
updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%compound%'
|
||||
AND model_id NOT LIKE '%mini%';
|
||||
|
||||
-- Fix Compound Mini pricing (was incorrectly set to $1.00/$2.50)
|
||||
UPDATE model_configs
|
||||
SET cost_per_million_input = 0.37,
|
||||
cost_per_million_output = 0.695,
|
||||
updated_at = NOW()
|
||||
WHERE provider = 'groq'
|
||||
AND model_id LIKE '%compound-mini%';
|
||||
|
||||
-- Report updated pricing
|
||||
SELECT model_id, name, cost_per_million_input as input_per_1m, cost_per_million_output as output_per_1m
|
||||
FROM model_configs
|
||||
WHERE provider = 'groq' AND model_id LIKE '%compound%'
|
||||
ORDER BY model_id;
|
||||
19
scripts/migrations/018_add_budget_storage_pricing.sql
Normal file
19
scripts/migrations/018_add_budget_storage_pricing.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Migration 018: Add budget and storage pricing fields to tenants
|
||||
-- Supports #234 (Budget Limits), #218 (Storage Tier Pricing)
|
||||
-- Updated: Removed warm tier, changed cold tier to allocation-based model
|
||||
|
||||
-- Budget fields
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS monthly_budget_cents INTEGER DEFAULT NULL;
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS budget_warning_threshold INTEGER DEFAULT 80;
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS budget_critical_threshold INTEGER DEFAULT 90;
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS budget_enforcement_enabled BOOLEAN DEFAULT true;
|
||||
|
||||
-- Hot tier storage pricing overrides (NULL = use system defaults)
|
||||
-- Default: $0.15/GiB/month (in cents per MiB: ~0.0146 cents/MiB)
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS storage_price_dataset_hot DECIMAL(10,4) DEFAULT NULL;
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS storage_price_conversation_hot DECIMAL(10,4) DEFAULT NULL;
|
||||
|
||||
-- Cold tier: Allocation-based model
|
||||
-- Monthly cost = allocated_tibs × price_per_tib
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS cold_storage_allocated_tibs DECIMAL(10,4) DEFAULT NULL;
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS cold_storage_price_per_tib DECIMAL(10,2) DEFAULT 10.00;
|
||||
17
scripts/migrations/019_add_embedding_usage.sql
Normal file
17
scripts/migrations/019_add_embedding_usage.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Migration 019: Add embedding usage tracking table
|
||||
-- Supports #241 (Embedding Model Pricing)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.embedding_usage_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tenant_id VARCHAR(100) NOT NULL,
|
||||
user_id VARCHAR(100) NOT NULL,
|
||||
tokens_used INTEGER NOT NULL,
|
||||
embedding_count INTEGER NOT NULL,
|
||||
model VARCHAR(100) DEFAULT 'BAAI/bge-m3',
|
||||
cost_cents DECIMAL(10,4) NOT NULL,
|
||||
request_id VARCHAR(100),
|
||||
timestamp TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_embedding_usage_tenant_timestamp
|
||||
ON public.embedding_usage_logs(tenant_id, timestamp);
|
||||
224
scripts/migrations/020_migrate_env_api_keys.py
Normal file
224
scripts/migrations/020_migrate_env_api_keys.py
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migration 020: Import GROQ_API_KEY from environment to database
|
||||
|
||||
Migrates API keys from .env file to encrypted database storage for test-company tenant.
|
||||
This is part of the move away from environment variables for API keys (#158, #219).
|
||||
|
||||
Idempotency: Checks if key already exists before importing
|
||||
Target: test-company tenant only (as specified in requirements)
|
||||
|
||||
Usage:
|
||||
python scripts/migrations/020_migrate_env_api_keys.py
|
||||
|
||||
Environment variables required:
|
||||
- GROQ_API_KEY: The Groq API key to migrate (optional - skips if not set)
|
||||
- API_KEY_ENCRYPTION_KEY: Fernet encryption key (auto-generated if not set)
|
||||
- CONTROL_PANEL_DB_HOST: Database host (default: localhost)
|
||||
- CONTROL_PANEL_DB_PORT: Database port (default: 5432)
|
||||
- CONTROL_PANEL_DB_NAME: Database name (default: gt2_admin)
|
||||
- CONTROL_PANEL_DB_USER: Database user (default: postgres)
|
||||
- ADMIN_POSTGRES_PASSWORD: Database password
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from cryptography.fernet import Fernet
|
||||
import psycopg2
|
||||
except ImportError as e:
|
||||
print(f"Missing required package: {e}")
|
||||
print("Run: pip install cryptography psycopg2-binary")
|
||||
sys.exit(1)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Migration constants
|
||||
TARGET_TENANT_DOMAIN = "test-company"
|
||||
PROVIDER = "groq"
|
||||
MIGRATION_ID = "020"
|
||||
|
||||
|
||||
def get_db_connection():
|
||||
"""Get database connection using environment variables or defaults"""
|
||||
try:
|
||||
conn = psycopg2.connect(
|
||||
host=os.getenv("CONTROL_PANEL_DB_HOST", "localhost"),
|
||||
port=os.getenv("CONTROL_PANEL_DB_PORT", "5432"),
|
||||
database=os.getenv("CONTROL_PANEL_DB_NAME", "gt2_admin"),
|
||||
user=os.getenv("CONTROL_PANEL_DB_USER", "postgres"),
|
||||
password=os.getenv("ADMIN_POSTGRES_PASSWORD", "dev_password_change_in_prod")
|
||||
)
|
||||
return conn
|
||||
except psycopg2.Error as e:
|
||||
logger.error(f"Database connection failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def get_encryption_key() -> str:
|
||||
"""Get or generate Fernet encryption key"""
|
||||
key = os.getenv("API_KEY_ENCRYPTION_KEY")
|
||||
if not key:
|
||||
# Generate a new key - in production this should be persisted
|
||||
key = Fernet.generate_key().decode()
|
||||
logger.warning("Generated new API_KEY_ENCRYPTION_KEY - add to .env for persistence:")
|
||||
logger.warning(f" API_KEY_ENCRYPTION_KEY={key}")
|
||||
return key
|
||||
|
||||
|
||||
def check_env_key_exists() -> str | None:
|
||||
"""Check if GROQ_API_KEY environment variable exists and is valid"""
|
||||
groq_key = os.getenv("GROQ_API_KEY")
|
||||
|
||||
# Skip placeholder values
|
||||
placeholder_values = [
|
||||
"gsk_your_actual_groq_api_key_here",
|
||||
"gsk_placeholder",
|
||||
"",
|
||||
None
|
||||
]
|
||||
|
||||
if groq_key in placeholder_values:
|
||||
logger.info("GROQ_API_KEY not set or is placeholder - skipping migration")
|
||||
return None
|
||||
|
||||
# Validate format
|
||||
if not groq_key.startswith("gsk_"):
|
||||
logger.warning(f"GROQ_API_KEY has invalid format (should start with 'gsk_')")
|
||||
return None
|
||||
|
||||
return groq_key
|
||||
|
||||
|
||||
def get_tenant_id(conn, domain: str) -> int | None:
|
||||
"""Get tenant ID by domain"""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT id FROM tenants WHERE domain = %s AND deleted_at IS NULL",
|
||||
(domain,)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
|
||||
def check_db_key_exists(conn, tenant_id: int) -> bool:
|
||||
"""Check if Groq key already exists in database for tenant"""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT api_keys FROM tenants WHERE id = %s",
|
||||
(tenant_id,)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row and row[0]:
|
||||
api_keys = row[0] if isinstance(row[0], dict) else json.loads(row[0])
|
||||
if PROVIDER in api_keys and api_keys[PROVIDER].get("key"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def migrate_api_key(conn, tenant_id: int, api_key: str, encryption_key: str) -> bool:
|
||||
"""Encrypt and store API key in database"""
|
||||
try:
|
||||
cipher = Fernet(encryption_key.encode())
|
||||
encrypted_key = cipher.encrypt(api_key.encode()).decode()
|
||||
|
||||
api_keys_data = {
|
||||
PROVIDER: {
|
||||
"key": encrypted_key,
|
||||
"secret": None,
|
||||
"enabled": True,
|
||||
"metadata": {
|
||||
"migrated_from": "environment",
|
||||
"migration_id": MIGRATION_ID,
|
||||
"migration_date": datetime.utcnow().isoformat()
|
||||
},
|
||||
"updated_at": datetime.utcnow().isoformat(),
|
||||
"updated_by": f"migration-{MIGRATION_ID}"
|
||||
}
|
||||
}
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE tenants
|
||||
SET api_keys = %s::jsonb,
|
||||
api_key_encryption_version = 'v1',
|
||||
updated_at = NOW()
|
||||
WHERE id = %s
|
||||
""",
|
||||
(json.dumps(api_keys_data), tenant_id)
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
logger.error(f"Failed to migrate API key: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def run_migration() -> bool:
|
||||
"""Main migration logic"""
|
||||
logger.info(f"=== Migration {MIGRATION_ID}: Import GROQ_API_KEY from environment ===")
|
||||
|
||||
# Step 1: Check if env var exists
|
||||
groq_key = check_env_key_exists()
|
||||
if not groq_key:
|
||||
logger.info("Migration skipped: No valid GROQ_API_KEY in environment")
|
||||
return True # Not an error - just nothing to migrate
|
||||
|
||||
logger.info(f"Found GROQ_API_KEY in environment (length: {len(groq_key)})")
|
||||
|
||||
# Step 2: Connect to database
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
logger.info("Connected to database")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to database: {e}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Step 3: Get tenant ID
|
||||
tenant_id = get_tenant_id(conn, TARGET_TENANT_DOMAIN)
|
||||
if not tenant_id:
|
||||
logger.warning(f"Tenant '{TARGET_TENANT_DOMAIN}' not found - skipping migration")
|
||||
logger.info("This is expected for fresh installs before tenant creation")
|
||||
return True
|
||||
|
||||
logger.info(f"Found tenant '{TARGET_TENANT_DOMAIN}' with ID: {tenant_id}")
|
||||
|
||||
# Step 4: Check if DB key already exists (idempotency)
|
||||
if check_db_key_exists(conn, tenant_id):
|
||||
logger.info("Migration already complete - Groq key exists in database")
|
||||
return True
|
||||
|
||||
# Step 5: Get/generate encryption key
|
||||
encryption_key = get_encryption_key()
|
||||
|
||||
# Step 6: Migrate the key
|
||||
logger.info(f"Migrating GROQ_API_KEY to database for tenant {tenant_id}...")
|
||||
if migrate_api_key(conn, tenant_id, groq_key, encryption_key):
|
||||
logger.info(f"=== Migration {MIGRATION_ID} completed successfully ===")
|
||||
logger.info("The GROQ_API_KEY env var can now be removed from docker-compose.yml")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Migration {MIGRATION_ID} failed")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Migration failed with error: {e}")
|
||||
return False
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_migration()
|
||||
sys.exit(0 if success else 1)
|
||||
432
scripts/migrations/021_add_nvidia_models.sql
Normal file
432
scripts/migrations/021_add_nvidia_models.sql
Normal file
@@ -0,0 +1,432 @@
|
||||
-- Migration: 021_add_nvidia_models.sql
|
||||
-- Description: Add NVIDIA NIM models to model_configs table
|
||||
-- Date: 2025-12-08
|
||||
-- Issue: #266 - Add NVIDIA API endpoint support
|
||||
-- Reference: https://build.nvidia.com/models
|
||||
|
||||
-- NVIDIA NIM Models (build.nvidia.com)
|
||||
-- Pricing: Estimated based on third-party providers and model size (Dec 2025)
|
||||
-- Models selected: SOTA reasoning, coding, and general-purpose LLMs
|
||||
|
||||
INSERT INTO model_configs (
|
||||
model_id,
|
||||
name,
|
||||
version,
|
||||
provider,
|
||||
model_type,
|
||||
endpoint,
|
||||
context_window,
|
||||
max_tokens,
|
||||
cost_per_million_input,
|
||||
cost_per_million_output,
|
||||
capabilities,
|
||||
is_active,
|
||||
description,
|
||||
created_at,
|
||||
updated_at,
|
||||
request_count,
|
||||
error_count,
|
||||
success_rate,
|
||||
avg_latency_ms,
|
||||
health_status
|
||||
)
|
||||
VALUES
|
||||
-- ==========================================
|
||||
-- NVIDIA Llama Nemotron Family (Flagship)
|
||||
-- ==========================================
|
||||
|
||||
-- Llama 3.3 Nemotron Super 49B v1 - Latest flagship reasoning model
|
||||
(
|
||||
'nvidia/llama-3.3-nemotron-super-49b-v1',
|
||||
'NVIDIA Llama 3.3 Nemotron Super 49B',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
131072,
|
||||
8192,
|
||||
0.5,
|
||||
1.5,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true}',
|
||||
true,
|
||||
'NVIDIA flagship reasoning model - best accuracy/throughput on single GPU',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
-- Llama 3.1 Nemotron Ultra 253B - Maximum accuracy
|
||||
(
|
||||
'nvidia/llama-3.1-nemotron-ultra-253b-v1',
|
||||
'NVIDIA Llama 3.1 Nemotron Ultra 253B',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
131072,
|
||||
8192,
|
||||
0.6,
|
||||
1.8,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true}',
|
||||
true,
|
||||
'Maximum agentic accuracy for scientific reasoning, math, and coding',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
-- Nemotron Nano 8B - Edge/PC deployment
|
||||
(
|
||||
'nvidia/llama-3.1-nemotron-nano-8b-v1',
|
||||
'NVIDIA Llama 3.1 Nemotron Nano 8B',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
131072,
|
||||
8192,
|
||||
0.02,
|
||||
0.06,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'Cost-effective model optimized for edge devices and low latency',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- Meta Llama 3.3 (via NVIDIA NIM)
|
||||
-- ==========================================
|
||||
|
||||
-- Llama 3.3 70B Instruct - Latest Llama
|
||||
(
|
||||
'nvidia/meta-llama-3.3-70b-instruct',
|
||||
'NVIDIA Meta Llama 3.3 70B Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
4096,
|
||||
0.13,
|
||||
0.4,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'Latest Meta Llama 3.3 - excellent for instruction following',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- DeepSeek Models (via NVIDIA NIM)
|
||||
-- ==========================================
|
||||
|
||||
-- DeepSeek V3 - Hybrid inference with Think/Non-Think modes
|
||||
(
|
||||
'nvidia/deepseek-ai-deepseek-v3',
|
||||
'NVIDIA DeepSeek V3',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
8192,
|
||||
0.5,
|
||||
1.5,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true}',
|
||||
true,
|
||||
'Hybrid LLM with Think/Non-Think modes, 128K context, strong tool use',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
-- DeepSeek R1 - Enhanced reasoning
|
||||
(
|
||||
'nvidia/deepseek-ai-deepseek-r1',
|
||||
'NVIDIA DeepSeek R1',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
8192,
|
||||
0.6,
|
||||
2.4,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true}',
|
||||
true,
|
||||
'Enhanced reasoning model - reduced hallucination, strong math/coding',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- Kimi K2 (Moonshot AI via NVIDIA NIM)
|
||||
-- ==========================================
|
||||
|
||||
(
|
||||
'nvidia/moonshot-ai-kimi-k2-instruct',
|
||||
'NVIDIA Kimi K2 Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
8192,
|
||||
0.4,
|
||||
1.2,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true}',
|
||||
true,
|
||||
'Long context window with enhanced reasoning capabilities',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- Mistral Models (via NVIDIA NIM)
|
||||
-- ==========================================
|
||||
|
||||
-- Mistral Large 3 - State-of-the-art MoE
|
||||
(
|
||||
'nvidia/mistralai-mistral-large-3-instruct',
|
||||
'NVIDIA Mistral Large 3 Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
8192,
|
||||
0.8,
|
||||
2.4,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'State-of-the-art general purpose MoE model',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- Qwen Models (via NVIDIA NIM)
|
||||
-- ==========================================
|
||||
|
||||
-- Qwen 3 - Ultra-long context (131K with YaRN extension)
|
||||
(
|
||||
'nvidia/qwen-qwen3-235b-a22b-fp8-instruct',
|
||||
'NVIDIA Qwen 3 235B Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
131072,
|
||||
8192,
|
||||
0.7,
|
||||
2.1,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'Ultra-long context AI with strong multilingual support',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- Meta Llama 3.1 (via NVIDIA NIM)
|
||||
-- ==========================================
|
||||
|
||||
-- Llama 3.1 405B - Largest open model
|
||||
(
|
||||
'nvidia/meta-llama-3.1-405b-instruct',
|
||||
'NVIDIA Meta Llama 3.1 405B Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
4096,
|
||||
1.0,
|
||||
3.0,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'Largest open-source LLM - exceptional quality across all tasks',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
-- Llama 3.1 70B
|
||||
(
|
||||
'nvidia/meta-llama-3.1-70b-instruct',
|
||||
'NVIDIA Meta Llama 3.1 70B Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
4096,
|
||||
0.13,
|
||||
0.4,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'Excellent balance of quality and speed',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
-- Llama 3.1 8B - Fast and efficient
|
||||
(
|
||||
'nvidia/meta-llama-3.1-8b-instruct',
|
||||
'NVIDIA Meta Llama 3.1 8B Instruct',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
4096,
|
||||
0.02,
|
||||
0.06,
|
||||
'{"streaming": true, "function_calling": true}',
|
||||
true,
|
||||
'Fast and cost-effective for simpler tasks',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
|
||||
-- ==========================================
|
||||
-- OpenAI GPT-OSS Models (via NVIDIA NIM)
|
||||
-- Released August 2025 - Apache 2.0 License
|
||||
-- ==========================================
|
||||
|
||||
-- GPT-OSS 120B via NVIDIA NIM - Production flagship, MoE architecture (117B params, 5.7B active)
|
||||
(
|
||||
'nvidia/openai-gpt-oss-120b',
|
||||
'NVIDIA OpenAI GPT-OSS 120B',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
8192,
|
||||
0.7,
|
||||
2.1,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true, "tool_use": true}',
|
||||
true,
|
||||
'OpenAI flagship open model via NVIDIA NIM - production-grade reasoning, fits single H100 GPU',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
),
|
||||
-- GPT-OSS 20B via NVIDIA NIM - Lightweight MoE for edge/local (21B params, 4B active)
|
||||
(
|
||||
'nvidia/openai-gpt-oss-20b',
|
||||
'NVIDIA OpenAI GPT-OSS 20B',
|
||||
'1.0',
|
||||
'nvidia',
|
||||
'llm',
|
||||
'https://integrate.api.nvidia.com/v1/chat/completions',
|
||||
128000,
|
||||
8192,
|
||||
0.15,
|
||||
0.45,
|
||||
'{"streaming": true, "function_calling": true, "reasoning": true, "tool_use": true}',
|
||||
true,
|
||||
'OpenAI lightweight open model via NVIDIA NIM - low latency, runs in 16GB VRAM',
|
||||
NOW(),
|
||||
NOW(),
|
||||
0,
|
||||
0,
|
||||
100.0,
|
||||
0,
|
||||
'unknown'
|
||||
)
|
||||
|
||||
ON CONFLICT (model_id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
version = EXCLUDED.version,
|
||||
provider = EXCLUDED.provider,
|
||||
endpoint = EXCLUDED.endpoint,
|
||||
context_window = EXCLUDED.context_window,
|
||||
max_tokens = EXCLUDED.max_tokens,
|
||||
cost_per_million_input = EXCLUDED.cost_per_million_input,
|
||||
cost_per_million_output = EXCLUDED.cost_per_million_output,
|
||||
capabilities = EXCLUDED.capabilities,
|
||||
is_active = EXCLUDED.is_active,
|
||||
description = EXCLUDED.description,
|
||||
updated_at = NOW();
|
||||
|
||||
-- Assign NVIDIA models to all existing tenants with 1000 RPM rate limits
|
||||
-- Note: model_config_id (UUID) is the foreign key, model_id kept for convenience
|
||||
INSERT INTO tenant_model_configs (tenant_id, model_config_id, model_id, is_enabled, priority, rate_limits, created_at, updated_at)
|
||||
SELECT
|
||||
t.id,
|
||||
m.id, -- UUID foreign key (auto-generated in model_configs)
|
||||
m.model_id, -- String identifier (kept for easier queries)
|
||||
true,
|
||||
5,
|
||||
'{"max_requests_per_hour": 1000, "max_tokens_per_request": 4000, "concurrent_requests": 5, "max_cost_per_hour": 10.0, "requests_per_minute": 1000, "tokens_per_minute": 100000, "max_concurrent": 10}'::json,
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM tenants t
|
||||
CROSS JOIN model_configs m
|
||||
WHERE m.provider = 'nvidia'
|
||||
ON CONFLICT (tenant_id, model_config_id) DO UPDATE SET
|
||||
rate_limits = EXCLUDED.rate_limits;
|
||||
|
||||
-- Log migration completion
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Migration 021: Added NVIDIA NIM models (Nemotron, Llama 3.3, DeepSeek, Kimi K2, Mistral, Qwen, OpenAI GPT-OSS) to model_configs and assigned to tenants';
|
||||
END $$;
|
||||
238
scripts/migrations/022_add_session_management.sql
Normal file
238
scripts/migrations/022_add_session_management.sql
Normal file
@@ -0,0 +1,238 @@
|
||||
-- Migration: 022_add_session_management.sql
|
||||
-- Description: Server-side session tracking for OWASP/NIST compliance
|
||||
-- Date: 2025-12-08
|
||||
-- Issue: #264 - Session timeout warning not appearing
|
||||
--
|
||||
-- Timeout Configuration:
|
||||
-- Idle Timeout: 4 hours (240 minutes) - covers meetings, lunch, context-switching
|
||||
-- Absolute Timeout: 8 hours (maximum session lifetime) - full work day
|
||||
-- Warning Threshold: 5 minutes before idle expiry
|
||||
|
||||
-- Active sessions table for server-side session tracking
|
||||
-- This is the authoritative source of truth for session validity,
|
||||
-- not the JWT expiration time alone.
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
session_token_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 of session token (never store plaintext)
|
||||
|
||||
-- Session timing (NIST SP 800-63B compliant)
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_activity_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
absolute_expires_at TIMESTAMP WITH TIME ZONE NOT NULL, -- 8 hours from creation
|
||||
|
||||
-- Session metadata for security auditing
|
||||
ip_address VARCHAR(45), -- IPv6 compatible (max 45 chars)
|
||||
user_agent TEXT,
|
||||
tenant_id INTEGER REFERENCES tenants(id),
|
||||
|
||||
-- Session state
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
revoked_at TIMESTAMP WITH TIME ZONE,
|
||||
revoke_reason VARCHAR(50), -- 'logout', 'idle_timeout', 'absolute_timeout', 'admin_revoke', 'password_change', 'cleanup_stale'
|
||||
ended_at TIMESTAMP WITH TIME ZONE, -- When session ended (any reason: logout, timeout, etc.)
|
||||
app_type VARCHAR(20) NOT NULL DEFAULT 'control_panel' -- 'control_panel' or 'tenant_app'
|
||||
);
|
||||
|
||||
-- Indexes for session lookup and cleanup
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(session_token_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_last_activity ON sessions(last_activity_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_absolute_expires ON sessions(absolute_expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_active ON sessions(is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_tenant_id ON sessions(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_ended_at ON sessions(ended_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_app_type ON sessions(app_type);
|
||||
|
||||
-- Function to clean up expired sessions (run periodically via cron or scheduled task)
|
||||
CREATE OR REPLACE FUNCTION cleanup_expired_sessions()
|
||||
RETURNS INTEGER AS $$
|
||||
DECLARE
|
||||
rows_affected INTEGER := 0;
|
||||
idle_rows INTEGER := 0;
|
||||
idle_timeout_minutes INTEGER := 240; -- 4 hours
|
||||
absolute_cutoff TIMESTAMP WITH TIME ZONE;
|
||||
idle_cutoff TIMESTAMP WITH TIME ZONE;
|
||||
BEGIN
|
||||
absolute_cutoff := CURRENT_TIMESTAMP;
|
||||
idle_cutoff := CURRENT_TIMESTAMP - (idle_timeout_minutes * INTERVAL '1 minute');
|
||||
|
||||
-- Mark sessions as inactive if absolute timeout exceeded
|
||||
UPDATE sessions
|
||||
SET is_active = false,
|
||||
revoked_at = CURRENT_TIMESTAMP,
|
||||
ended_at = CURRENT_TIMESTAMP,
|
||||
revoke_reason = 'absolute_timeout'
|
||||
WHERE is_active = true
|
||||
AND absolute_expires_at < absolute_cutoff;
|
||||
|
||||
GET DIAGNOSTICS rows_affected = ROW_COUNT;
|
||||
|
||||
-- Mark sessions as inactive if idle timeout exceeded
|
||||
UPDATE sessions
|
||||
SET is_active = false,
|
||||
revoked_at = CURRENT_TIMESTAMP,
|
||||
ended_at = CURRENT_TIMESTAMP,
|
||||
revoke_reason = 'idle_timeout'
|
||||
WHERE is_active = true
|
||||
AND last_activity_at < idle_cutoff;
|
||||
|
||||
GET DIAGNOSTICS idle_rows = ROW_COUNT;
|
||||
rows_affected := rows_affected + idle_rows;
|
||||
|
||||
RETURN rows_affected;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Function to get session status (for internal API validation)
|
||||
CREATE OR REPLACE FUNCTION get_session_status(p_token_hash VARCHAR(64))
|
||||
RETURNS TABLE (
|
||||
is_valid BOOLEAN,
|
||||
expiry_reason VARCHAR(50),
|
||||
seconds_until_idle_timeout INTEGER,
|
||||
seconds_until_absolute_timeout INTEGER,
|
||||
user_id INTEGER,
|
||||
tenant_id INTEGER
|
||||
) AS $$
|
||||
DECLARE
|
||||
v_session RECORD;
|
||||
v_idle_timeout_minutes INTEGER := 240; -- 4 hours
|
||||
v_warning_threshold_minutes INTEGER := 5;
|
||||
v_idle_expires_at TIMESTAMP WITH TIME ZONE;
|
||||
v_seconds_until_idle INTEGER;
|
||||
v_seconds_until_absolute INTEGER;
|
||||
BEGIN
|
||||
-- Find the session
|
||||
SELECT s.* INTO v_session
|
||||
FROM sessions s
|
||||
WHERE s.session_token_hash = p_token_hash
|
||||
AND s.is_active = true;
|
||||
|
||||
-- Session not found or inactive
|
||||
IF NOT FOUND THEN
|
||||
RETURN QUERY SELECT
|
||||
false::BOOLEAN,
|
||||
NULL::VARCHAR(50),
|
||||
NULL::INTEGER,
|
||||
NULL::INTEGER,
|
||||
NULL::INTEGER,
|
||||
NULL::INTEGER;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Calculate expiration times
|
||||
v_idle_expires_at := v_session.last_activity_at + (v_idle_timeout_minutes * INTERVAL '1 minute');
|
||||
|
||||
-- Check absolute timeout first
|
||||
IF CURRENT_TIMESTAMP >= v_session.absolute_expires_at THEN
|
||||
-- Mark session as expired
|
||||
UPDATE sessions
|
||||
SET is_active = false,
|
||||
revoked_at = CURRENT_TIMESTAMP,
|
||||
ended_at = CURRENT_TIMESTAMP,
|
||||
revoke_reason = 'absolute_timeout'
|
||||
WHERE session_token_hash = p_token_hash;
|
||||
|
||||
RETURN QUERY SELECT
|
||||
false::BOOLEAN,
|
||||
'absolute'::VARCHAR(50),
|
||||
NULL::INTEGER,
|
||||
NULL::INTEGER,
|
||||
v_session.user_id,
|
||||
v_session.tenant_id;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Check idle timeout
|
||||
IF CURRENT_TIMESTAMP >= v_idle_expires_at THEN
|
||||
-- Mark session as expired
|
||||
UPDATE sessions
|
||||
SET is_active = false,
|
||||
revoked_at = CURRENT_TIMESTAMP,
|
||||
ended_at = CURRENT_TIMESTAMP,
|
||||
revoke_reason = 'idle_timeout'
|
||||
WHERE session_token_hash = p_token_hash;
|
||||
|
||||
RETURN QUERY SELECT
|
||||
false::BOOLEAN,
|
||||
'idle'::VARCHAR(50),
|
||||
NULL::INTEGER,
|
||||
NULL::INTEGER,
|
||||
v_session.user_id,
|
||||
v_session.tenant_id;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Session is valid - calculate remaining times
|
||||
v_seconds_until_idle := EXTRACT(EPOCH FROM (v_idle_expires_at - CURRENT_TIMESTAMP))::INTEGER;
|
||||
v_seconds_until_absolute := EXTRACT(EPOCH FROM (v_session.absolute_expires_at - CURRENT_TIMESTAMP))::INTEGER;
|
||||
|
||||
RETURN QUERY SELECT
|
||||
true::BOOLEAN,
|
||||
NULL::VARCHAR(50),
|
||||
v_seconds_until_idle,
|
||||
v_seconds_until_absolute,
|
||||
v_session.user_id,
|
||||
v_session.tenant_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Function to update session activity (called on each authenticated request)
|
||||
CREATE OR REPLACE FUNCTION update_session_activity(p_token_hash VARCHAR(64))
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
v_updated INTEGER;
|
||||
BEGIN
|
||||
UPDATE sessions
|
||||
SET last_activity_at = CURRENT_TIMESTAMP
|
||||
WHERE session_token_hash = p_token_hash
|
||||
AND is_active = true;
|
||||
|
||||
GET DIAGNOSTICS v_updated = ROW_COUNT;
|
||||
RETURN v_updated > 0;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Function to revoke a session
|
||||
CREATE OR REPLACE FUNCTION revoke_session(p_token_hash VARCHAR(64), p_reason VARCHAR(50) DEFAULT 'logout')
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
v_updated INTEGER;
|
||||
BEGIN
|
||||
UPDATE sessions
|
||||
SET is_active = false,
|
||||
revoked_at = CURRENT_TIMESTAMP,
|
||||
ended_at = CURRENT_TIMESTAMP,
|
||||
revoke_reason = p_reason
|
||||
WHERE session_token_hash = p_token_hash
|
||||
AND is_active = true;
|
||||
|
||||
GET DIAGNOSTICS v_updated = ROW_COUNT;
|
||||
RETURN v_updated > 0;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Function to revoke all sessions for a user (e.g., on password change)
|
||||
CREATE OR REPLACE FUNCTION revoke_all_user_sessions(p_user_id INTEGER, p_reason VARCHAR(50) DEFAULT 'password_change')
|
||||
RETURNS INTEGER AS $$
|
||||
DECLARE
|
||||
v_updated INTEGER;
|
||||
BEGIN
|
||||
UPDATE sessions
|
||||
SET is_active = false,
|
||||
revoked_at = CURRENT_TIMESTAMP,
|
||||
ended_at = CURRENT_TIMESTAMP,
|
||||
revoke_reason = p_reason
|
||||
WHERE user_id = p_user_id
|
||||
AND is_active = true;
|
||||
|
||||
GET DIAGNOSTICS v_updated = ROW_COUNT;
|
||||
RETURN v_updated;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Log migration completion
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Migration 022: Created sessions table and session management functions for OWASP/NIST compliance';
|
||||
END $$;
|
||||
278
scripts/migrations/023_add_uuid_primary_key_to_model_configs.sql
Normal file
278
scripts/migrations/023_add_uuid_primary_key_to_model_configs.sql
Normal file
@@ -0,0 +1,278 @@
|
||||
-- Migration: 023_add_uuid_primary_key_to_model_configs.sql
|
||||
-- Description: Add UUID primary key to model_configs table instead of using model_id string
|
||||
-- This fixes the database design issue where model_id (a human-readable string) was used as primary key
|
||||
-- Author: Claude Code
|
||||
-- Date: 2025-12-08
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 1: Ensure uuid-ossp extension is available
|
||||
-- ============================================================================
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 2: Add new UUID 'id' column to model_configs
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if 'id' column already exists
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'model_configs' AND column_name = 'id' AND table_schema = 'public'
|
||||
) THEN
|
||||
-- Add the new UUID column
|
||||
ALTER TABLE model_configs ADD COLUMN id UUID DEFAULT uuid_generate_v4();
|
||||
|
||||
-- Populate UUIDs for all existing rows
|
||||
UPDATE model_configs SET id = uuid_generate_v4() WHERE id IS NULL;
|
||||
|
||||
-- Make id NOT NULL
|
||||
ALTER TABLE model_configs ALTER COLUMN id SET NOT NULL;
|
||||
|
||||
RAISE NOTICE 'Added id column to model_configs';
|
||||
ELSE
|
||||
RAISE NOTICE 'id column already exists in model_configs';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 3: Add new UUID 'model_config_id' column to tenant_model_configs
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if 'model_config_id' column already exists
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'tenant_model_configs' AND column_name = 'model_config_id' AND table_schema = 'public'
|
||||
) THEN
|
||||
-- Add the new UUID column
|
||||
ALTER TABLE tenant_model_configs ADD COLUMN model_config_id UUID;
|
||||
|
||||
RAISE NOTICE 'Added model_config_id column to tenant_model_configs';
|
||||
ELSE
|
||||
RAISE NOTICE 'model_config_id column already exists in tenant_model_configs';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 4: Populate model_config_id based on model_id mapping
|
||||
-- ============================================================================
|
||||
UPDATE tenant_model_configs tmc
|
||||
SET model_config_id = mc.id
|
||||
FROM model_configs mc
|
||||
WHERE tmc.model_id = mc.model_id
|
||||
AND tmc.model_config_id IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 5: Drop the old foreign key constraint
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Drop foreign key if it exists
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'tenant_model_configs_model_id_fkey'
|
||||
AND table_name = 'tenant_model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE tenant_model_configs DROP CONSTRAINT tenant_model_configs_model_id_fkey;
|
||||
RAISE NOTICE 'Dropped old foreign key constraint tenant_model_configs_model_id_fkey';
|
||||
ELSE
|
||||
RAISE NOTICE 'Foreign key constraint tenant_model_configs_model_id_fkey does not exist';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 6: Drop old unique constraint on (tenant_id, model_id)
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'unique_tenant_model'
|
||||
AND table_name = 'tenant_model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE tenant_model_configs DROP CONSTRAINT unique_tenant_model;
|
||||
RAISE NOTICE 'Dropped old unique constraint unique_tenant_model';
|
||||
ELSE
|
||||
RAISE NOTICE 'Unique constraint unique_tenant_model does not exist';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 7: Drop the old primary key on model_configs.model_id
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'model_configs_pkey'
|
||||
AND constraint_type = 'PRIMARY KEY'
|
||||
AND table_name = 'model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE model_configs DROP CONSTRAINT model_configs_pkey;
|
||||
RAISE NOTICE 'Dropped old primary key model_configs_pkey';
|
||||
ELSE
|
||||
RAISE NOTICE 'Primary key model_configs_pkey does not exist';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 8: Add new primary key on model_configs.id
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if primary key already exists on id column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.table_name = 'model_configs'
|
||||
AND tc.constraint_type = 'PRIMARY KEY'
|
||||
AND kcu.column_name = 'id'
|
||||
AND tc.table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE model_configs ADD CONSTRAINT model_configs_pkey PRIMARY KEY (id);
|
||||
RAISE NOTICE 'Added new primary key on model_configs.id';
|
||||
ELSE
|
||||
RAISE NOTICE 'Primary key on model_configs.id already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 9: Add unique constraint on model_configs.model_id
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'model_configs_model_id_unique'
|
||||
AND table_name = 'model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE model_configs ADD CONSTRAINT model_configs_model_id_unique UNIQUE (model_id);
|
||||
RAISE NOTICE 'Added unique constraint on model_configs.model_id';
|
||||
ELSE
|
||||
RAISE NOTICE 'Unique constraint on model_configs.model_id already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 10: Make model_config_id NOT NULL and add foreign key
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Make model_config_id NOT NULL (only if all values are populated)
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM tenant_model_configs WHERE model_config_id IS NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Cannot make model_config_id NOT NULL: some values are NULL. Run the UPDATE first.';
|
||||
END IF;
|
||||
|
||||
-- Alter column to NOT NULL
|
||||
ALTER TABLE tenant_model_configs ALTER COLUMN model_config_id SET NOT NULL;
|
||||
RAISE NOTICE 'Set model_config_id to NOT NULL';
|
||||
EXCEPTION
|
||||
WHEN others THEN
|
||||
RAISE NOTICE 'Could not set model_config_id to NOT NULL: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 11: Add foreign key from tenant_model_configs.model_config_id to model_configs.id
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'tenant_model_configs_model_config_id_fkey'
|
||||
AND table_name = 'tenant_model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE tenant_model_configs
|
||||
ADD CONSTRAINT tenant_model_configs_model_config_id_fkey
|
||||
FOREIGN KEY (model_config_id) REFERENCES model_configs(id) ON DELETE CASCADE;
|
||||
RAISE NOTICE 'Added foreign key on tenant_model_configs.model_config_id';
|
||||
ELSE
|
||||
RAISE NOTICE 'Foreign key tenant_model_configs_model_config_id_fkey already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 12: Add new unique constraint on (tenant_id, model_config_id)
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'unique_tenant_model_config'
|
||||
AND table_name = 'tenant_model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE tenant_model_configs
|
||||
ADD CONSTRAINT unique_tenant_model_config UNIQUE (tenant_id, model_config_id);
|
||||
RAISE NOTICE 'Added unique constraint unique_tenant_model_config';
|
||||
ELSE
|
||||
RAISE NOTICE 'Unique constraint unique_tenant_model_config already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 13: Add index on model_configs.model_id for fast lookups
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'model_configs'
|
||||
AND indexname = 'ix_model_configs_model_id'
|
||||
AND schemaname = 'public'
|
||||
) THEN
|
||||
CREATE INDEX ix_model_configs_model_id ON model_configs(model_id);
|
||||
RAISE NOTICE 'Created index ix_model_configs_model_id';
|
||||
ELSE
|
||||
RAISE NOTICE 'Index ix_model_configs_model_id already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 14: Add index on tenant_model_configs.model_config_id
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'tenant_model_configs'
|
||||
AND indexname = 'ix_tenant_model_configs_model_config_id'
|
||||
AND schemaname = 'public'
|
||||
) THEN
|
||||
CREATE INDEX ix_tenant_model_configs_model_config_id ON tenant_model_configs(model_config_id);
|
||||
RAISE NOTICE 'Created index ix_tenant_model_configs_model_config_id';
|
||||
ELSE
|
||||
RAISE NOTICE 'Index ix_tenant_model_configs_model_config_id already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- VERIFICATION: Show final schema
|
||||
-- ============================================================================
|
||||
SELECT 'model_configs schema:' AS info;
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'model_configs' AND table_schema = 'public'
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
SELECT 'tenant_model_configs schema:' AS info;
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'tenant_model_configs' AND table_schema = 'public'
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
SELECT 'model_configs constraints:' AS info;
|
||||
SELECT constraint_name, constraint_type
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_name = 'model_configs' AND table_schema = 'public';
|
||||
|
||||
SELECT 'tenant_model_configs constraints:' AS info;
|
||||
SELECT constraint_name, constraint_type
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_name = 'tenant_model_configs' AND table_schema = 'public';
|
||||
@@ -0,0 +1,51 @@
|
||||
-- Migration: 024_allow_same_model_id_different_providers.sql
|
||||
-- Description: Allow same model_id with different providers
|
||||
-- The unique constraint should be on (model_id, provider) not just model_id
|
||||
-- This allows the same model to be registered from multiple providers (e.g., Groq and NVIDIA)
|
||||
-- Author: Claude Code
|
||||
-- Date: 2025-12-08
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 1: Drop the unique constraint on model_id alone
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'model_configs_model_id_unique'
|
||||
AND table_name = 'model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE model_configs DROP CONSTRAINT model_configs_model_id_unique;
|
||||
RAISE NOTICE 'Dropped unique constraint model_configs_model_id_unique';
|
||||
ELSE
|
||||
RAISE NOTICE 'Constraint model_configs_model_id_unique does not exist';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- STEP 2: Add new unique constraint on (model_id, provider)
|
||||
-- ============================================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'model_configs_model_id_provider_unique'
|
||||
AND table_name = 'model_configs'
|
||||
AND table_schema = 'public'
|
||||
) THEN
|
||||
ALTER TABLE model_configs ADD CONSTRAINT model_configs_model_id_provider_unique UNIQUE (model_id, provider);
|
||||
RAISE NOTICE 'Added unique constraint on (model_id, provider)';
|
||||
ELSE
|
||||
RAISE NOTICE 'Constraint model_configs_model_id_provider_unique already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- VERIFICATION
|
||||
-- ============================================================================
|
||||
SELECT 'model_configs constraints after migration:' AS info;
|
||||
SELECT constraint_name, constraint_type
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_name = 'model_configs' AND table_schema = 'public'
|
||||
ORDER BY constraint_type, constraint_name;
|
||||
117
scripts/migrations/025_fix_nvidia_model_names.sql
Normal file
117
scripts/migrations/025_fix_nvidia_model_names.sql
Normal file
@@ -0,0 +1,117 @@
|
||||
-- Migration 025: Fix NVIDIA model names to match API format
|
||||
--
|
||||
-- Problem: Model names stored with incorrect format (e.g., nvidia/meta-llama-3.1-8b-instruct)
|
||||
-- Solution: Update to match NVIDIA NIM API expected format (e.g., meta/llama-3.1-8b-instruct)
|
||||
--
|
||||
-- NVIDIA NIM API model naming:
|
||||
-- - Models from Meta: meta/llama-3.1-8b-instruct (NOT nvidia/meta-llama-*)
|
||||
-- - Models from NVIDIA: nvidia/llama-3.1-nemotron-70b-instruct
|
||||
-- - Models from Mistral: mistralai/mistral-large-3-instruct
|
||||
-- - Models from DeepSeek: deepseek-ai/deepseek-v3
|
||||
-- - Models from OpenAI-compatible: openai/gpt-oss-120b (already correct in groq provider)
|
||||
|
||||
-- Idempotency: Only update if old format exists
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Fix Meta Llama models (remove nvidia/ prefix for meta models)
|
||||
UPDATE model_configs
|
||||
SET model_id = 'meta/llama-3.1-8b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.1-8b-instruct' AND provider = 'nvidia';
|
||||
|
||||
UPDATE model_configs
|
||||
SET model_id = 'meta/llama-3.1-70b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.1-70b-instruct' AND provider = 'nvidia';
|
||||
|
||||
UPDATE model_configs
|
||||
SET model_id = 'meta/llama-3.1-405b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.1-405b-instruct' AND provider = 'nvidia';
|
||||
|
||||
UPDATE model_configs
|
||||
SET model_id = 'meta/llama-3.3-70b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.3-70b-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Fix DeepSeek models
|
||||
UPDATE model_configs
|
||||
SET model_id = 'deepseek-ai/deepseek-v3'
|
||||
WHERE model_id = 'nvidia/deepseek-ai-deepseek-v3' AND provider = 'nvidia';
|
||||
|
||||
UPDATE model_configs
|
||||
SET model_id = 'deepseek-ai/deepseek-r1'
|
||||
WHERE model_id = 'nvidia/deepseek-ai-deepseek-r1' AND provider = 'nvidia';
|
||||
|
||||
-- Fix Mistral models
|
||||
UPDATE model_configs
|
||||
SET model_id = 'mistralai/mistral-large-3-instruct'
|
||||
WHERE model_id = 'nvidia/mistralai-mistral-large-3-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Fix Moonshot/Kimi models
|
||||
UPDATE model_configs
|
||||
SET model_id = 'moonshot-ai/kimi-k2-instruct'
|
||||
WHERE model_id = 'nvidia/moonshot-ai-kimi-k2-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Fix Qwen models
|
||||
UPDATE model_configs
|
||||
SET model_id = 'qwen/qwen3-235b-a22b-fp8-instruct'
|
||||
WHERE model_id = 'nvidia/qwen-qwen3-235b-a22b-fp8-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Fix OpenAI-compatible models (for NVIDIA provider)
|
||||
UPDATE model_configs
|
||||
SET model_id = 'openai/gpt-oss-120b'
|
||||
WHERE model_id = 'nvidia/openai-gpt-oss-120b' AND provider = 'nvidia';
|
||||
|
||||
UPDATE model_configs
|
||||
SET model_id = 'openai/gpt-oss-20b'
|
||||
WHERE model_id = 'nvidia/openai-gpt-oss-20b' AND provider = 'nvidia';
|
||||
|
||||
-- Also update tenant_model_configs to match (if they reference old model_ids)
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'meta/llama-3.1-8b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.1-8b-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'meta/llama-3.1-70b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.1-70b-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'meta/llama-3.1-405b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.1-405b-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'meta/llama-3.3-70b-instruct'
|
||||
WHERE model_id = 'nvidia/meta-llama-3.3-70b-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'deepseek-ai/deepseek-v3'
|
||||
WHERE model_id = 'nvidia/deepseek-ai-deepseek-v3';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'deepseek-ai/deepseek-r1'
|
||||
WHERE model_id = 'nvidia/deepseek-ai-deepseek-r1';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'mistralai/mistral-large-3-instruct'
|
||||
WHERE model_id = 'nvidia/mistralai-mistral-large-3-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'moonshot-ai/kimi-k2-instruct'
|
||||
WHERE model_id = 'nvidia/moonshot-ai-kimi-k2-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'qwen/qwen3-235b-a22b-fp8-instruct'
|
||||
WHERE model_id = 'nvidia/qwen-qwen3-235b-a22b-fp8-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'openai/gpt-oss-120b'
|
||||
WHERE model_id = 'nvidia/openai-gpt-oss-120b';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'openai/gpt-oss-20b'
|
||||
WHERE model_id = 'nvidia/openai-gpt-oss-20b';
|
||||
|
||||
RAISE NOTICE 'Migration 025: Fixed NVIDIA model names to match API format';
|
||||
END $$;
|
||||
|
||||
-- Log migration completion
|
||||
INSERT INTO system_versions (version, component, description, applied_at)
|
||||
VALUES ('025', 'model_configs', 'Fixed NVIDIA model names to match API format', NOW())
|
||||
ON CONFLICT DO NOTHING;
|
||||
59
scripts/migrations/026_fix_nvidia_model_ids_api_format.sql
Normal file
59
scripts/migrations/026_fix_nvidia_model_ids_api_format.sql
Normal file
@@ -0,0 +1,59 @@
|
||||
-- Migration 026: Fix NVIDIA model_ids to exact NVIDIA NIM API format
|
||||
--
|
||||
-- Verified against docs.api.nvidia.com and build.nvidia.com (December 2025)
|
||||
--
|
||||
-- Issues found:
|
||||
-- 1. moonshot-ai/kimi-k2-instruct -> should be moonshotai/kimi-k2-instruct (no hyphen)
|
||||
-- 2. mistralai/mistral-large-3-instruct -> model doesn't exist, should be mistralai/mistral-large
|
||||
-- 3. deepseek-ai/deepseek-v3 -> model doesn't exist on NVIDIA, should be deepseek-ai/deepseek-v3.1
|
||||
-- 4. qwen/qwen3-235b-a22b-fp8-instruct -> should be qwen/qwen3-235b-a22b (no fp8-instruct suffix)
|
||||
--
|
||||
-- Note: These are the model_id strings passed to NVIDIA's API, not the names shown to users
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Fix Kimi K2: moonshot-ai -> moonshotai (NVIDIA uses no hyphen)
|
||||
UPDATE model_configs
|
||||
SET model_id = 'moonshotai/kimi-k2-instruct'
|
||||
WHERE model_id = 'moonshot-ai/kimi-k2-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Fix Mistral Large 3: Use the correct model name from NVIDIA
|
||||
-- The full name is mistralai/mistral-large or mistralai/mistral-large-3-675b-instruct-2512
|
||||
UPDATE model_configs
|
||||
SET model_id = 'mistralai/mistral-large'
|
||||
WHERE model_id = 'mistralai/mistral-large-3-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Fix DeepSeek V3: NVIDIA only has v3.1, not plain v3
|
||||
UPDATE model_configs
|
||||
SET model_id = 'deepseek-ai/deepseek-v3.1'
|
||||
WHERE model_id = 'deepseek-ai/deepseek-v3' AND provider = 'nvidia';
|
||||
|
||||
-- Fix Qwen 3 235B: Remove fp8-instruct suffix
|
||||
UPDATE model_configs
|
||||
SET model_id = 'qwen/qwen3-235b-a22b'
|
||||
WHERE model_id = 'qwen/qwen3-235b-a22b-fp8-instruct' AND provider = 'nvidia';
|
||||
|
||||
-- Also update tenant_model_configs to match
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'moonshotai/kimi-k2-instruct'
|
||||
WHERE model_id = 'moonshot-ai/kimi-k2-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'mistralai/mistral-large'
|
||||
WHERE model_id = 'mistralai/mistral-large-3-instruct';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'deepseek-ai/deepseek-v3.1'
|
||||
WHERE model_id = 'deepseek-ai/deepseek-v3';
|
||||
|
||||
UPDATE tenant_model_configs
|
||||
SET model_id = 'qwen/qwen3-235b-a22b'
|
||||
WHERE model_id = 'qwen/qwen3-235b-a22b-fp8-instruct';
|
||||
|
||||
RAISE NOTICE 'Migration 026: Fixed NVIDIA model_ids to match exact API format';
|
||||
END $$;
|
||||
|
||||
-- Log migration completion
|
||||
INSERT INTO system_versions (version, component, description, applied_at)
|
||||
VALUES ('026', 'model_configs', 'Fixed NVIDIA model_ids to exact API format', NOW())
|
||||
ON CONFLICT DO NOTHING;
|
||||
35
scripts/migrations/027_assign_nvidia_models_to_tenants.sql
Normal file
35
scripts/migrations/027_assign_nvidia_models_to_tenants.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
-- Migration: 027_assign_nvidia_models_to_tenants.sql
|
||||
-- Description: Ensure NVIDIA models are assigned to all tenants (fix for partial 021 migration)
|
||||
-- Date: 2025-12-08
|
||||
-- Issue: Deploy.sh updates add models but don't assign to existing tenants
|
||||
|
||||
-- Assign NVIDIA models to all existing tenants with 1000 RPM rate limits
|
||||
-- This is idempotent - ON CONFLICT DO NOTHING means it won't duplicate
|
||||
INSERT INTO tenant_model_configs (tenant_id, model_config_id, model_id, is_enabled, priority, rate_limits, created_at, updated_at)
|
||||
SELECT
|
||||
t.id,
|
||||
m.id, -- UUID foreign key (auto-generated in model_configs)
|
||||
m.model_id, -- String identifier (kept for easier queries)
|
||||
true,
|
||||
5,
|
||||
'{"max_requests_per_hour": 1000, "max_tokens_per_request": 4000, "concurrent_requests": 5, "max_cost_per_hour": 10.0, "requests_per_minute": 1000, "tokens_per_minute": 100000, "max_concurrent": 10}'::json,
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM tenants t
|
||||
CROSS JOIN model_configs m
|
||||
WHERE m.provider = 'nvidia'
|
||||
AND m.is_active = true
|
||||
ON CONFLICT (tenant_id, model_config_id) DO NOTHING;
|
||||
|
||||
-- Log migration completion
|
||||
DO $$
|
||||
DECLARE
|
||||
assigned_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO assigned_count
|
||||
FROM tenant_model_configs tmc
|
||||
JOIN model_configs mc ON mc.id = tmc.model_config_id
|
||||
WHERE mc.provider = 'nvidia';
|
||||
|
||||
RAISE NOTICE 'Migration 027: Ensured NVIDIA models are assigned to all tenants (% total assignments)', assigned_count;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user