mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 16:06:08 +00:00
Fold the final columns, constraints, and indexes into the initial schema and remove superseded migrations and historical backfills. Refresh the frozen checksum manifest, simplify migration test setup, and retain canonical audit identity coverage. Keep migration documentation focused on general rules. Validation: PostgreSQL schema dumps match before and after consolidation, all 125 PostgreSQL tests passed, and frozen checksum, workspace rustfmt, and diff whitespace checks passed. Co-authored-by: Codex <noreply@openai.com>
965 lines
33 KiB
SQL
965 lines
33 KiB
SQL
-- 管理员身份与密码摘要。
|
|
create table admin_users (
|
|
id text primary key,
|
|
password_hash text not null,
|
|
created_at timestamptz not null,
|
|
updated_at timestamptz not null,
|
|
constraint admin_users_time_ck check (created_at <= updated_at)
|
|
);
|
|
|
|
-- 管理操作审计事件。
|
|
create table admin_audit_events (
|
|
id text primary key,
|
|
actor_kind text not null,
|
|
actor_admin_user_id text,
|
|
actor_ref text not null,
|
|
admin_request_id text,
|
|
action text not null,
|
|
entity_kind text not null,
|
|
entity_ref text not null,
|
|
config_revision bigint,
|
|
changed_fields text[] not null default '{}',
|
|
created_at timestamptz not null,
|
|
constraint admin_audit_events_actor_kind_ck check (
|
|
actor_kind in ('admin_session', 'admin_api_key', 'system', 'anonymous')
|
|
),
|
|
constraint admin_audit_events_config_revision_ck check (
|
|
config_revision is null or config_revision > 0
|
|
),
|
|
constraint admin_audit_events_changed_fields_ck check (
|
|
cardinality(changed_fields) <= 64
|
|
and array_position(changed_fields, null) is null
|
|
),
|
|
constraint admin_audit_events_actor_fk foreign key (actor_admin_user_id)
|
|
references admin_users (id)
|
|
on update restrict
|
|
on delete set null,
|
|
constraint admin_audit_events_actor_identity_ck check (
|
|
actor_admin_user_id is null
|
|
or (actor_kind = 'admin_session' and actor_ref = 'admin:' || actor_admin_user_id)
|
|
)
|
|
);
|
|
|
|
create index admin_audit_events_actor_idx
|
|
on admin_audit_events (actor_admin_user_id)
|
|
where actor_admin_user_id is not null;
|
|
create index admin_audit_events_created_idx
|
|
on admin_audit_events (created_at desc, id desc);
|
|
|
|
-- 客户端 API 密钥、鉴权范围与限额。
|
|
create table client_api_keys (
|
|
id text primary key,
|
|
name text not null,
|
|
label text,
|
|
key text not null unique,
|
|
enabled boolean not null default true,
|
|
max_concurrency bigint not null default 0,
|
|
requests_per_minute bigint not null default 0,
|
|
last_used_at timestamptz,
|
|
created_at timestamptz not null,
|
|
updated_at timestamptz not null,
|
|
constraint client_api_keys_key_ck check (
|
|
key ~ '^sk_[A-Za-z0-9_-]{43}$'
|
|
),
|
|
constraint client_api_keys_limits_ck check (
|
|
max_concurrency >= 0
|
|
and requests_per_minute >= 0
|
|
),
|
|
constraint client_api_keys_time_ck check (created_at <= updated_at)
|
|
);
|
|
|
|
create index client_api_keys_created_idx
|
|
on client_api_keys (created_at desc, id desc);
|
|
|
|
-- 全局运行参数与配置版本。
|
|
create table runtime_settings (
|
|
id bigint primary key,
|
|
config_revision bigint not null default 1,
|
|
admin_api_key text,
|
|
refresh_margin_seconds bigint not null default 3600,
|
|
refresh_concurrency bigint not null default 2,
|
|
max_concurrent_per_account bigint not null default 3,
|
|
request_interval_ms bigint not null default 50,
|
|
rotation_strategy text not null default 'smart',
|
|
model_mappings_json jsonb not null default '{}'::jsonb,
|
|
usage_retention_days bigint not null default 31,
|
|
ops_event_retention_days bigint not null default 30,
|
|
audit_retention_days bigint not null default 90,
|
|
updated_at timestamptz not null,
|
|
min_codex_desktop_version text,
|
|
min_codex_cli_version text,
|
|
constraint runtime_settings_singleton_ck check (id = 1),
|
|
constraint runtime_settings_revision_ck check (config_revision > 0),
|
|
constraint runtime_settings_refresh_ck check (
|
|
refresh_margin_seconds > 0
|
|
and refresh_concurrency > 0
|
|
and max_concurrent_per_account > 0
|
|
and request_interval_ms >= 0
|
|
),
|
|
constraint runtime_settings_rotation_ck check (
|
|
rotation_strategy in ('smart', 'quota_reset_priority', 'round_robin', 'sticky')
|
|
),
|
|
constraint runtime_settings_model_mappings_ck check (
|
|
jsonb_typeof(model_mappings_json) = 'object'
|
|
and octet_length(model_mappings_json::text) <= 131072
|
|
),
|
|
constraint runtime_settings_retention_ck check (
|
|
usage_retention_days >= 31
|
|
and ops_event_retention_days > 0
|
|
and audit_retention_days > 0
|
|
),
|
|
constraint runtime_settings_client_versions_ck check (
|
|
(
|
|
min_codex_desktop_version is null
|
|
or (
|
|
octet_length(min_codex_desktop_version) between 1 and 64
|
|
and min_codex_desktop_version = btrim(min_codex_desktop_version)
|
|
and min_codex_desktop_version !~ '[[:cntrl:]]'
|
|
)
|
|
)
|
|
and (
|
|
min_codex_cli_version is null
|
|
or (
|
|
octet_length(min_codex_cli_version) between 1 and 64
|
|
and min_codex_cli_version = btrim(min_codex_cli_version)
|
|
and min_codex_cli_version !~ '[[:cntrl:]]'
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
-- 上游 Provider 账号、凭据与可用性状态。
|
|
create table provider_accounts (
|
|
id text primary key,
|
|
provider_kind text not null,
|
|
name text not null,
|
|
email text,
|
|
upstream_user_id text,
|
|
upstream_account_id text,
|
|
plan_type text,
|
|
authentication_kind text not null,
|
|
provider_credentials_json jsonb not null,
|
|
credential_revision bigint not null default 1,
|
|
has_refresh_token boolean not null,
|
|
access_token_expires_at timestamptz,
|
|
next_refresh_at timestamptz,
|
|
enabled boolean not null default true,
|
|
credential_state text not null default 'unknown',
|
|
provider_quota_json jsonb,
|
|
last_error_message text,
|
|
credential_observed_at timestamptz not null,
|
|
quota_observed_at timestamptz,
|
|
created_at timestamptz not null,
|
|
updated_at timestamptz not null,
|
|
quota_access_state text not null default 'unknown',
|
|
quota_evidence text,
|
|
quota_access_observed_at timestamptz,
|
|
quota_reset_at timestamptz,
|
|
last_error_reason text,
|
|
concurrency_limit bigint,
|
|
weight smallint not null default 1,
|
|
constraint provider_accounts_revision_ck check (credential_revision > 0),
|
|
constraint provider_accounts_authentication_kind_ck check (
|
|
authentication_kind ~ '^[a-z][a-z0-9_]{0,63}$'
|
|
),
|
|
constraint provider_accounts_credentials_ck check (
|
|
jsonb_typeof(provider_credentials_json) = 'object'
|
|
and octet_length(provider_credentials_json::text) <= 262144
|
|
),
|
|
constraint provider_accounts_quota_ck check (
|
|
provider_quota_json is null
|
|
or (
|
|
jsonb_typeof(provider_quota_json) = 'object'
|
|
and octet_length(provider_quota_json::text) <= 131072
|
|
)
|
|
),
|
|
constraint provider_accounts_refresh_ck check (
|
|
has_refresh_token or next_refresh_at is null
|
|
),
|
|
constraint provider_accounts_credential_state_ck check (
|
|
credential_state in ('unknown', 'ready', 'expired', 'banned', 'invalid')
|
|
),
|
|
constraint provider_accounts_quota_access_state_ck check (
|
|
quota_access_state in ('unknown', 'allowed', 'exhausted')
|
|
),
|
|
constraint provider_accounts_quota_evidence_ck check (
|
|
quota_evidence is null
|
|
or quota_evidence in (
|
|
'provider_denied',
|
|
'account_limit_reached',
|
|
'usage_limit_reached',
|
|
'payment_required'
|
|
)
|
|
),
|
|
constraint provider_accounts_quota_fact_ck check (
|
|
(quota_access_state = 'unknown' and quota_evidence is null and quota_reset_at is null)
|
|
or (
|
|
quota_access_state = 'allowed'
|
|
and quota_evidence is null
|
|
and quota_access_observed_at is not null
|
|
and quota_reset_at is null
|
|
)
|
|
or (
|
|
quota_access_state = 'exhausted'
|
|
and quota_evidence is not null
|
|
and quota_access_observed_at is not null
|
|
)
|
|
),
|
|
constraint provider_accounts_quota_observation_ck check (
|
|
(provider_quota_json is null) = (quota_observed_at is null)
|
|
),
|
|
constraint provider_accounts_error_reason_ck check (
|
|
last_error_reason is null
|
|
or last_error_reason in (
|
|
'account_unverified',
|
|
'access_token_expired',
|
|
'credential_expired',
|
|
'credential_invalid',
|
|
'account_banned'
|
|
)
|
|
),
|
|
constraint provider_accounts_concurrency_limit_ck check (
|
|
concurrency_limit is null or concurrency_limit between 1 and 4294967295
|
|
),
|
|
constraint provider_accounts_weight_ck check (
|
|
weight between 1 and 100
|
|
),
|
|
constraint provider_accounts_time_ck check (
|
|
created_at <= updated_at
|
|
and credential_observed_at <= updated_at
|
|
and (quota_access_observed_at is null or quota_access_observed_at <= updated_at)
|
|
and (quota_observed_at is null or quota_observed_at <= updated_at)
|
|
)
|
|
);
|
|
|
|
create unique index provider_accounts_upstream_identity_uq
|
|
on provider_accounts (
|
|
provider_kind,
|
|
upstream_user_id,
|
|
coalesce(upstream_account_id, '')
|
|
);
|
|
create index provider_accounts_runtime_idx
|
|
on provider_accounts (provider_kind, enabled, id);
|
|
create index provider_accounts_credential_state_idx
|
|
on provider_accounts (credential_state, id);
|
|
create index provider_accounts_access_expiry_idx
|
|
on provider_accounts (access_token_expires_at, id);
|
|
create index provider_accounts_email_idx
|
|
on provider_accounts (provider_kind, lower(email))
|
|
where email is not null;
|
|
|
|
-- 与 Provider 无关的账号组,以及账号和客户端 API 密钥的组成员关系。
|
|
create table account_groups (
|
|
id text primary key,
|
|
name text not null,
|
|
description text,
|
|
enabled boolean not null default true,
|
|
created_at timestamptz not null,
|
|
updated_at timestamptz not null,
|
|
color text not null,
|
|
constraint account_groups_id_ck check (
|
|
id ~ '^grp_[0-9a-f]{32}$'
|
|
),
|
|
constraint account_groups_name_ck check (
|
|
char_length(btrim(name)) between 1 and 100
|
|
and name = btrim(name)
|
|
and name !~ '[[:cntrl:]]'
|
|
),
|
|
constraint account_groups_description_ck check (
|
|
description is null or (
|
|
octet_length(description) <= 4096
|
|
and description !~ '[[:cntrl:]]'
|
|
)
|
|
),
|
|
constraint account_groups_time_ck check (
|
|
created_at <= updated_at
|
|
),
|
|
constraint account_groups_color_ck check (color ~ '^#[0-9A-F]{8}$')
|
|
);
|
|
|
|
create unique index account_groups_name_uq
|
|
on account_groups (lower(name));
|
|
|
|
create table account_group_accounts (
|
|
account_group_id text not null,
|
|
provider_account_id text not null,
|
|
created_at timestamptz not null,
|
|
primary key (account_group_id, provider_account_id),
|
|
constraint account_group_accounts_group_fk foreign key (account_group_id)
|
|
references account_groups (id)
|
|
on update restrict
|
|
on delete cascade,
|
|
constraint account_group_accounts_account_fk foreign key (provider_account_id)
|
|
references provider_accounts (id)
|
|
on update restrict
|
|
on delete cascade
|
|
);
|
|
|
|
create index account_group_accounts_account_idx
|
|
on account_group_accounts (provider_account_id, account_group_id);
|
|
|
|
create table client_api_key_groups (
|
|
client_api_key_id text not null,
|
|
account_group_id text not null,
|
|
created_at timestamptz not null,
|
|
primary key (client_api_key_id, account_group_id),
|
|
constraint client_api_key_groups_key_fk foreign key (client_api_key_id)
|
|
references client_api_keys (id)
|
|
on update restrict
|
|
on delete cascade,
|
|
constraint client_api_key_groups_group_fk foreign key (account_group_id)
|
|
references account_groups (id)
|
|
on update restrict
|
|
on delete restrict
|
|
);
|
|
|
|
create index client_api_key_groups_group_idx
|
|
on client_api_key_groups (account_group_id, client_api_key_id);
|
|
|
|
-- 模型请求、上游尝试、用量、计费与结果。
|
|
create table model_requests (
|
|
id text primary key,
|
|
client_api_key_id text,
|
|
client_api_key_ref text not null,
|
|
config_revision bigint not null,
|
|
protocol text not null,
|
|
operation text not null,
|
|
endpoint text not null,
|
|
client_transport text not null,
|
|
requested_model_id text,
|
|
provider_kind text,
|
|
upstream_model_id text,
|
|
service_tier text,
|
|
provider_account_id text,
|
|
provider_account_ref text,
|
|
provider_account_name_snapshot text,
|
|
provider_account_email_snapshot text,
|
|
provider_account_authentication_kind_snapshot text,
|
|
upstream_transport text,
|
|
http_version text,
|
|
websocket_pool text,
|
|
provider_observation_json jsonb,
|
|
attempt_count integer not null default 0,
|
|
upstream_send_state text not null default 'not_sent',
|
|
downstream_committed_at timestamptz,
|
|
outcome text not null default 'running',
|
|
client_status_code integer,
|
|
upstream_status_code integer,
|
|
client_response_id bytea,
|
|
upstream_request_id text,
|
|
upstream_response_id bytea,
|
|
error_kind text,
|
|
provider_error_code text,
|
|
error_message text,
|
|
retry_after_ms bigint,
|
|
input_tokens bigint,
|
|
output_tokens bigint,
|
|
cached_tokens bigint,
|
|
cache_write_tokens bigint,
|
|
reasoning_tokens bigint,
|
|
image_input_tokens bigint,
|
|
image_output_tokens bigint,
|
|
total_tokens bigint,
|
|
image_generation_succeeded boolean,
|
|
cost_source text not null default 'unavailable',
|
|
cost_amount numeric(20, 10),
|
|
cost_currency text,
|
|
transport_decision_wait_ms bigint,
|
|
connect_ms bigint,
|
|
headers_ms bigint,
|
|
first_event_ms bigint,
|
|
first_reasoning_ms bigint,
|
|
first_text_ms bigint,
|
|
first_token_ms bigint,
|
|
provider_processing_ms bigint,
|
|
latency_ms bigint,
|
|
client_ip inet,
|
|
user_agent text,
|
|
reasoning_effort text,
|
|
reasoning_preset text,
|
|
request_kind text,
|
|
subagent_kind text,
|
|
compact boolean not null default false,
|
|
image_generation_requested boolean not null default false,
|
|
started_at timestamptz not null,
|
|
deadline_at timestamptz not null,
|
|
completed_at timestamptz,
|
|
routing_scope text not null,
|
|
routing_group_refs text[] not null default '{}',
|
|
routing_group_names_snapshot jsonb not null default '[]'::jsonb,
|
|
admission_decision_ms bigint,
|
|
account_selection_wait_ms bigint,
|
|
capacity_used_slots bigint,
|
|
capacity_total_slots bigint,
|
|
raw_upstream_error text,
|
|
continuation_affinity_hash text,
|
|
continuation_previous_response_id_hash text,
|
|
continuation_requested boolean not null default false,
|
|
continuation_unavailable_reason text,
|
|
upstream_connection_id text,
|
|
upstream_connection_exit_reason text,
|
|
upstream_connection_age_ms bigint,
|
|
upstream_connection_idle_ms bigint,
|
|
recovery_request_id text,
|
|
recovered_at timestamptz,
|
|
recovery_attempt_count integer not null default 0,
|
|
recovery_retry_delay_ms bigint,
|
|
recovery_total_latency_ms bigint,
|
|
diagnostic_trace_json jsonb,
|
|
constraint model_requests_client_ref_ck check (
|
|
client_api_key_id is null or client_api_key_id = client_api_key_ref
|
|
),
|
|
constraint model_requests_account_ref_ck check (
|
|
provider_account_id is null or provider_account_id = provider_account_ref
|
|
),
|
|
constraint model_requests_revision_attempt_ck check (
|
|
config_revision > 0 and attempt_count >= 0
|
|
),
|
|
constraint model_requests_send_state_ck check (
|
|
upstream_send_state in ('not_sent', 'sent', 'ambiguous')
|
|
and (
|
|
(
|
|
attempt_count = 0
|
|
and upstream_send_state = 'not_sent'
|
|
)
|
|
or (
|
|
attempt_count > 0
|
|
and provider_kind is not null
|
|
and provider_account_ref is not null
|
|
and upstream_transport is not null
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_outcome_ck check (
|
|
outcome in ('running', 'succeeded', 'failed', 'cancelled', 'incomplete')
|
|
),
|
|
constraint model_requests_status_ck check (
|
|
(client_status_code is null or client_status_code between 100 and 599)
|
|
and (upstream_status_code is null or upstream_status_code between 100 and 599)
|
|
and (retry_after_ms is null or retry_after_ms >= 0)
|
|
),
|
|
constraint model_requests_tokens_ck check (
|
|
(input_tokens is null or input_tokens >= 0)
|
|
and (output_tokens is null or output_tokens >= 0)
|
|
and (cached_tokens is null or cached_tokens >= 0)
|
|
and (cache_write_tokens is null or cache_write_tokens >= 0)
|
|
and (reasoning_tokens is null or reasoning_tokens >= 0)
|
|
and (image_input_tokens is null or image_input_tokens >= 0)
|
|
and (image_output_tokens is null or image_output_tokens >= 0)
|
|
and (total_tokens is null or total_tokens >= 0)
|
|
),
|
|
constraint model_requests_image_generation_ck check (
|
|
(
|
|
not image_generation_requested
|
|
and image_generation_succeeded is null
|
|
)
|
|
or (
|
|
image_generation_requested
|
|
and (
|
|
(outcome = 'running' and image_generation_succeeded is null)
|
|
or (outcome <> 'running' and image_generation_succeeded is not null)
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_websocket_pool_ck check (
|
|
websocket_pool is null or websocket_pool in ('new', 'reuse')
|
|
),
|
|
constraint model_requests_provider_observation_ck check (
|
|
provider_observation_json is null
|
|
or jsonb_typeof(provider_observation_json) = 'object'
|
|
),
|
|
constraint model_requests_service_tier_ck check (
|
|
service_tier is null
|
|
or (
|
|
octet_length(service_tier) between 1 and 64
|
|
and service_tier !~ '[[:cntrl:]]'
|
|
)
|
|
),
|
|
constraint model_requests_routing_scope_ck check (
|
|
routing_scope in ('legacy_provider', 'all', 'groups')
|
|
),
|
|
constraint model_requests_routing_group_names_ck check (
|
|
jsonb_typeof(routing_group_names_snapshot) = 'array'
|
|
and (
|
|
(
|
|
routing_scope in ('legacy_provider', 'all')
|
|
and cardinality(routing_group_refs) = 0
|
|
and jsonb_array_length(routing_group_names_snapshot) = 0
|
|
)
|
|
or (
|
|
routing_scope = 'groups'
|
|
and cardinality(routing_group_refs) > 0
|
|
and array_position(routing_group_refs, null) is null
|
|
and jsonb_array_length(routing_group_names_snapshot)
|
|
= cardinality(routing_group_refs)
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_cost_ck check (
|
|
cost_source in ('provider_reported', 'calculated', 'unavailable')
|
|
and (
|
|
(
|
|
cost_source = 'unavailable'
|
|
and cost_amount is null
|
|
and cost_currency is null
|
|
)
|
|
or (
|
|
cost_source in ('provider_reported', 'calculated')
|
|
and cost_amount is not null
|
|
and cost_amount >= 0
|
|
and cost_currency ~ '^[A-Z]{3}$'
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_latency_ck check (
|
|
(transport_decision_wait_ms is null or transport_decision_wait_ms >= 0)
|
|
and (connect_ms is null or connect_ms >= 0)
|
|
and (headers_ms is null or headers_ms >= 0)
|
|
and (first_event_ms is null or first_event_ms >= 0)
|
|
and (first_reasoning_ms is null or first_reasoning_ms >= 0)
|
|
and (first_text_ms is null or first_text_ms >= 0)
|
|
and (first_token_ms is null or first_token_ms >= 0)
|
|
and (provider_processing_ms is null or provider_processing_ms >= 0)
|
|
and (latency_ms is null or latency_ms >= 0)
|
|
and (
|
|
latency_ms is null
|
|
or (
|
|
(transport_decision_wait_ms is null or transport_decision_wait_ms <= latency_ms)
|
|
and (connect_ms is null or connect_ms <= latency_ms)
|
|
and (headers_ms is null or headers_ms <= latency_ms)
|
|
and (first_event_ms is null or first_event_ms <= latency_ms)
|
|
and (first_reasoning_ms is null or first_reasoning_ms <= latency_ms)
|
|
and (first_text_ms is null or first_text_ms <= latency_ms)
|
|
and (first_token_ms is null or first_token_ms <= latency_ms)
|
|
and (provider_processing_ms is null or provider_processing_ms <= latency_ms)
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_lifecycle_ck check (
|
|
started_at <= deadline_at
|
|
and (
|
|
(outcome = 'running' and completed_at is null)
|
|
or (
|
|
outcome <> 'running'
|
|
and completed_at is not null
|
|
and completed_at >= started_at
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_client_fk foreign key (client_api_key_id)
|
|
references client_api_keys (id)
|
|
on update restrict
|
|
on delete set null,
|
|
constraint model_requests_account_fk foreign key (provider_account_id)
|
|
references provider_accounts (id)
|
|
on update restrict
|
|
on delete set null,
|
|
constraint model_requests_runtime_pressure_ck check (
|
|
(admission_decision_ms is null or admission_decision_ms >= 0)
|
|
and (account_selection_wait_ms is null or account_selection_wait_ms >= 0)
|
|
and (
|
|
(capacity_used_slots is null and capacity_total_slots is null)
|
|
or (
|
|
capacity_used_slots >= 0
|
|
and capacity_total_slots > 0
|
|
and capacity_used_slots <= capacity_total_slots
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_continuation_hashes_ck check (
|
|
(continuation_affinity_hash is null
|
|
or continuation_affinity_hash ~ '^[0-9a-f]{64}$')
|
|
and (continuation_previous_response_id_hash is null
|
|
or continuation_previous_response_id_hash ~ '^[0-9a-f]{64}$')
|
|
and (continuation_requested
|
|
or continuation_previous_response_id_hash is null)
|
|
),
|
|
constraint model_requests_continuation_reason_ck check (
|
|
continuation_unavailable_reason is null
|
|
or (
|
|
octet_length(continuation_unavailable_reason) between 1 and 64
|
|
and continuation_unavailable_reason ~ '^[a-z][a-z0-9_]*$'
|
|
)
|
|
),
|
|
constraint model_requests_upstream_connection_ck check (
|
|
(
|
|
upstream_connection_id is null
|
|
and upstream_connection_exit_reason is null
|
|
and upstream_connection_age_ms is null
|
|
and upstream_connection_idle_ms is null
|
|
)
|
|
or (
|
|
octet_length(upstream_connection_id) between 1 and 128
|
|
and upstream_connection_id !~ '[[:cntrl:]]'
|
|
and octet_length(upstream_connection_exit_reason) between 1 and 64
|
|
and upstream_connection_exit_reason ~ '^[a-z][a-z0-9_]*$'
|
|
and upstream_connection_age_ms >= 0
|
|
and upstream_connection_idle_ms between 0 and upstream_connection_age_ms
|
|
)
|
|
),
|
|
constraint model_requests_recovery_ck check (
|
|
recovery_attempt_count >= 0
|
|
and (
|
|
(
|
|
recovered_at is null
|
|
and recovery_request_id is null
|
|
and recovery_retry_delay_ms is null
|
|
and recovery_total_latency_ms is null
|
|
)
|
|
or (
|
|
recovered_at is not null
|
|
and recovered_at >= completed_at
|
|
and recovery_request_id is not null
|
|
and octet_length(recovery_request_id) between 1 and 128
|
|
and recovery_attempt_count > 0
|
|
and recovery_retry_delay_ms >= 0
|
|
and recovery_total_latency_ms >= recovery_retry_delay_ms
|
|
)
|
|
)
|
|
),
|
|
constraint model_requests_diagnostic_trace_object
|
|
check (diagnostic_trace_json is null or jsonb_typeof(diagnostic_trace_json) = 'object'),
|
|
constraint model_requests_fact_completeness_ck check (
|
|
(provider_account_id is null or provider_account_ref is not null)
|
|
and (cost_source = 'unavailable' or cost_currency is not null)
|
|
and num_nonnulls(capacity_used_slots, capacity_total_slots) in (0, 2)
|
|
and num_nonnulls(
|
|
upstream_connection_id, upstream_connection_exit_reason,
|
|
upstream_connection_age_ms, upstream_connection_idle_ms
|
|
) in (0, 4)
|
|
and (
|
|
recovered_at is null
|
|
or (
|
|
completed_at is not null
|
|
and recovery_retry_delay_ms is not null
|
|
and recovery_total_latency_ms is not null
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
create index model_requests_client_idx
|
|
on model_requests (client_api_key_id)
|
|
where client_api_key_id is not null;
|
|
create index model_requests_account_idx
|
|
on model_requests (provider_account_id)
|
|
where provider_account_id is not null;
|
|
create index model_requests_started_idx
|
|
on model_requests (started_at desc, id desc);
|
|
create index model_requests_client_ref_idx
|
|
on model_requests (client_api_key_ref, started_at desc, id desc);
|
|
create index model_requests_account_ref_idx
|
|
on model_requests (provider_account_ref, started_at desc, id desc)
|
|
where provider_account_ref is not null;
|
|
create index model_requests_requested_model_idx
|
|
on model_requests (requested_model_id, started_at desc, id desc);
|
|
create index model_requests_upstream_model_idx
|
|
on model_requests (upstream_model_id, started_at desc, id desc)
|
|
where upstream_model_id is not null;
|
|
create index model_requests_provider_idx
|
|
on model_requests (provider_kind, started_at desc, id desc)
|
|
where provider_kind is not null;
|
|
create index model_requests_outcome_idx
|
|
on model_requests (outcome, started_at desc, id desc);
|
|
create index model_requests_running_deadline_idx
|
|
on model_requests (deadline_at, id)
|
|
where outcome = 'running';
|
|
create index model_requests_retention_idx
|
|
on model_requests (completed_at)
|
|
where outcome <> 'running';
|
|
create index model_requests_routing_group_refs_idx
|
|
on model_requests using gin (routing_group_refs);
|
|
|
|
create index model_requests_pending_continuation_recovery_idx
|
|
on model_requests (
|
|
client_api_key_ref,
|
|
continuation_affinity_hash,
|
|
completed_at desc,
|
|
id desc
|
|
)
|
|
where error_kind = 'continuation_recovery_required'
|
|
and recovered_at is null
|
|
and continuation_affinity_hash is not null;
|
|
|
|
create index model_requests_recovery_request_idx
|
|
on model_requests (recovery_request_id)
|
|
where recovery_request_id is not null;
|
|
|
|
create index model_requests_pending_ws_transport_recovery_idx
|
|
on model_requests (
|
|
client_api_key_ref,
|
|
continuation_affinity_hash,
|
|
completed_at desc,
|
|
id desc
|
|
)
|
|
where provider_kind = 'openai'
|
|
and outcome = 'failed'
|
|
and error_kind = 'upstream_unavailable'
|
|
and upstream_transport = 'websocket'
|
|
and upstream_send_state = 'ambiguous'
|
|
and downstream_committed_at is null
|
|
and recovered_at is null
|
|
and continuation_affinity_hash is not null;
|
|
|
|
-- 运行期警告与错误事件。
|
|
create table ops_events (
|
|
id text primary key,
|
|
model_request_id text,
|
|
attempt_index integer,
|
|
level text not null,
|
|
component text not null,
|
|
operation text not null,
|
|
provider_kind text,
|
|
provider_account_id text,
|
|
provider_account_ref text,
|
|
provider_account_name_snapshot text,
|
|
provider_account_email_snapshot text,
|
|
provider_account_authentication_kind_snapshot text,
|
|
upstream_model_id text,
|
|
failure_kind text not null,
|
|
status_code integer,
|
|
provider_error_code text,
|
|
retry_after_ms bigint,
|
|
upstream_request_id text,
|
|
latency_ms bigint,
|
|
message text not null,
|
|
created_at timestamptz not null,
|
|
upstream_send_state text,
|
|
raw_upstream_error text,
|
|
constraint ops_events_request_attempt_ck check (
|
|
(
|
|
model_request_id is null
|
|
and attempt_index is null
|
|
)
|
|
or (
|
|
model_request_id is not null
|
|
and attempt_index is not null
|
|
and attempt_index > 0
|
|
)
|
|
),
|
|
constraint ops_events_level_ck check (level in ('warning', 'error')),
|
|
constraint ops_events_values_ck check (
|
|
(status_code is null or status_code between 100 and 599)
|
|
and (retry_after_ms is null or retry_after_ms >= 0)
|
|
and (latency_ms is null or latency_ms >= 0)
|
|
),
|
|
constraint ops_events_account_ref_ck check (
|
|
provider_account_id is null or provider_account_id = provider_account_ref
|
|
),
|
|
constraint ops_events_request_fk foreign key (model_request_id)
|
|
references model_requests (id)
|
|
on update restrict
|
|
on delete cascade,
|
|
constraint ops_events_account_fk foreign key (provider_account_id)
|
|
references provider_accounts (id)
|
|
on update restrict
|
|
on delete set null,
|
|
constraint ops_events_upstream_send_state_ck check (
|
|
upstream_send_state is null
|
|
or upstream_send_state in ('not_sent', 'sent', 'ambiguous')
|
|
),
|
|
constraint ops_events_account_snapshot_present_ck check (
|
|
provider_account_id is null or provider_account_ref is not null
|
|
)
|
|
);
|
|
|
|
create index ops_events_request_idx
|
|
on ops_events (model_request_id, attempt_index, id);
|
|
create index ops_events_account_idx
|
|
on ops_events (provider_account_id)
|
|
where provider_account_id is not null;
|
|
create index ops_events_created_idx
|
|
on ops_events (created_at desc, id desc);
|
|
create index ops_events_account_ref_idx
|
|
on ops_events (provider_account_ref, created_at desc, id desc)
|
|
where provider_account_ref is not null;
|
|
create index ops_events_retention_idx
|
|
on ops_events (created_at)
|
|
where model_request_id is null;
|
|
|
|
insert into runtime_settings (id, updated_at)
|
|
values (1, now());
|
|
|
|
-- S3 兼容对象存储、备份计划与保留策略。
|
|
create table backup_settings (
|
|
id bigint primary key,
|
|
storage_revision bigint not null default 1,
|
|
endpoint text,
|
|
region text,
|
|
bucket text,
|
|
access_key_id text,
|
|
secret_access_key text,
|
|
prefix text,
|
|
force_path_style boolean not null default false,
|
|
schedule_enabled boolean not null default false,
|
|
cron_expression text,
|
|
schedule_timezone text default 'Asia/Shanghai',
|
|
retention_days bigint not null default 0,
|
|
retention_count bigint not null default 0,
|
|
next_run_at timestamptz,
|
|
last_verified_at timestamptz,
|
|
updated_at timestamptz not null,
|
|
constraint backup_settings_singleton_ck check (id = 1),
|
|
constraint backup_settings_revision_ck check (storage_revision > 0),
|
|
constraint backup_settings_endpoint_ck check (
|
|
endpoint is null
|
|
or (
|
|
endpoint ~ '^https?://'
|
|
and endpoint !~ '[[:space:][:cntrl:]]'
|
|
and endpoint !~ '://[^/]*@'
|
|
)
|
|
),
|
|
constraint backup_settings_lengths_ck check (
|
|
(bucket is null or (octet_length(bucket) between 1 and 255 and bucket !~ '[[:cntrl:]]'))
|
|
and (region is null or (octet_length(region) between 1 and 64 and region !~ '[[:cntrl:]]'))
|
|
and (prefix is null or (octet_length(prefix) <= 1024 and prefix !~ '[[:cntrl:]]'))
|
|
and (
|
|
access_key_id is null
|
|
or (octet_length(access_key_id) between 1 and 255 and access_key_id !~ '[[:cntrl:]]')
|
|
)
|
|
and (
|
|
secret_access_key is null
|
|
or (
|
|
octet_length(secret_access_key) between 1 and 512
|
|
and secret_access_key !~ '[[:cntrl:]]'
|
|
)
|
|
)
|
|
),
|
|
constraint backup_settings_schedule_ck check (
|
|
(cron_expression is null or cron_expression ~ '^[^[:space:]]+([[:space:]]+[^[:space:]]+){4}$')
|
|
and (
|
|
schedule_timezone is null
|
|
or (octet_length(schedule_timezone) between 1 and 64 and schedule_timezone !~ '[[:cntrl:]]')
|
|
)
|
|
and retention_days >= 0
|
|
and retention_count >= 0
|
|
and (
|
|
not schedule_enabled
|
|
or (
|
|
endpoint is not null
|
|
and bucket is not null
|
|
and region is not null
|
|
and access_key_id is not null
|
|
and secret_access_key is not null
|
|
and prefix is not null
|
|
and cron_expression is not null
|
|
and schedule_timezone is not null
|
|
and last_verified_at is not null
|
|
)
|
|
)
|
|
),
|
|
-- 计划启停与游标一致性:启用必有 next_run_at,关闭必清空。
|
|
constraint backup_settings_cursor_ck check (
|
|
(not schedule_enabled and next_run_at is null)
|
|
or (schedule_enabled and next_run_at is not null)
|
|
)
|
|
);
|
|
|
|
insert into backup_settings (
|
|
id,
|
|
storage_revision,
|
|
schedule_enabled,
|
|
retention_days,
|
|
retention_count,
|
|
force_path_style,
|
|
schedule_timezone,
|
|
updated_at
|
|
)
|
|
values (1, 1, false, 0, 0, false, 'Asia/Shanghai', now());
|
|
|
|
-- 备份任务状态与归档信息。
|
|
create table backup_records (
|
|
id text primary key,
|
|
trigger_kind text not null,
|
|
status text not null,
|
|
scheduled_at timestamptz,
|
|
object_key text not null,
|
|
size_bytes bigint,
|
|
sha256 text,
|
|
expires_at timestamptz,
|
|
attempt_count integer not null default 0,
|
|
error_code text,
|
|
error_message text,
|
|
started_at timestamptz,
|
|
completed_at timestamptz,
|
|
created_at timestamptz not null,
|
|
updated_at timestamptz not null,
|
|
constraint backup_records_id_ck check (id ~ '^backup_[0-9a-f]{32}$'),
|
|
constraint backup_records_trigger_kind_ck check (
|
|
trigger_kind in ('manual', 'scheduled')
|
|
and (
|
|
(trigger_kind = 'manual' and scheduled_at is null)
|
|
or (trigger_kind = 'scheduled' and scheduled_at is not null)
|
|
)
|
|
),
|
|
constraint backup_records_status_ck check (
|
|
status in ('queued', 'dumping', 'uploading', 'completed', 'failed', 'deleting')
|
|
),
|
|
constraint backup_records_attempt_ck check (attempt_count >= 0),
|
|
constraint backup_records_lifecycle_ck check (
|
|
created_at <= updated_at
|
|
and (started_at is null or started_at >= created_at)
|
|
and (completed_at is null or completed_at >= created_at)
|
|
and (
|
|
(
|
|
status = 'queued'
|
|
and started_at is null
|
|
and completed_at is null
|
|
)
|
|
or (
|
|
status in ('dumping', 'uploading')
|
|
and started_at is not null
|
|
and completed_at is null
|
|
)
|
|
or (
|
|
status in ('completed', 'failed', 'deleting')
|
|
and started_at is not null
|
|
and completed_at is not null
|
|
)
|
|
)
|
|
),
|
|
constraint backup_records_artifact_ck check (
|
|
(
|
|
status not in ('uploading', 'completed')
|
|
or (size_bytes is not null and sha256 is not null)
|
|
)
|
|
and (size_bytes is null or size_bytes >= 0)
|
|
and (sha256 is null or sha256 ~ '^[0-9a-f]{64}$')
|
|
),
|
|
constraint backup_records_error_ck check (
|
|
(error_code is null) = (error_message is null)
|
|
and (status <> 'failed' or (error_code is not null and error_message is not null))
|
|
and (
|
|
status not in ('queued', 'dumping', 'uploading', 'completed')
|
|
or error_code is null
|
|
)
|
|
),
|
|
-- expires_at 为空,或不早于创建时间(创建时 now + 天数 恒晚于 created_at)。
|
|
constraint backup_records_expiry_ck check (
|
|
expires_at is null or expires_at >= created_at
|
|
),
|
|
constraint backup_records_completion_order_ck check (
|
|
completed_at is null or completed_at >= started_at
|
|
)
|
|
);
|
|
|
|
create index backup_records_created_idx
|
|
on backup_records (created_at desc, id desc);
|
|
create index backup_records_status_idx
|
|
on backup_records (status, created_at, id);
|
|
create unique index backup_records_scheduled_uq
|
|
on backup_records (scheduled_at)
|
|
where trigger_kind = 'scheduled' and scheduled_at is not null;
|
|
create unique index backup_records_active_uq
|
|
on backup_records ((1))
|
|
where status in ('queued', 'dumping', 'uploading');
|
|
create unique index backup_records_object_key_uq
|
|
on backup_records (object_key)
|
|
where object_key is not null;
|
|
create index backup_records_scheduled_completed_idx
|
|
on backup_records (completed_at desc, id desc)
|
|
where trigger_kind = 'scheduled' and status = 'completed';
|
|
create index backup_records_retention_idx
|
|
on backup_records (expires_at)
|
|
where expires_at is not null and status in ('completed', 'failed');
|