Files
chorus/migrations/000003_mvp1_routing.up.sql
T

247 lines
12 KiB
SQL

-- Validate that a pre-MVP-1 Gemini template or model has not been configured without
-- the explicit capability that MVP-1 requires. The guard remains only after a failed run.
CREATE TABLE IF NOT EXISTS chorus_mvp1_routing_up_guard (
id TINYINT NOT NULL,
CONSTRAINT chk_chorus_mvp1_routing_up_guard CHECK (id = 0)
) ENGINE=InnoDB;
DELETE FROM chorus_mvp1_routing_up_guard;
INSERT INTO chorus_mvp1_routing_up_guard (id)
SELECT 1
WHERE EXISTS (SELECT 1 FROM provider_models WHERE api_type = 'gemini')
OR EXISTS (SELECT 1 FROM prompt_templates WHERE api_type = 'gemini');
DROP TABLE chorus_mvp1_routing_up_guard;
CREATE TABLE provider_credentials (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
provider_id BIGINT UNSIGNED NOT NULL,
credential_version INT UNSIGNED NOT NULL,
api_key_enc JSON NOT NULL,
key_id VARCHAR(128) NOT NULL,
status VARCHAR(16) NOT NULL,
created_by BIGINT NULL,
updated_by BIGINT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
retired_at DATETIME(6) NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_provider_credentials_version (provider_id, credential_version),
KEY idx_provider_credentials_status (provider_id, status, created_at),
CONSTRAINT fk_provider_credentials_provider FOREIGN KEY (provider_id) REFERENCES providers (id) ON DELETE RESTRICT,
CONSTRAINT chk_provider_credentials_status CHECK (status IN ('active', 'retired')),
CONSTRAINT chk_provider_credentials_envelope CHECK (
JSON_TYPE(api_key_enc) = 'OBJECT' AND
JSON_CONTAINS_PATH(api_key_enc, 'all', '$.version', '$.key_id', '$.nonce', '$.ciphertext')
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE provider_model_capabilities (
provider_model_id BIGINT UNSIGNED NOT NULL,
capability VARCHAR(32) NOT NULL,
PRIMARY KEY (provider_model_id, capability),
CONSTRAINT fk_provider_model_capabilities_model FOREIGN KEY (provider_model_id) REFERENCES provider_models (id) ON DELETE CASCADE,
CONSTRAINT chk_provider_model_capabilities_capability CHECK (capability IN ('text', 'image_generate', 'image_edit'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE route_pools (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
slug VARCHAR(80) NOT NULL,
name VARCHAR(120) NOT NULL,
capability VARCHAR(32) NOT NULL,
prompt_template_id BIGINT UNSIGNED NOT NULL,
max_failover SMALLINT UNSIGNED NOT NULL DEFAULT 0,
version INT UNSIGNED NOT NULL DEFAULT 1,
enabled BOOLEAN NOT NULL DEFAULT FALSE,
created_by BIGINT NULL,
updated_by BIGINT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uq_route_pools_slug (slug),
KEY idx_route_pools_capability_enabled (capability, enabled),
KEY idx_route_pools_prompt_template (prompt_template_id),
CONSTRAINT fk_route_pools_template FOREIGN KEY (prompt_template_id) REFERENCES prompt_templates (id) ON DELETE RESTRICT,
CONSTRAINT chk_route_pools_capability CHECK (capability IN ('text', 'image_generate', 'image_edit')),
CONSTRAINT chk_route_pools_version CHECK (version > 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE active_routes (
capability VARCHAR(32) NOT NULL,
route_pool_id BIGINT UNSIGNED NOT NULL,
operator_id BIGINT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (capability),
KEY idx_active_routes_pool (route_pool_id),
CONSTRAINT fk_active_routes_pool FOREIGN KEY (route_pool_id) REFERENCES route_pools (id) ON DELETE RESTRICT,
CONSTRAINT chk_active_routes_capability CHECK (capability IN ('text', 'image_generate', 'image_edit'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE route_pool_members (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
route_pool_id BIGINT UNSIGNED NOT NULL,
provider_model_id BIGINT UNSIGNED NOT NULL,
weight SMALLINT UNSIGNED NOT NULL,
failure_threshold SMALLINT UNSIGNED NOT NULL DEFAULT 3,
open_seconds INT UNSIGNED NOT NULL DEFAULT 60,
half_open_max SMALLINT UNSIGNED NOT NULL DEFAULT 1,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
position SMALLINT UNSIGNED NOT NULL DEFAULT 0,
created_by BIGINT NULL,
updated_by BIGINT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uq_route_pool_members_model (route_pool_id, provider_model_id),
KEY idx_route_pool_members_list (route_pool_id, enabled, position),
CONSTRAINT fk_route_pool_members_pool FOREIGN KEY (route_pool_id) REFERENCES route_pools (id) ON DELETE CASCADE,
CONSTRAINT fk_route_pool_members_model FOREIGN KEY (provider_model_id) REFERENCES provider_models (id) ON DELETE RESTRICT,
CONSTRAINT chk_route_pool_members_weight CHECK (weight > 0),
CONSTRAINT chk_route_pool_members_failure_threshold CHECK (failure_threshold > 0),
CONSTRAINT chk_route_pool_members_open_seconds CHECK (open_seconds > 0),
CONSTRAINT chk_route_pool_members_half_open CHECK (half_open_max > 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE route_member_runtime (
route_pool_member_id BIGINT UNSIGNED NOT NULL,
state VARCHAR(16) NOT NULL DEFAULT 'closed',
consecutive_failures INT UNSIGNED NOT NULL DEFAULT 0,
open_until DATETIME(6) NULL,
probe_owner VARCHAR(128) NULL,
probe_token CHAR(36) NULL,
probe_until DATETIME(6) NULL,
last_error_code VARCHAR(64) NULL,
last_error_message VARCHAR(1024) NULL,
last_observed_at DATETIME(6) NULL,
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (route_pool_member_id),
KEY idx_route_member_runtime_state (state, open_until),
CONSTRAINT fk_route_member_runtime_member FOREIGN KEY (route_pool_member_id) REFERENCES route_pool_members (id) ON DELETE CASCADE,
CONSTRAINT chk_route_member_runtime_state CHECK (state IN ('closed', 'open', 'half_open')),
CONSTRAINT chk_route_member_runtime_probe CHECK (
(probe_owner IS NULL AND probe_token IS NULL AND probe_until IS NULL) OR
(probe_owner IS NOT NULL AND probe_token IS NOT NULL AND probe_until IS NOT NULL)
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE provider_connectivity_checks (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
provider_model_id BIGINT UNSIGNED NOT NULL,
operator_id BIGINT NULL,
status VARCHAR(16) NOT NULL,
error_code VARCHAR(64) NULL,
error_message VARCHAR(1024) NULL,
latency_ms INT UNSIGNED NULL,
started_at DATETIME(6) NOT NULL,
completed_at DATETIME(6) NULL,
cooldown_until DATETIME(6) NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
KEY idx_provider_connectivity_checks_model (provider_model_id, started_at DESC),
KEY idx_provider_connectivity_checks_status (status, started_at),
CONSTRAINT fk_provider_connectivity_checks_model FOREIGN KEY (provider_model_id) REFERENCES provider_models (id) ON DELETE RESTRICT,
CONSTRAINT chk_provider_connectivity_checks_status CHECK (status IN ('running', 'succeeded', 'failed'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE admin_audit_events (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
operator_id BIGINT NULL,
action VARCHAR(128) NOT NULL,
target_type VARCHAR(64) NOT NULL,
target_id VARCHAR(128) NOT NULL,
result VARCHAR(16) NOT NULL,
request_id VARCHAR(128) NOT NULL,
summary JSON NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uq_admin_audit_events_request_action (request_id, action),
KEY idx_admin_audit_events_target (target_type, target_id, created_at),
KEY idx_admin_audit_events_operator (operator_id, created_at),
CONSTRAINT chk_admin_audit_events_result CHECK (result IN ('succeeded', 'failed')),
CONSTRAINT chk_admin_audit_events_summary CHECK (JSON_TYPE(summary) = 'OBJECT')
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Backfill only envelopes that already passed the MVP-0 provider check constraint.
INSERT INTO provider_credentials (provider_id, credential_version, api_key_enc, key_id, status, created_at, updated_at)
SELECT id,
1,
api_key_enc,
JSON_UNQUOTE(JSON_EXTRACT(api_key_enc, '$.key_id')),
'active',
created_at,
updated_at
FROM providers
WHERE api_key_enc IS NOT NULL;
INSERT INTO provider_model_capabilities (provider_model_id, capability)
SELECT id,
CASE api_type
WHEN 'chat' THEN 'text'
WHEN 'images' THEN 'image_generate'
WHEN 'images_edits' THEN 'image_edit'
END
FROM provider_models;
ALTER TABLE providers
ADD COLUMN active_credential_id BIGINT UNSIGNED NULL,
ADD UNIQUE KEY uq_providers_active_credential (active_credential_id);
UPDATE providers p
JOIN provider_credentials c ON c.provider_id = p.id AND c.status = 'active'
SET p.active_credential_id = c.id;
ALTER TABLE providers
DROP CHECK chk_providers_auth,
ADD CONSTRAINT fk_providers_active_credential FOREIGN KEY (active_credential_id) REFERENCES provider_credentials (id) ON DELETE RESTRICT;
UPDATE providers SET api_key_enc = NULL WHERE active_credential_id IS NOT NULL;
ALTER TABLE providers
ADD CONSTRAINT chk_providers_auth_mvp1 CHECK (
auth_type IN ('none', 'bearer', 'x-goog-api-key') AND
((auth_type = 'none' AND active_credential_id IS NULL AND api_key_enc IS NULL) OR
(auth_type IN ('bearer', 'x-goog-api-key') AND active_credential_id IS NOT NULL AND api_key_enc IS NULL))
);
ALTER TABLE provider_models
DROP FOREIGN KEY fk_provider_models_provider,
ADD CONSTRAINT fk_provider_models_provider_mvp1 FOREIGN KEY (provider_id) REFERENCES providers (id) ON DELETE RESTRICT;
ALTER TABLE prompt_templates
ADD COLUMN capability VARCHAR(32) NULL,
ADD COLUMN default_role_rule TEXT NULL;
UPDATE prompt_templates
SET capability = CASE api_type
WHEN 'chat' THEN 'text'
WHEN 'images' THEN 'image_generate'
WHEN 'images_edits' THEN 'image_edit'
END,
default_role_rule = '';
ALTER TABLE prompt_templates
MODIFY COLUMN capability VARCHAR(32) NOT NULL,
MODIFY COLUMN default_role_rule TEXT NOT NULL,
ADD CONSTRAINT chk_prompt_templates_capability CHECK (capability IN ('text', 'image_generate', 'image_edit'));
ALTER TABLE generations
ADD COLUMN route_pool_id BIGINT UNSIGNED NULL,
ADD COLUMN route_pool_version INT UNSIGNED NULL,
ADD COLUMN prompt_template_id BIGINT UNSIGNED NULL,
ADD COLUMN route_snapshot JSON NULL,
ADD COLUMN role_rule TEXT NULL,
ADD COLUMN provider_attempt_count INT UNSIGNED NOT NULL DEFAULT 0,
ADD KEY idx_generations_route_pool (route_pool_id),
ADD KEY idx_generations_prompt_template (prompt_template_id),
ADD CONSTRAINT fk_generations_route_pool FOREIGN KEY (route_pool_id) REFERENCES route_pools (id) ON DELETE RESTRICT,
ADD CONSTRAINT fk_generations_prompt_template FOREIGN KEY (prompt_template_id) REFERENCES prompt_templates (id) ON DELETE RESTRICT,
ADD CONSTRAINT chk_generations_route_snapshot CHECK (
(route_pool_id IS NULL AND route_pool_version IS NULL AND prompt_template_id IS NULL AND route_snapshot IS NULL) OR
(route_pool_id IS NOT NULL AND route_pool_version IS NOT NULL AND prompt_template_id IS NOT NULL AND route_snapshot IS NOT NULL AND JSON_TYPE(route_snapshot) = 'OBJECT')
),
ADD CONSTRAINT chk_generations_provider_attempt_count CHECK (provider_attempt_count <= attempt_count);
ALTER TABLE generation_inputs
ADD COLUMN note VARCHAR(500) NULL,
ADD CONSTRAINT chk_generation_inputs_note CHECK (note IS NULL OR CHAR_LENGTH(note) <= 500);