mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 16:06:08 +00:00
refactor(store): consolidate database migrations into initial baseline
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>
This commit is contained in:
@@ -55,10 +55,6 @@ pub(super) fn admin_account_store(pool: &PgPool) -> PgAdminAccountStore {
|
||||
|
||||
impl TestDatabase {
|
||||
pub(super) async fn create(label: &str) -> Option<Self> {
|
||||
Self::create_at(label, i64::MAX).await
|
||||
}
|
||||
|
||||
pub(super) async fn create_at(label: &str, version: i64) -> Option<Self> {
|
||||
let database_url = crate::support::test_env("CPR_TEST_DATABASE_URL")?;
|
||||
let schema = format!("cpr_store_{label}_{}", Uuid::new_v4().simple());
|
||||
let admin = PgPoolOptions::new()
|
||||
@@ -86,16 +82,10 @@ impl TestDatabase {
|
||||
.connect(&database_url)
|
||||
.await
|
||||
.expect("connect isolated test schema");
|
||||
sqlx::migrate::Migrator::with_migrations(
|
||||
TEST_MIGRATOR
|
||||
.iter()
|
||||
.filter(|migration| migration.version <= version)
|
||||
.cloned()
|
||||
.collect(),
|
||||
)
|
||||
.run(&pool)
|
||||
.await
|
||||
.expect("apply test migrations");
|
||||
TEST_MIGRATOR
|
||||
.run(&pool)
|
||||
.await
|
||||
.expect("apply test migrations");
|
||||
Some(Self {
|
||||
admin,
|
||||
pool,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{TEST_MIGRATOR, TestDatabase};
|
||||
use super::TestDatabase;
|
||||
|
||||
async fn seed_request(pool: &sqlx::PgPool) {
|
||||
sqlx::query(
|
||||
@@ -101,8 +101,8 @@ async fn account_snapshots_are_required_before_live_foreign_keys_can_be_cleared(
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn upgrade_normalizes_admin_identity_and_preserves_existing_event_rows() {
|
||||
let Some(db) = TestDatabase::create_at("audit_upgrade", 7).await else {
|
||||
async fn audit_requires_canonical_admin_identity_and_retains_it_after_admin_deletion() {
|
||||
let Some(db) = TestDatabase::create("audit_identity").await else {
|
||||
return;
|
||||
};
|
||||
sqlx::raw_sql(
|
||||
@@ -110,28 +110,12 @@ async fn upgrade_normalizes_admin_identity_and_preserves_existing_event_rows() {
|
||||
values ('admin_test', 'test_hash', now(), now());
|
||||
insert into admin_audit_events (id, actor_kind, actor_admin_user_id, actor_ref,
|
||||
action, entity_kind, entity_ref, created_at)
|
||||
values ('audit_before_upgrade', 'admin_session', 'admin_test', 'admin_test',
|
||||
'update', 'settings', '1', now());
|
||||
insert into ops_events (id, level, component, operation, failure_kind, message, created_at)
|
||||
values ('ops_before_upgrade', 'error', 'probe', 'probe', 'timeout', 'test', now());",
|
||||
values ('audit_identity', 'admin_session', 'admin_test', 'admin:admin_test',
|
||||
'update', 'settings', '1', now());",
|
||||
)
|
||||
.execute(&db.pool)
|
||||
.await
|
||||
.expect("seed data from pre-optimization schema");
|
||||
TEST_MIGRATOR
|
||||
.run(&db.pool)
|
||||
.await
|
||||
.expect("upgrade populated database");
|
||||
let identity: String = sqlx::query_scalar("select actor_ref from admin_audit_events")
|
||||
.fetch_one(&db.pool)
|
||||
.await
|
||||
.expect("read normalized identity");
|
||||
assert_eq!(identity, "admin:admin_test");
|
||||
let events: i64 = sqlx::query_scalar("select count(*) from ops_events")
|
||||
.fetch_one(&db.pool)
|
||||
.await
|
||||
.expect("read retained events");
|
||||
assert_eq!(events, 1);
|
||||
.expect("seed audit event with canonical admin identity");
|
||||
let error = sqlx::query("update admin_audit_events set actor_ref = 'admin_test'")
|
||||
.execute(&db.pool)
|
||||
.await
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
60bcece284f2d6a2cd3e8dea67833dfc1c7da625cdd5f0b63884c725fa00a3d4 0001_initial.sql
|
||||
93219760c733616e7bba9a8933c207b26752b2ebf7e6edfe1ef42ba227c50a7d 0002_runtime_pressure_observability.sql
|
||||
1b6ffab434c754814aebb0eee3b650bd9770010059472bccf5bc856b319880ca 0003_client_min_versions.sql
|
||||
22f912ab3e196de7983ef50a1dba5aee340bbea82bdd15d05863e87012000c83 0004_ops_error_send_state.sql
|
||||
194d7b4bedd83f0d76428a21699b0e946449bf2ba61a0fdf8710ebfd421af8b2 0005_openai_continuation_recovery_observability.sql
|
||||
3b4bfd5e3f5135fa2fad1088b0c2a287861e1250d66af31dbd40dc890751b567 0006_openai_session_transport_recovery.sql
|
||||
8c0ec7fab80d95dfd5a370c70706c36ac5907bb5afea0cf1886196f21fd8317d 0007_request_diagnostics.sql
|
||||
374f0d8fa8b671ca845d085f7d173b2aa57320e7e5c41e0398329f0bdd89506e 0008_persisted_fact_integrity.sql
|
||||
de20c9c9c57fcf0371a2245a3de9a57d72ab75d1c08ffff92f76b2b270d6a72c 0009_trim_observability_indexes.sql
|
||||
661aba0469d4b9e4626e9e9331015bb2120cc948e8d15b1ca83a394a5007e11a 0010_remove_unused_event_aggregation.sql
|
||||
b7d3df7b48d835a00a3b74cbefc958a039d38fea5529cdad04bfbc703dca72aa 0001_initial.sql
|
||||
|
||||
@@ -33,18 +33,18 @@ create table admin_audit_events (
|
||||
constraint admin_audit_events_actor_fk foreign key (actor_admin_user_id)
|
||||
references admin_users (id)
|
||||
on update restrict
|
||||
on delete set null
|
||||
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, created_at desc, id desc)
|
||||
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);
|
||||
create index admin_audit_events_entity_idx
|
||||
on admin_audit_events (entity_kind, entity_ref, created_at desc, id desc);
|
||||
create index admin_audit_events_actor_ref_idx
|
||||
on admin_audit_events (actor_ref, created_at desc, id desc);
|
||||
|
||||
-- 客户端 API 密钥、鉴权范围与限额。
|
||||
create table client_api_keys (
|
||||
@@ -70,8 +70,6 @@ create table client_api_keys (
|
||||
|
||||
create index client_api_keys_created_idx
|
||||
on client_api_keys (created_at desc, id desc);
|
||||
create index client_api_keys_last_used_idx
|
||||
on client_api_keys (last_used_at desc, id desc);
|
||||
|
||||
-- 全局运行参数与配置版本。
|
||||
create table runtime_settings (
|
||||
@@ -88,6 +86,8 @@ create table runtime_settings (
|
||||
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 (
|
||||
@@ -107,6 +107,24 @@ create table runtime_settings (
|
||||
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:]]'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -224,13 +242,8 @@ 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_quota_access_idx
|
||||
on provider_accounts (quota_access_state, quota_reset_at, id);
|
||||
create index provider_accounts_access_expiry_idx
|
||||
on provider_accounts (access_token_expires_at, id);
|
||||
create index provider_accounts_refresh_due_idx
|
||||
on provider_accounts (next_refresh_at, id)
|
||||
where enabled and has_refresh_token and next_refresh_at is not null;
|
||||
create index provider_accounts_email_idx
|
||||
on provider_accounts (provider_kind, lower(email))
|
||||
where email is not null;
|
||||
@@ -266,8 +279,6 @@ create table account_groups (
|
||||
|
||||
create unique index account_groups_name_uq
|
||||
on account_groups (lower(name));
|
||||
create index account_groups_list_idx
|
||||
on account_groups (enabled, created_at desc, id desc);
|
||||
|
||||
create table account_group_accounts (
|
||||
account_group_id text not null,
|
||||
@@ -376,6 +387,25 @@ create table model_requests (
|
||||
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
|
||||
),
|
||||
@@ -523,18 +553,97 @@ create table model_requests (
|
||||
constraint model_requests_account_fk foreign key (provider_account_id)
|
||||
references provider_accounts (id)
|
||||
on update restrict
|
||||
on delete set null
|
||||
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, started_at desc, id desc);
|
||||
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,
|
||||
provider_kind,
|
||||
started_at desc,
|
||||
id desc
|
||||
);
|
||||
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
|
||||
@@ -561,6 +670,37 @@ create index model_requests_retention_idx
|
||||
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,
|
||||
@@ -583,8 +723,9 @@ create table ops_events (
|
||||
upstream_request_id text,
|
||||
latency_ms bigint,
|
||||
message text not null,
|
||||
occurrence_count integer not null default 1,
|
||||
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
|
||||
@@ -601,7 +742,6 @@ create table ops_events (
|
||||
(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)
|
||||
and occurrence_count > 0
|
||||
),
|
||||
constraint ops_events_account_ref_ck check (
|
||||
provider_account_id is null or provider_account_id = provider_account_ref
|
||||
@@ -613,24 +753,23 @@ create table ops_events (
|
||||
constraint ops_events_account_fk foreign key (provider_account_id)
|
||||
references provider_accounts (id)
|
||||
on update restrict
|
||||
on delete set null
|
||||
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,
|
||||
provider_kind,
|
||||
created_at desc,
|
||||
id desc
|
||||
);
|
||||
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_component_idx
|
||||
on ops_events (component, created_at desc, id desc);
|
||||
create index ops_events_failure_idx
|
||||
on ops_events (failure_kind, 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;
|
||||
@@ -798,6 +937,9 @@ create table backup_records (
|
||||
-- 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
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
-- 请求热路径的准入判定、账号选择等待与账号池容量观测。
|
||||
alter table model_requests
|
||||
add column admission_decision_ms bigint,
|
||||
add column account_selection_wait_ms bigint,
|
||||
add column capacity_used_slots bigint,
|
||||
add column capacity_total_slots bigint;
|
||||
|
||||
alter table model_requests
|
||||
add 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
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -1,21 +0,0 @@
|
||||
alter table runtime_settings
|
||||
add column min_codex_desktop_version text,
|
||||
add column min_codex_cli_version text,
|
||||
add 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:]]'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
-- 为运维错误保留 payload 发送边界与上游错误原文。
|
||||
alter table model_requests
|
||||
add column raw_upstream_error text;
|
||||
|
||||
alter table ops_events
|
||||
add column upstream_send_state text,
|
||||
add column raw_upstream_error text,
|
||||
add constraint ops_events_upstream_send_state_ck check (
|
||||
upstream_send_state is null
|
||||
or upstream_send_state in ('not_sent', 'sent', 'ambiguous')
|
||||
);
|
||||
@@ -1,80 +0,0 @@
|
||||
alter table model_requests
|
||||
add column continuation_affinity_hash text,
|
||||
add column continuation_previous_response_id_hash text,
|
||||
add column continuation_requested boolean not null default false,
|
||||
add column continuation_unavailable_reason text,
|
||||
add column upstream_connection_id text,
|
||||
add column upstream_connection_exit_reason text,
|
||||
add column upstream_connection_age_ms bigint,
|
||||
add column upstream_connection_idle_ms bigint,
|
||||
add column recovery_request_id text,
|
||||
add column recovered_at timestamptz,
|
||||
add column recovery_attempt_count integer not null default 0,
|
||||
add column recovery_retry_delay_ms bigint,
|
||||
add column recovery_total_latency_ms bigint,
|
||||
add 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)
|
||||
),
|
||||
add 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_]*$'
|
||||
)
|
||||
),
|
||||
add 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
|
||||
)
|
||||
),
|
||||
add 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
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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;
|
||||
@@ -1,61 +0,0 @@
|
||||
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;
|
||||
|
||||
with session_transport_recoveries as (
|
||||
select
|
||||
prior.id as failed_request_id,
|
||||
recovered.id as recovery_request_id,
|
||||
recovered.started_at as recovery_started_at,
|
||||
recovered.completed_at as recovered_at
|
||||
from model_requests prior
|
||||
cross join lateral (
|
||||
select current.id, current.started_at, current.completed_at
|
||||
from model_requests current
|
||||
where current.client_api_key_ref = prior.client_api_key_ref
|
||||
and current.continuation_affinity_hash = prior.continuation_affinity_hash
|
||||
and current.provider_kind = prior.provider_kind
|
||||
and current.outcome = 'succeeded'
|
||||
and current.upstream_transport = 'http_sse'
|
||||
and current.completed_at is not null
|
||||
and current.started_at >= prior.completed_at
|
||||
and current.started_at <= prior.completed_at + interval '30 seconds'
|
||||
order by current.started_at, current.id
|
||||
limit 1
|
||||
) recovered
|
||||
where prior.provider_kind = 'openai'
|
||||
and prior.outcome = 'failed'
|
||||
and prior.error_kind = 'upstream_unavailable'
|
||||
and prior.upstream_transport = 'websocket'
|
||||
and prior.upstream_send_state = 'ambiguous'
|
||||
and prior.downstream_committed_at is null
|
||||
and prior.recovered_at is null
|
||||
and prior.continuation_affinity_hash is not null
|
||||
)
|
||||
update model_requests prior
|
||||
set recovery_attempt_count = prior.recovery_attempt_count + 1,
|
||||
recovery_request_id = recovery.recovery_request_id,
|
||||
recovered_at = recovery.recovered_at,
|
||||
recovery_retry_delay_ms = greatest(
|
||||
0,
|
||||
floor(extract(epoch from (recovery.recovery_started_at - prior.completed_at)) * 1000)
|
||||
)::bigint,
|
||||
recovery_total_latency_ms = greatest(
|
||||
0,
|
||||
floor(extract(epoch from (recovery.recovered_at - prior.completed_at)) * 1000)
|
||||
)::bigint
|
||||
from session_transport_recoveries recovery
|
||||
where prior.id = recovery.failed_request_id
|
||||
and prior.recovered_at is null;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- Bounded request evidence is retained with its owning model request.
|
||||
-- NULL identifies historical requests for which no trace was captured.
|
||||
alter table model_requests add column diagnostic_trace_json jsonb;
|
||||
alter table model_requests add constraint model_requests_diagnostic_trace_object
|
||||
check (diagnostic_trace_json is null or jsonb_typeof(diagnostic_trace_json) = 'object');
|
||||
@@ -1,41 +0,0 @@
|
||||
-- Existing value checks also need explicit presence checks: CHECK accepts NULL.
|
||||
alter table model_requests
|
||||
add 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
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
alter table ops_events
|
||||
add constraint ops_events_account_snapshot_present_ck check (
|
||||
provider_account_id is null or provider_account_ref is not null
|
||||
);
|
||||
|
||||
alter table backup_records
|
||||
add constraint backup_records_completion_order_ck check (
|
||||
completed_at is null or completed_at >= started_at
|
||||
);
|
||||
|
||||
update admin_audit_events
|
||||
set actor_ref = 'admin:' || actor_admin_user_id
|
||||
where actor_admin_user_id is not null
|
||||
and actor_kind = 'admin_session'
|
||||
and actor_ref is distinct from 'admin:' || actor_admin_user_id;
|
||||
|
||||
alter table admin_audit_events
|
||||
add 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)
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
-- Live references support FK maintenance; historical queries use *_ref indexes.
|
||||
drop index model_requests_account_idx;
|
||||
create index model_requests_account_idx on model_requests (provider_account_id)
|
||||
where provider_account_id is not null;
|
||||
|
||||
drop index model_requests_client_idx;
|
||||
create index model_requests_client_idx on model_requests (client_api_key_id)
|
||||
where client_api_key_id is not null;
|
||||
|
||||
drop index ops_events_account_idx;
|
||||
create index ops_events_account_idx on ops_events (provider_account_id)
|
||||
where provider_account_id is not null;
|
||||
|
||||
drop index admin_audit_events_actor_idx;
|
||||
create index admin_audit_events_actor_idx on admin_audit_events (actor_admin_user_id)
|
||||
where actor_admin_user_id is not null;
|
||||
|
||||
-- Key usage is updated continuously; a small management list can sort on demand.
|
||||
drop index client_api_keys_last_used_idx;
|
||||
|
||||
-- These leading keys no longer match the current runtime queries.
|
||||
drop index provider_accounts_quota_access_idx;
|
||||
drop index provider_accounts_refresh_due_idx;
|
||||
drop index account_groups_list_idx;
|
||||
drop index ops_events_component_idx;
|
||||
drop index ops_events_failure_idx;
|
||||
drop index admin_audit_events_actor_ref_idx;
|
||||
drop index admin_audit_events_entity_idx;
|
||||
@@ -1,8 +0,0 @@
|
||||
-- Each row records one event; aggregation is performed by the read queries.
|
||||
alter table ops_events drop constraint ops_events_values_ck;
|
||||
alter table ops_events drop column occurrence_count;
|
||||
alter table ops_events add 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)
|
||||
);
|
||||
@@ -3,8 +3,8 @@
|
||||
sqlx 在服务启动、监听请求之前按编号执行迁移,并把文件 checksum 记入 `_sqlx_migrations`。
|
||||
已应用迁移的字节与记录不一致时,服务会拒绝启动。
|
||||
|
||||
`0001_initial.sql` 是初始基线,后续编号继续增加字段和索引;
|
||||
当前完整清单以本目录 SQL 文件和 `.frozen-sha256` 为准,不要只执行 `0001`。
|
||||
`0001_initial.sql` 定义数据库初始化基线,后续 schema 变更按编号新增迁移。
|
||||
完整清单以本目录 SQL 文件和 `.frozen-sha256` 为准。
|
||||
|
||||
## 冻结规则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user