codexLB-up 1.25.0b10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app/__init__.py +10 -0
- app/admin_cli.py +494 -0
- app/cli.py +339 -0
- app/codex_sessions_retag.py +487 -0
- app/core/__init__.py +0 -0
- app/core/audit/__init__.py +0 -0
- app/core/audit/service.py +216 -0
- app/core/audit/types.py +94 -0
- app/core/auth/__init__.py +236 -0
- app/core/auth/api_key_cache.py +65 -0
- app/core/auth/dashboard_access.py +429 -0
- app/core/auth/dashboard_mode.py +171 -0
- app/core/auth/dashboard_session_ttl.py +74 -0
- app/core/auth/dashboard_users_cache.py +120 -0
- app/core/auth/dependencies.py +710 -0
- app/core/auth/external_identity.py +78 -0
- app/core/auth/guardian.py +374 -0
- app/core/auth/models.py +49 -0
- app/core/auth/providers/__init__.py +129 -0
- app/core/auth/providers/oidc.py +838 -0
- app/core/auth/providers/registry.py +118 -0
- app/core/auth/refresh.py +394 -0
- app/core/auth/step_up.py +117 -0
- app/core/auth/totp.py +81 -0
- app/core/balancer/__init__.py +71 -0
- app/core/balancer/logic.py +1612 -0
- app/core/balancer/rendezvous_hash.py +21 -0
- app/core/balancer/types.py +21 -0
- app/core/bootstrap.py +172 -0
- app/core/cache/invalidation.py +426 -0
- app/core/clients/__init__.py +0 -0
- app/core/clients/codex.py +996 -0
- app/core/clients/codex_version.py +187 -0
- app/core/clients/codex_version_snapshot.py +2 -0
- app/core/clients/files.py +534 -0
- app/core/clients/headers.py +29 -0
- app/core/clients/http.py +511 -0
- app/core/clients/model_fetcher.py +276 -0
- app/core/clients/native_egress.py +1304 -0
- app/core/clients/oauth.py +477 -0
- app/core/clients/proxy.py +6089 -0
- app/core/clients/proxy_websocket.py +1411 -0
- app/core/clients/rate_limit_reset_credits.py +412 -0
- app/core/clients/stream_errors.py +12 -0
- app/core/clients/thread_cache_identity.py +347 -0
- app/core/clients/usage.py +467 -0
- app/core/clock.py +170 -0
- app/core/config/__init__.py +0 -0
- app/core/config/background_jobs.py +73 -0
- app/core/config/context_window_overrides.py +166 -0
- app/core/config/dashboard_overrides.py +145 -0
- app/core/config/inheritable.py +47 -0
- app/core/config/key_fingerprint.py +115 -0
- app/core/config/settings.py +872 -0
- app/core/config/settings_cache.py +100 -0
- app/core/config/spool_retention.py +217 -0
- app/core/config/tiers.py +174 -0
- app/core/conversation_archive.py +568 -0
- app/core/crypto.py +37 -0
- app/core/draining.py +11 -0
- app/core/errors.py +365 -0
- app/core/exceptions.py +155 -0
- app/core/handlers/__init__.py +3 -0
- app/core/handlers/exceptions.py +478 -0
- app/core/http_protocol.py +176 -0
- app/core/http_protocol_httptools.py +204 -0
- app/core/ingress_limits.py +17 -0
- app/core/metadata_store.py +18 -0
- app/core/metrics/__init__.py +0 -0
- app/core/metrics/middleware.py +74 -0
- app/core/metrics/prometheus.py +604 -0
- app/core/middleware/__init__.py +25 -0
- app/core/middleware/api_firewall.py +111 -0
- app/core/middleware/app_version.py +37 -0
- app/core/middleware/dashboard_auth_proxy.py +55 -0
- app/core/middleware/dashboard_csrf.py +177 -0
- app/core/middleware/dashboard_gzip.py +41 -0
- app/core/middleware/dashboard_overrides.py +59 -0
- app/core/middleware/firewall_cache.py +91 -0
- app/core/middleware/inflight.py +126 -0
- app/core/middleware/multipart_content_encoding.py +129 -0
- app/core/middleware/path_rewrite.py +148 -0
- app/core/middleware/request_body_limit.py +196 -0
- app/core/middleware/request_decompression.py +209 -0
- app/core/middleware/request_id.py +72 -0
- app/core/middleware/required_capability_http.py +116 -0
- app/core/middleware/trusted_proxy_headers.py +36 -0
- app/core/multipart.py +237 -0
- app/core/multipart_fields.py +91 -0
- app/core/openai/__init__.py +0 -0
- app/core/openai/chat_requests.py +493 -0
- app/core/openai/chat_responses.py +931 -0
- app/core/openai/contracts.py +46 -0
- app/core/openai/exceptions.py +24 -0
- app/core/openai/host_models.py +14 -0
- app/core/openai/images.py +413 -0
- app/core/openai/message_coercion.py +376 -0
- app/core/openai/model_refresh_scheduler.py +423 -0
- app/core/openai/model_registry.py +997 -0
- app/core/openai/model_registry_store.py +548 -0
- app/core/openai/models.py +172 -0
- app/core/openai/parsing.py +97 -0
- app/core/openai/requests.py +1980 -0
- app/core/openai/strict_schema.py +199 -0
- app/core/openai/tool_call_safety.py +99 -0
- app/core/openai/v1_requests.py +187 -0
- app/core/plan_types.py +90 -0
- app/core/prestop.py +202 -0
- app/core/rate_limiter/__init__.py +0 -0
- app/core/rate_limiter/db_rate_limiter.py +215 -0
- app/core/request_locality.py +473 -0
- app/core/resilience/__init__.py +0 -0
- app/core/resilience/backpressure.py +57 -0
- app/core/resilience/bulkhead.py +202 -0
- app/core/resilience/circuit_breaker.py +150 -0
- app/core/resilience/degradation.py +55 -0
- app/core/resilience/loop_lag_monitor.py +60 -0
- app/core/resilience/memory_monitor.py +137 -0
- app/core/resilience/network_recovery.py +256 -0
- app/core/resilience/overload.py +98 -0
- app/core/resilience/toggles.py +114 -0
- app/core/retention/__init__.py +0 -0
- app/core/retention/job.py +240 -0
- app/core/retention/scheduler.py +69 -0
- app/core/runtime_logging.py +601 -0
- app/core/scheduling/__init__.py +0 -0
- app/core/scheduling/leader_election.py +993 -0
- app/core/scheduling/leader_election_handle.py +25 -0
- app/core/server.py +140 -0
- app/core/shutdown.py +326 -0
- app/core/socket_peer.py +22 -0
- app/core/startup.py +41 -0
- app/core/timeout_invariants.py +347 -0
- app/core/tracing/__init__.py +0 -0
- app/core/tracing/otel.py +207 -0
- app/core/types.py +6 -0
- app/core/upstream_proxy/__init__.py +13 -0
- app/core/upstream_proxy/cache.py +130 -0
- app/core/upstream_proxy/resolver.py +189 -0
- app/core/upstream_proxy/types.py +104 -0
- app/core/usage/__init__.py +407 -0
- app/core/usage/depletion.py +109 -0
- app/core/usage/live_hub.py +41 -0
- app/core/usage/live_snapshots.py +174 -0
- app/core/usage/logs.py +212 -0
- app/core/usage/metadata_scheduler.py +118 -0
- app/core/usage/models.py +54 -0
- app/core/usage/pricing.py +576 -0
- app/core/usage/pricing_catalog.py +241 -0
- app/core/usage/pricing_snapshot.json +821 -0
- app/core/usage/quota.py +133 -0
- app/core/usage/refresh_policy.py +21 -0
- app/core/usage/refresh_scheduler.py +713 -0
- app/core/usage/reset_credits_refresh_scheduler.py +435 -0
- app/core/usage/types.py +168 -0
- app/core/utils/__init__.py +0 -0
- app/core/utils/json_guards.py +18 -0
- app/core/utils/locks.py +33 -0
- app/core/utils/masking.py +20 -0
- app/core/utils/proxy_env.py +104 -0
- app/core/utils/request_id.py +65 -0
- app/core/utils/retry.py +77 -0
- app/core/utils/shared_future.py +172 -0
- app/core/utils/sse.py +286 -0
- app/core/utils/time.py +24 -0
- app/db/__init__.py +0 -0
- app/db/account_identity_lock.py +56 -0
- app/db/alembic/__init__.py +1 -0
- app/db/alembic/env.py +70 -0
- app/db/alembic/revision_ids.py +43 -0
- app/db/alembic/versions/20260213_000000_base_schema.py +248 -0
- app/db/alembic/versions/20260213_000100_normalize_account_plan_types.py +45 -0
- app/db/alembic/versions/20260213_000200_add_request_logs_reasoning_effort.py +45 -0
- app/db/alembic/versions/20260213_000300_add_accounts_reset_at.py +45 -0
- app/db/alembic/versions/20260213_000400_add_accounts_chatgpt_account_id.py +45 -0
- app/db/alembic/versions/20260213_000500_add_dashboard_settings.py +70 -0
- app/db/alembic/versions/20260213_000600_add_dashboard_settings_totp.py +64 -0
- app/db/alembic/versions/20260213_000700_add_dashboard_settings_password.py +59 -0
- app/db/alembic/versions/20260213_000800_add_api_keys.py +87 -0
- app/db/alembic/versions/20260214_000000_add_api_key_limits.py +143 -0
- app/db/alembic/versions/20260215_000000_add_idx_logs_requested_at.py +38 -0
- app/db/alembic/versions/20260218_000000_add_api_key_usage_reservations.py +117 -0
- app/db/alembic/versions/20260218_000100_add_import_without_overwrite_and_drop_accounts_email_unique.py +198 -0
- app/db/alembic/versions/20260225_000000_add_dashboard_settings_routing_strategy.py +53 -0
- app/db/alembic/versions/20260228_020000_align_api_key_limit_enum_types.py +104 -0
- app/db/alembic/versions/20260228_030000_add_api_firewall_allowlist.py +42 -0
- app/db/alembic/versions/20260306_000000_add_request_logs_service_tier.py +45 -0
- app/db/alembic/versions/20260307_000000_add_api_key_enforcement_fields.py +56 -0
- app/db/alembic/versions/20260308_000000_add_sqlite_performance_indexes.py +41 -0
- app/db/alembic/versions/20260309_000000_add_additional_usage_history.py +71 -0
- app/db/alembic/versions/20260309_000000_request_logs_nullable_account_id.py +28 -0
- app/db/alembic/versions/20260310_000000_add_request_logs_transport.py +45 -0
- app/db/alembic/versions/20260310_000000_fix_postgresql_enum_value_casing.py +79 -0
- app/db/alembic/versions/20260310_120000_add_sticky_session_kinds_and_affinity_ttl.py +110 -0
- app/db/alembic/versions/20260312_000000_add_additional_usage_quota_key.py +193 -0
- app/db/alembic/versions/20260312_000000_split_sticky_sessions_primary_key_by_kind.py +96 -0
- app/db/alembic/versions/20260312_010000_merge_additional_usage_and_sticky_session_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_feature_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_sticky_heads.py +25 -0
- app/db/alembic/versions/20260312_120000_add_dashboard_upstream_stream_transport.py +62 -0
- app/db/alembic/versions/20260313_024500_merge_request_log_transport_heads.py +27 -0
- app/db/alembic/versions/20260319_100937_increase_prompt_cache_ttl_to_1800s.py +73 -0
- app/db/alembic/versions/20260319_111500_merge_prompt_cache_ttl_and_upstream_transport_heads.py +25 -0
- app/db/alembic/versions/20260319_183000_normalize_sqlite_account_status_casing.py +48 -0
- app/db/alembic/versions/20260320_000000_add_request_log_requested_actual_tiers.py +51 -0
- app/db/alembic/versions/20260321_190000_tighten_dashboard_db_indexes.py +64 -0
- app/db/alembic/versions/20260321_210000_merge_request_log_tiers_and_dashboard_index_heads.py +25 -0
- app/db/alembic/versions/20260325_000000_add_request_log_cost.py +193 -0
- app/db/alembic/versions/20260328_000000_add_rate_limit_attempts.py +48 -0
- app/db/alembic/versions/20260328_120000_add_request_log_ttft.py +37 -0
- app/db/alembic/versions/20260328_130000_add_audit_logs_table.py +45 -0
- app/db/alembic/versions/20260328_140000_add_scheduler_leader_table.py +39 -0
- app/db/alembic/versions/20260330_000000_add_cache_locality_settings.py +69 -0
- app/db/alembic/versions/20260330_010000_merge_scheduler_leader_and_cache_locality_heads.py +25 -0
- app/db/alembic/versions/20260330_020000_add_bridge_ring_members.py +46 -0
- app/db/alembic/versions/20260401_000000_add_api_key_enforced_service_tier.py +52 -0
- app/db/alembic/versions/20260401_000000_add_cache_invalidation.py +41 -0
- app/db/alembic/versions/20260401_020000_merge_cache_invalidation_and_api_key_service_tier_heads.py +22 -0
- app/db/alembic/versions/20260402_000000_switch_dashboard_routing_default_to_capacity_weighted.py +66 -0
- app/db/alembic/versions/20260403_000000_add_credit_api_key_limit_values.py +44 -0
- app/db/alembic/versions/20260406_000000_add_api_key_account_assignments.py +57 -0
- app/db/alembic/versions/20260406_010000_add_api_key_assignment_scope_flag.py +75 -0
- app/db/alembic/versions/20260407_000000_add_bridge_gateway_safe_mode.py +51 -0
- app/db/alembic/versions/20260407_010000_merge_api_key_assignment_and_bridge_gateway_heads.py +25 -0
- app/db/alembic/versions/20260408_000000_switch_import_without_overwrite_default_to_true.py +73 -0
- app/db/alembic/versions/20260408_010000_merge_import_without_overwrite_and_assignment_heads.py +25 -0
- app/db/alembic/versions/20260409_000000_add_http_bridge_sessions.py +130 -0
- app/db/alembic/versions/20260409_000000_switch_sticky_threads_and_prefer_earlier_reset_defaults_to_true.py +89 -0
- app/db/alembic/versions/20260409_010000_add_dashboard_settings_bootstrap_token.py +57 -0
- app/db/alembic/versions/20260409_010000_merge_http_bridge_and_import_without_overwrite_heads.py +25 -0
- app/db/alembic/versions/20260409_020000_fix_http_bridge_last_seen_index.py +46 -0
- app/db/alembic/versions/20260410_000000_backfill_pristine_dashboard_settings_defaults.py +77 -0
- app/db/alembic/versions/20260410_030000_restore_import_without_overwrite_default_true.py +73 -0
- app/db/alembic/versions/20260410_040000_merge_dashboard_defaults_and_import_default_heads.py +25 -0
- app/db/alembic/versions/20260413_000000_add_accounts_blocked_at.py +44 -0
- app/db/alembic/versions/20260415_160000_add_automations_tables.py +189 -0
- app/db/alembic/versions/20260415_160000_add_request_logs_response_lookup_index.py +67 -0
- app/db/alembic/versions/20260417_000000_add_request_log_plan_type.py +44 -0
- app/db/alembic/versions/20260418_190000_add_automation_runs_cycle_key.py +87 -0
- app/db/alembic/versions/20260419_000000_add_automation_run_cycle_metadata.py +56 -0
- app/db/alembic/versions/20260419_020000_add_automation_run_cycles_snapshot_tables.py +342 -0
- app/db/alembic/versions/20260421_000000_add_dashboard_session_ttl_setting.py +51 -0
- app/db/alembic/versions/20260421_120000_merge_request_log_lookup_and_plan_type_heads.py +25 -0
- app/db/alembic/versions/20260421_130000_merge_automation_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260422_103000_normalize_legacy_automation_run_cycle_keys.py +648 -0
- app/db/alembic/versions/20260423_120000_add_api_key_limit_reset_at_index.py +43 -0
- app/db/alembic/versions/20260424_000000_merge_dashboard_session_ttl_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260426_000000_add_dashboard_relative_availability_settings.py +68 -0
- app/db/alembic/versions/20260427_130000_add_warmup_model_and_request_kind.py +75 -0
- app/db/alembic/versions/20260506_000000_add_account_routing_policy.py +37 -0
- app/db/alembic/versions/20260509_000000_add_api_key_traffic_class.py +37 -0
- app/db/alembic/versions/20260509_010000_add_additional_quota_routing_policies.py +37 -0
- app/db/alembic/versions/20260513_000000_add_accounts_alias.py +44 -0
- app/db/alembic/versions/20260513_000000_add_api_key_apply_to_codex_model.py +59 -0
- app/db/alembic/versions/20260514_000000_add_request_logs_api_key_time_index.py +43 -0
- app/db/alembic/versions/20260515_000000_add_prefer_earlier_reset_window.py +47 -0
- app/db/alembic/versions/20260515_000000_soft_delete_request_logs_on_account_delete.py +83 -0
- app/db/alembic/versions/20260515_020000_add_split_sticky_budget_thresholds.py +62 -0
- app/db/alembic/versions/20260516_000000_add_sqlite_hot_path_indexes.py +75 -0
- app/db/alembic/versions/20260517_000000_merge_sqlite_hot_path_and_request_log_delete_heads.py +24 -0
- app/db/alembic/versions/20260518_000000_add_http_bridge_durable_input_prefix.py +44 -0
- app/db/alembic/versions/20260518_010000_merge_http_bridge_and_sqlite_recovery_heads.py +25 -0
- app/db/alembic/versions/20260518_120000_add_dashboard_guest_access.py +57 -0
- app/db/alembic/versions/20260520_000000_merge_api_key_and_http_bridge_heads.py +26 -0
- app/db/alembic/versions/20260520_010000_add_request_logs_api_key_account_index.py +45 -0
- app/db/alembic/versions/20260520_010000_merge_automation_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260520_030000_add_quota_planner.py +194 -0
- app/db/alembic/versions/20260520_040000_merge_quota_planner_and_request_log_index_heads.py +24 -0
- app/db/alembic/versions/20260521_000000_add_account_security_work_authorized.py +46 -0
- app/db/alembic/versions/20260521_000000_merge_dashboard_guest_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_prefer_reset_window_head.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_warmup_and_request_log_index_heads.py +25 -0
- app/db/alembic/versions/20260522_000000_add_limit_warmup_trigger.py +195 -0
- app/db/alembic/versions/20260524_000000_merge_accounts_alias_and_reset_window_heads.py +24 -0
- app/db/alembic/versions/20260525_000000_add_usage_raw_window_latest_index.py +31 -0
- app/db/alembic/versions/20260526_000000_add_request_log_failure_metadata.py +100 -0
- app/db/alembic/versions/20260531_000000_add_account_workspace_identity.py +39 -0
- app/db/alembic/versions/20260531_000000_add_single_account_routing.py +42 -0
- app/db/alembic/versions/20260601_000000_merge_relative_availability_and_usage_raw_heads.py +24 -0
- app/db/alembic/versions/20260601_010000_add_api_key_usage_sections.py +58 -0
- app/db/alembic/versions/20260601_010000_add_routing_policy_persistence.py +67 -0
- app/db/alembic/versions/20260601_010000_merge_security_work_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_split_sticky_and_relative_usage_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_warmup_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_additional_quota_routing_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_quota_planner_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_reset_window_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_routing_policy_and_split_threshold_heads.py +24 -0
- app/db/alembic/versions/20260601_020000_merge_warmup_and_request_log_failure_heads.py +25 -0
- app/db/alembic/versions/20260601_030000_merge_single_account_and_routing_quota_heads.py +29 -0
- app/db/alembic/versions/20260601_040000_merge_security_work_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260601_050000_merge_quota_planner_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260602_050000_add_upstream_proxy_routing.py +164 -0
- app/db/alembic/versions/20260602_060000_merge_account_workspace_and_failure_heads.py +25 -0
- app/db/alembic/versions/20260602_070000_add_request_log_useragent_fields.py +86 -0
- app/db/alembic/versions/20260602_070000_merge_quota_planner_and_account_workspace_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_upstream_proxy_and_quota_planner_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_useragent_and_upstream_proxy_heads.py +24 -0
- app/db/alembic/versions/20260603_000000_add_weekly_pace_working_days.py +52 -0
- app/db/alembic/versions/20260603_000000_free_account_monthly_window.py +60 -0
- app/db/alembic/versions/20260604_000000_add_reauth_required_account_status.py +159 -0
- app/db/alembic/versions/20260604_010000_merge_automations_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_dashboard_guest_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_useragent_and_reauth_heads.py +24 -0
- app/db/alembic/versions/20260607_000000_merge_weekly_monthly_useragent_heads.py +28 -0
- app/db/alembic/versions/20260608_000000_add_hide_upstream_quota_from_api_keys.py +50 -0
- app/db/alembic/versions/20260611_000000_add_request_log_archive_request_id.py +47 -0
- app/db/alembic/versions/20260611_000000_merge_automations_and_weekly_monthly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_000000_merge_dashboard_guest_and_weekly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_010000_add_request_log_client_ip.py +57 -0
- app/db/alembic/versions/20260611_020000_merge_request_log_archive_and_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260611_030000_merge_dashboard_guest_and_request_log_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260613_000000_add_accounts_codex_installation_id.py +57 -0
- app/db/alembic/versions/20260613_010000_merge_installation_id_and_dashboard_weekly_heads.py +26 -0
- app/db/alembic/versions/20260615_000000_merge_automations_and_dashboard_guest_heads.py +26 -0
- app/db/alembic/versions/20260623_000000_add_limit_warmup_exhausted_threshold.py +51 -0
- app/db/alembic/versions/20260626_000000_add_http_downstream_transport_policy.py +46 -0
- app/db/alembic/versions/20260626_010000_add_request_logs_upstream_transport.py +31 -0
- app/db/alembic/versions/20260629_000000_add_dashboard_query_hot_path_indexes.py +53 -0
- app/db/alembic/versions/20260630_000000_add_limit_warmup_staggered_idle.py +47 -0
- app/db/alembic/versions/20260630_000000_merge_request_log_client_ip_and_dashboard_hot_path_heads.py +26 -0
- app/db/alembic/versions/20260630_010000_merge_warmup_and_request_log_dashboard_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_automations_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_warmup_threshold_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_030000_add_automation_run_model_snapshot.py +59 -0
- app/db/alembic/versions/20260630_040000_merge_automation_snapshot_and_warmup_threshold_heads.py +26 -0
- app/db/alembic/versions/20260630_050000_add_automation_run_prompt_snapshot.py +52 -0
- app/db/alembic/versions/20260701_000000_add_weekly_pace_smoothing_minutes.py +51 -0
- app/db/alembic/versions/20260702_000000_add_model_sources.py +176 -0
- app/db/alembic/versions/20260705_000000_add_model_source_audio_transcriptions.py +50 -0
- app/db/alembic/versions/20260705_000000_harden_dashboard_session_ttl.py +62 -0
- app/db/alembic/versions/20260706_000000_add_model_source_audio_per_minute.py +43 -0
- app/db/alembic/versions/20260707_000000_merge_model_sources_and_session_ttl.py +24 -0
- app/db/alembic/versions/20260707_010000_merge_automations_and_model_sources_heads.py +24 -0
- app/db/alembic/versions/20260707_020000_add_automation_job_account_scope.py +125 -0
- app/db/alembic/versions/20260709_000000_add_ttft_phase_observability.py +66 -0
- app/db/alembic/versions/20260711_000000_add_dashboard_prohibit_fast_mode.py +42 -0
- app/db/alembic/versions/20260711_010000_add_account_chatgpt_user_id.py +40 -0
- app/db/alembic/versions/20260711_020000_add_dashboard_account_concurrency_caps.py +71 -0
- app/db/alembic/versions/20260711_030000_add_limit_warmup_idle_threshold.py +51 -0
- app/db/alembic/versions/20260712_010000_add_account_usage_rollups.py +56 -0
- app/db/alembic/versions/20260712_020000_add_api_key_usage_rollups.py +53 -0
- app/db/alembic/versions/20260713_020000_add_model_registry_snapshot.py +42 -0
- app/db/alembic/versions/20260713_040000_add_account_refresh_claims.py +42 -0
- app/db/alembic/versions/20260713_040000_add_replica_guardrails.py +64 -0
- app/db/alembic/versions/20260713_070000_add_reset_credit_redeem_tables.py +58 -0
- app/db/alembic/versions/20260714_000000_add_oauth_flow_states.py +63 -0
- app/db/alembic/versions/20260715_000000_add_request_log_queue_latency.py +44 -0
- app/db/alembic/versions/20260716_000000_add_oauth_device_flow_slots.py +41 -0
- app/db/alembic/versions/20260716_010000_add_dashboard_retention_settings.py +48 -0
- app/db/alembic/versions/20260716_010000_add_reset_credit_display_settings.py +73 -0
- app/db/alembic/versions/20260717_000000_add_http_bridge_retry_circuits.py +47 -0
- app/db/alembic/versions/20260717_000000_merge_retention_and_reset_credit_display_heads.py +26 -0
- app/db/alembic/versions/20260717_000000_optimize_dashboard_hot_path_indexes.py +162 -0
- app/db/alembic/versions/20260717_000001_merge_retry_circuits_and_dashboard_indexes.py +22 -0
- app/db/alembic/versions/20260720_000000_add_request_log_conversation_id.py +57 -0
- app/db/alembic/versions/20260722_000000_backfill_request_log_useragent_families.py +40 -0
- app/db/alembic/versions/20260724_000000_add_request_usage_time_rollups.py +176 -0
- app/db/alembic/versions/20260724_000000_merge_request_log_schema_heads.py +33 -0
- app/db/alembic/versions/20260725_000000_add_http_bridge_pending_tool_calls.py +43 -0
- app/db/alembic/versions/20260726_000000_add_account_plan_downgrade_observations.py +44 -0
- app/db/alembic/versions/20260726_000000_repair_request_usage_rollups_after_merge.py +37 -0
- app/db/alembic/versions/20260727_000000_add_sticky_session_continuity_abandoned_at.py +45 -0
- app/db/alembic/versions/20260728_000000_merge_pending_tool_calls_and_rollup_repair_heads.py +31 -0
- app/db/alembic/versions/20260730_000000_add_api_key_fair_share_threshold.py +44 -0
- app/db/alembic/versions/20260730_000000_add_http_bridge_recovery_attempts.py +60 -0
- app/db/alembic/versions/20260731_000000_add_capability_lineage_markers.py +38 -0
- app/db/alembic/versions/20260803_000000_merge_http_bridge_recovery_and_capability_lineage_heads.py +30 -0
- app/db/alembic/versions/20260804_000000_add_http_bridge_operations.py +63 -0
- app/db/alembic/versions/20260804_000001_add_global_http_bridge_operation_fingerprint.py +53 -0
- app/db/alembic/versions/20260804_230000_add_request_log_connection_request_kind.py +43 -0
- app/db/alembic/versions/20260805_000000_add_http_bridge_operation_spool.py +75 -0
- app/db/alembic/versions/20260805_000001_finalize_http_bridge_operation_spool.py +65 -0
- app/db/alembic/versions/20260806_000000_add_additional_usage_alias_probe_indexes.py +84 -0
- app/db/alembic/versions/20260806_000000_add_anonymous_telemetry.py +49 -0
- app/db/alembic/versions/20260806_010000_add_conversation_presence_rollup.py +72 -0
- app/db/alembic/versions/20260806_020000_add_usage_history_bulk_covering_indexes.py +99 -0
- app/db/alembic/versions/20260806_030000_add_api_key_allowed_reasoning_efforts.py +65 -0
- app/db/alembic/versions/20260806_120000_add_http_bridge_owner_process_epoch.py +57 -0
- app/db/alembic/versions/20260807_000000_merge_http_bridge_operation_spool_and_latest_main.py +24 -0
- app/db/alembic/versions/20260808_000000_tune_usage_history_autovacuum.py +54 -0
- app/db/alembic/versions/20260810_000000_add_http_bridge_recovery_dispatch_count.py +44 -0
- app/db/alembic/versions/20260811_000000_add_hourly_rollup_cancelled_count.py +92 -0
- app/db/alembic/versions/20260812_000000_merge_recovery_dispatch_and_hourly_cancelled_heads.py +25 -0
- app/db/alembic/versions/20260812_120000_add_sticky_abandonment_scope.py +43 -0
- app/db/alembic/versions/20260813_000000_add_file_account_pins.py +40 -0
- app/db/alembic/versions/20260816_000000_add_account_pending_deletion.py +81 -0
- app/db/alembic/versions/20260816_000000_add_model_source_embeddings.py +50 -0
- app/db/alembic/versions/20260821_000000_add_retry_circuit_admission_generation.py +49 -0
- app/db/alembic/versions/20260826_000000_add_http_bridge_event_chunks.py +107 -0
- app/db/alembic/versions/20260828_000000_add_accounts_chatgpt_identity_index.py +55 -0
- app/db/alembic/versions/20260830_000000_add_quota_warmup_claim_expiry.py +83 -0
- app/db/alembic/versions/20260908_000000_add_guest_session_generation.py +37 -0
- app/db/alembic/versions/20260908_000000_add_subscription_overflow.py +140 -0
- app/db/alembic/versions/20260908_000000_replace_upstream_stream_transport_default_sentinel.py +50 -0
- app/db/alembic/versions/20260908_020000_merge_overflow_transport_heads.py +27 -0
- app/db/alembic/versions/20260909_000000_add_dashboard_roles.py +72 -0
- app/db/alembic/versions/20260909_010000_add_dashboard_users.py +183 -0
- app/db/alembic/versions/20260909_020000_reproject_compat_admin_credentials.py +105 -0
- app/db/alembic/versions/20260909_030000_add_audit_actor_columns.py +103 -0
- app/db/alembic/versions/20260909_030000_dashboard_resilience_toggle_settings.py +63 -0
- app/db/alembic/versions/20260909_040000_add_dashboard_user_invites.py +54 -0
- app/db/alembic/versions/20260909_040000_dashboard_timeout_settings.py +68 -0
- app/db/alembic/versions/20260909_050000_dashboard_routing_overload_settings.py +65 -0
- app/db/alembic/versions/20260909_060000_add_report_rollup.py +74 -0
- app/db/alembic/versions/20260909_070000_automation_run_claim_budget.py +50 -0
- app/db/alembic/versions/20260909_080000_dashboard_stream_bridge_budgets.py +62 -0
- app/db/alembic/versions/20260909_090000_dashboard_background_job_toggles.py +63 -0
- app/db/alembic/versions/20260909_100000_dashboard_codex_prewarm.py +49 -0
- app/db/alembic/versions/20260909_110000_model_context_window_overrides.py +45 -0
- app/db/alembic/versions/20260909_120000_dashboard_conversation_archive.py +48 -0
- app/db/alembic/versions/20260909_130000_add_request_logs_live_facet_indexes.py +96 -0
- app/db/alembic/versions/20260910_000000_add_totp_required_for_admin_role.py +44 -0
- app/db/alembic/versions/20260910_000000_request_logs_missing_cost_index.py +52 -0
- app/db/alembic/versions/20260910_010000_add_dashboard_auth_providers.py +134 -0
- app/db/alembic/versions/20260910_010000_dashboard_spool_retention.py +52 -0
- app/db/alembic/versions/20260910_020000_add_dashboard_role_mappings.py +74 -0
- app/db/alembic/versions/20260910_143000_request_log_affinity.py +28 -0
- app/db/alembic/versions/20260910_180000_merge_affinity_guest_heads.py +17 -0
- app/db/alembic/versions/20260910_200000_merge_affinity_identity_heads.py +17 -0
- app/db/alembic/versions/20260910_220000_merge_affinity_invite_heads.py +17 -0
- app/db/alembic/versions/20260911_000000_model_source_pins_kind_expires_index.py +43 -0
- app/db/alembic/versions/20260911_010000_merge_pin_index_and_affinity_heads.py +17 -0
- app/db/alembic/versions/20260911_020000_add_http_bridge_terminal_append_phase.py +54 -0
- app/db/alembic/versions/20260911_030000_add_local_login_policy.py +48 -0
- app/db/alembic/versions/20260911_040000_add_thread_cache_identity_mode.py +48 -0
- app/db/alembic/versions/20260911_060000_add_bridge_session_continuity_abandonment.py +40 -0
- app/db/alembic/versions/20260912_000000_merge_thread_cache_and_bridge_retirement_heads.py +22 -0
- app/db/alembic/versions/20260912_010000_drop_legacy_dashboard_credentials.py +140 -0
- app/db/alembic/versions/20260913_000000_add_oidc_provider_flow.py +134 -0
- app/db/alembic/versions/20260914_000000_add_scim_tokens.py +78 -0
- app/db/alembic/versions/20260914_000000_drop_subscription_overflow_schema.py +132 -0
- app/db/alembic/versions/20260918_000000_merge_scim_and_overflow_heads.py +17 -0
- app/db/alembic/versions/__init__.py +1 -0
- app/db/backup.py +64 -0
- app/db/migrate.py +1249 -0
- app/db/migration_lock.py +160 -0
- app/db/migration_url.py +18 -0
- app/db/models.py +3100 -0
- app/db/recover.py +244 -0
- app/db/session.py +1295 -0
- app/db/snapshot.py +46 -0
- app/db/sqlite_lock_retry.py +190 -0
- app/db/sqlite_utils.py +549 -0
- app/dependencies.py +462 -0
- app/main.py +1240 -0
- app/modules/__init__.py +0 -0
- app/modules/accounts/__init__.py +0 -0
- app/modules/accounts/api.py +426 -0
- app/modules/accounts/auth_manager.py +1439 -0
- app/modules/accounts/background_repository.py +160 -0
- app/modules/accounts/deletion.py +540 -0
- app/modules/accounts/mappers.py +654 -0
- app/modules/accounts/refresh_claims.py +352 -0
- app/modules/accounts/repository.py +1679 -0
- app/modules/accounts/schemas.py +308 -0
- app/modules/accounts/service.py +809 -0
- app/modules/accounts/usage_rollup.py +486 -0
- app/modules/accounts/usage_rollup_scheduler.py +75 -0
- app/modules/accounts/usage_time_rollup.py +1037 -0
- app/modules/accounts/usage_time_rollup_read.py +522 -0
- app/modules/api_keys/__init__.py +1 -0
- app/modules/api_keys/api.py +344 -0
- app/modules/api_keys/last_used_coalescer.py +216 -0
- app/modules/api_keys/limit_windows.py +41 -0
- app/modules/api_keys/repository.py +1200 -0
- app/modules/api_keys/reset_scheduler.py +85 -0
- app/modules/api_keys/schemas.py +135 -0
- app/modules/api_keys/service.py +2123 -0
- app/modules/audit/__init__.py +0 -0
- app/modules/audit/api.py +88 -0
- app/modules/audit/repository.py +74 -0
- app/modules/audit/schemas.py +33 -0
- app/modules/audit/service.py +71 -0
- app/modules/auth_providers/__init__.py +0 -0
- app/modules/auth_providers/api.py +127 -0
- app/modules/auth_providers/config.py +343 -0
- app/modules/auth_providers/repository.py +101 -0
- app/modules/auth_providers/schemas.py +88 -0
- app/modules/auth_providers/seed.py +105 -0
- app/modules/auth_providers/service.py +289 -0
- app/modules/automations/__init__.py +1 -0
- app/modules/automations/api.py +369 -0
- app/modules/automations/repository.py +1896 -0
- app/modules/automations/scheduler.py +107 -0
- app/modules/automations/schemas.py +158 -0
- app/modules/automations/service.py +2548 -0
- app/modules/cache_isolation_probe/__init__.py +0 -0
- app/modules/cache_isolation_probe/api.py +188 -0
- app/modules/cache_isolation_probe/prefix.py +144 -0
- app/modules/cache_isolation_probe/schemas.py +94 -0
- app/modules/cache_isolation_probe/sender.py +229 -0
- app/modules/cache_isolation_probe/service.py +525 -0
- app/modules/conversation_archive/__init__.py +1 -0
- app/modules/conversation_archive/api.py +101 -0
- app/modules/conversation_archive/schemas.py +28 -0
- app/modules/conversation_archive/service.py +165 -0
- app/modules/dashboard/__init__.py +1 -0
- app/modules/dashboard/api.py +106 -0
- app/modules/dashboard/builders.py +107 -0
- app/modules/dashboard/repository.py +304 -0
- app/modules/dashboard/schemas.py +148 -0
- app/modules/dashboard/service.py +534 -0
- app/modules/dashboard/timeframes.py +19 -0
- app/modules/dashboard/weekly_pace.py +617 -0
- app/modules/dashboard_auth/__init__.py +1 -0
- app/modules/dashboard_auth/api.py +1344 -0
- app/modules/dashboard_auth/oidc_api.py +753 -0
- app/modules/dashboard_auth/oidc_flows.py +330 -0
- app/modules/dashboard_auth/repository.py +452 -0
- app/modules/dashboard_auth/schemas.py +232 -0
- app/modules/dashboard_auth/service.py +1384 -0
- app/modules/dashboard_roles/__init__.py +0 -0
- app/modules/dashboard_roles/api.py +64 -0
- app/modules/dashboard_roles/repository.py +54 -0
- app/modules/dashboard_roles/schemas.py +29 -0
- app/modules/dashboard_roles/seed.py +80 -0
- app/modules/dashboard_roles/service.py +142 -0
- app/modules/dashboard_users/__init__.py +0 -0
- app/modules/dashboard_users/api.py +278 -0
- app/modules/dashboard_users/break_glass.py +169 -0
- app/modules/dashboard_users/credentials.py +18 -0
- app/modules/dashboard_users/identity_resolver.py +665 -0
- app/modules/dashboard_users/repository.py +725 -0
- app/modules/dashboard_users/schemas.py +117 -0
- app/modules/dashboard_users/service.py +1054 -0
- app/modules/firewall/__init__.py +1 -0
- app/modules/firewall/api.py +78 -0
- app/modules/firewall/repository.py +51 -0
- app/modules/firewall/schemas.py +28 -0
- app/modules/firewall/service.py +93 -0
- app/modules/fleet/__init__.py +1 -0
- app/modules/fleet/api.py +177 -0
- app/modules/fleet/mappers.py +56 -0
- app/modules/fleet/observability.py +372 -0
- app/modules/fleet/schemas.py +119 -0
- app/modules/health/__init__.py +0 -0
- app/modules/health/api.py +246 -0
- app/modules/health/schemas.py +27 -0
- app/modules/limit_warmup/__init__.py +1 -0
- app/modules/limit_warmup/repository.py +159 -0
- app/modules/limit_warmup/service.py +1001 -0
- app/modules/model_sources/__init__.py +1 -0
- app/modules/model_sources/api.py +99 -0
- app/modules/model_sources/catalog.py +307 -0
- app/modules/model_sources/forwarding.py +1435 -0
- app/modules/model_sources/projection.py +55 -0
- app/modules/model_sources/repository.py +201 -0
- app/modules/model_sources/schemas.py +79 -0
- app/modules/model_sources/selection.py +166 -0
- app/modules/model_sources/service.py +236 -0
- app/modules/oauth/__init__.py +0 -0
- app/modules/oauth/api.py +101 -0
- app/modules/oauth/repository.py +238 -0
- app/modules/oauth/schemas.py +49 -0
- app/modules/oauth/service.py +1196 -0
- app/modules/oauth/templates/oauth_success.html +122 -0
- app/modules/proxy/__init__.py +0 -0
- app/modules/proxy/_load_balancer/__init__.py +1 -0
- app/modules/proxy/_load_balancer/error_rate.py +116 -0
- app/modules/proxy/_load_balancer/latency_cohort.py +154 -0
- app/modules/proxy/_load_balancer/model_eligibility.py +250 -0
- app/modules/proxy/_load_balancer/opportunistic_admission.py +350 -0
- app/modules/proxy/_load_balancer/overload_backoff.py +394 -0
- app/modules/proxy/_load_balancer/sticky_selection.py +2417 -0
- app/modules/proxy/_load_balancer/throughput_cohort.py +233 -0
- app/modules/proxy/_load_balancer/ttft_cohort.py +214 -0
- app/modules/proxy/_load_balancer/tunables.py +97 -0
- app/modules/proxy/_load_balancer/types.py +96 -0
- app/modules/proxy/_load_balancer/unbound_selection.py +534 -0
- app/modules/proxy/_service/__init__.py +6 -0
- app/modules/proxy/_service/api_key_usage.py +745 -0
- app/modules/proxy/_service/clock_budget.py +38 -0
- app/modules/proxy/_service/codex_control.py +611 -0
- app/modules/proxy/_service/compact.py +2211 -0
- app/modules/proxy/_service/file_ops.py +749 -0
- app/modules/proxy/_service/http_bridge/__init__.py +3 -0
- app/modules/proxy/_service/http_bridge/accepted_replay.py +331 -0
- app/modules/proxy/_service/http_bridge/account_sessions.py +57 -0
- app/modules/proxy/_service/http_bridge/activity.py +178 -0
- app/modules/proxy/_service/http_bridge/helpers.py +3959 -0
- app/modules/proxy/_service/http_bridge/mixin.py +2435 -0
- app/modules/proxy/_service/http_bridge/owner_forwarding.py +609 -0
- app/modules/proxy/_service/http_bridge/protocol.py +167 -0
- app/modules/proxy/_service/http_bridge/proxy_failover.py +58 -0
- app/modules/proxy/_service/http_bridge/quarantine.py +443 -0
- app/modules/proxy/_service/http_bridge/request_submit.py +4582 -0
- app/modules/proxy/_service/http_bridge/retry_circuit.py +2442 -0
- app/modules/proxy/_service/http_bridge/service_stubs.py +492 -0
- app/modules/proxy/_service/http_bridge/session_registry.py +778 -0
- app/modules/proxy/_service/http_bridge/streaming.py +5328 -0
- app/modules/proxy/_service/http_bridge/upstream_events.py +4538 -0
- app/modules/proxy/_service/observability.py +410 -0
- app/modules/proxy/_service/rate_limit.py +361 -0
- app/modules/proxy/_service/realtime_live.py +655 -0
- app/modules/proxy/_service/refresh.py +154 -0
- app/modules/proxy/_service/request_log.py +585 -0
- app/modules/proxy/_service/response_create.py +930 -0
- app/modules/proxy/_service/streaming/__init__.py +3 -0
- app/modules/proxy/_service/streaming/helpers.py +1317 -0
- app/modules/proxy/_service/streaming/mixin.py +1100 -0
- app/modules/proxy/_service/streaming/protocol.py +32 -0
- app/modules/proxy/_service/streaming/retry.py +3762 -0
- app/modules/proxy/_service/support.py +2122 -0
- app/modules/proxy/_service/transcribe.py +434 -0
- app/modules/proxy/_service/warmup.py +483 -0
- app/modules/proxy/_service/websocket/__init__.py +3 -0
- app/modules/proxy/_service/websocket/helpers.py +2360 -0
- app/modules/proxy/_service/websocket/mixin.py +7285 -0
- app/modules/proxy/_service/websocket/protocol.py +74 -0
- app/modules/proxy/account_cache.py +229 -0
- app/modules/proxy/account_eligibility.py +87 -0
- app/modules/proxy/additional_model_limits.py +40 -0
- app/modules/proxy/affinity.py +830 -0
- app/modules/proxy/affinity_observation.py +101 -0
- app/modules/proxy/api.py +10254 -0
- app/modules/proxy/api_key_usage.py +113 -0
- app/modules/proxy/cap_partitioning.py +280 -0
- app/modules/proxy/capability_lineage.py +65 -0
- app/modules/proxy/capability_lineage_repository.py +82 -0
- app/modules/proxy/capability_routing.py +253 -0
- app/modules/proxy/continuity.py +112 -0
- app/modules/proxy/downstream_delivery.py +277 -0
- app/modules/proxy/durable_bridge_coordinator.py +1217 -0
- app/modules/proxy/durable_bridge_repository.py +4516 -0
- app/modules/proxy/durable_bridge_runtime.py +9 -0
- app/modules/proxy/durable_bridge_transcript_codec.py +122 -0
- app/modules/proxy/fair_share.py +131 -0
- app/modules/proxy/file_pin_repository.py +210 -0
- app/modules/proxy/helpers.py +439 -0
- app/modules/proxy/http_bridge_event_batcher.py +613 -0
- app/modules/proxy/http_bridge_forwarding.py +797 -0
- app/modules/proxy/http_continuation.py +76 -0
- app/modules/proxy/images_observability.py +62 -0
- app/modules/proxy/images_service.py +1054 -0
- app/modules/proxy/load_balancer.py +3016 -0
- app/modules/proxy/rate_limit_cache.py +46 -0
- app/modules/proxy/replay_safety.py +1063 -0
- app/modules/proxy/repo_bundle.py +31 -0
- app/modules/proxy/request_policy.py +1054 -0
- app/modules/proxy/ring_membership.py +251 -0
- app/modules/proxy/schemas.py +345 -0
- app/modules/proxy/selection_errors.py +41 -0
- app/modules/proxy/service.py +2600 -0
- app/modules/proxy/source_admission.py +125 -0
- app/modules/proxy/source_dispatch.py +999 -0
- app/modules/proxy/sticky_repository.py +831 -0
- app/modules/proxy/thread_anchors.py +491 -0
- app/modules/proxy/tool_call_dedupe.py +802 -0
- app/modules/proxy/types.py +54 -0
- app/modules/proxy/work_admission.py +121 -0
- app/modules/quota_planner/__init__.py +1 -0
- app/modules/quota_planner/api.py +276 -0
- app/modules/quota_planner/logic.py +743 -0
- app/modules/quota_planner/repository.py +722 -0
- app/modules/quota_planner/scheduler.py +212 -0
- app/modules/quota_planner/schemas.py +102 -0
- app/modules/quota_planner/warmup.py +693 -0
- app/modules/rate_limit_reset_credits/__init__.py +0 -0
- app/modules/rate_limit_reset_credits/api.py +601 -0
- app/modules/rate_limit_reset_credits/redeem_coordination.py +274 -0
- app/modules/rate_limit_reset_credits/store.py +114 -0
- app/modules/reports/__init__.py +1 -0
- app/modules/reports/api.py +134 -0
- app/modules/reports/cache.py +74 -0
- app/modules/reports/filters.py +27 -0
- app/modules/reports/repository.py +525 -0
- app/modules/reports/rollup.py +150 -0
- app/modules/reports/rollup_read.py +148 -0
- app/modules/reports/schemas.py +123 -0
- app/modules/reports/service.py +287 -0
- app/modules/reports/thread_identity.py +256 -0
- app/modules/request_logs/__init__.py +0 -0
- app/modules/request_logs/api.py +224 -0
- app/modules/request_logs/cost_backfill.py +154 -0
- app/modules/request_logs/mappers.py +97 -0
- app/modules/request_logs/repository.py +1765 -0
- app/modules/request_logs/schemas.py +145 -0
- app/modules/request_logs/service.py +348 -0
- app/modules/role_mappings/__init__.py +0 -0
- app/modules/role_mappings/api.py +206 -0
- app/modules/role_mappings/matching.py +55 -0
- app/modules/role_mappings/repository.py +106 -0
- app/modules/role_mappings/schemas.py +67 -0
- app/modules/role_mappings/service.py +334 -0
- app/modules/runtime/__init__.py +1 -0
- app/modules/runtime/api.py +18 -0
- app/modules/runtime/schemas.py +14 -0
- app/modules/runtime/service.py +218 -0
- app/modules/scim/__init__.py +1 -0
- app/modules/scim/api.py +194 -0
- app/modules/scim/dependencies.py +112 -0
- app/modules/scim/errors.py +77 -0
- app/modules/scim/management_api.py +156 -0
- app/modules/scim/repository.py +212 -0
- app/modules/scim/schemas.py +145 -0
- app/modules/scim/service.py +786 -0
- app/modules/scim/tokens.py +43 -0
- app/modules/settings/__init__.py +1 -0
- app/modules/settings/api.py +1677 -0
- app/modules/settings/repository.py +606 -0
- app/modules/settings/schemas.py +496 -0
- app/modules/settings/service.py +769 -0
- app/modules/shared/__init__.py +0 -0
- app/modules/shared/schemas.py +22 -0
- app/modules/sticky_sessions/__init__.py +1 -0
- app/modules/sticky_sessions/api.py +118 -0
- app/modules/sticky_sessions/cleanup_scheduler.py +487 -0
- app/modules/sticky_sessions/schemas.py +78 -0
- app/modules/sticky_sessions/service.py +196 -0
- app/modules/telemetry/__init__.py +5 -0
- app/modules/telemetry/api.py +115 -0
- app/modules/telemetry/clients.py +62 -0
- app/modules/telemetry/consent.py +115 -0
- app/modules/telemetry/scheduler.py +85 -0
- app/modules/telemetry/schemas.py +163 -0
- app/modules/telemetry/sender.py +221 -0
- app/modules/telemetry/snapshot.py +490 -0
- app/modules/usage/__init__.py +0 -0
- app/modules/usage/additional_quota_keys.py +379 -0
- app/modules/usage/api.py +45 -0
- app/modules/usage/background_repository.py +147 -0
- app/modules/usage/builders.py +559 -0
- app/modules/usage/depletion_service.py +277 -0
- app/modules/usage/live_ingest.py +356 -0
- app/modules/usage/mappers.py +29 -0
- app/modules/usage/plan_downgrade_observations.py +488 -0
- app/modules/usage/repository.py +1890 -0
- app/modules/usage/schemas.py +68 -0
- app/modules/usage/service.py +102 -0
- app/modules/usage/updater.py +1488 -0
- app/static/assets/Trans-Cj-J9mVA.js +1 -0
- app/static/assets/access-page-DHzZiXZi.js +1 -0
- app/static/assets/account-cost-donut-DGhfA4U8.js +1 -0
- app/static/assets/account-identifiers-CxbdLshZ.js +1 -0
- app/static/assets/account-status-DIsU5sUS.js +1 -0
- app/static/assets/account-trend-chart-BptlfwwQ.js +1 -0
- app/static/assets/accounts-page-Bbai8ift.js +3 -0
- app/static/assets/api-Dwa7XxPe.js +1 -0
- app/static/assets/api-client-dugX1Pti.js +55 -0
- app/static/assets/api-key-create-dialog-DnRm0oYN.js +1 -0
- app/static/assets/api-key-created-dialog-B6jkDG9f.js +1 -0
- app/static/assets/api-key-edit-dialog-Ck3Bnc3V.js +1 -0
- app/static/assets/api-trend-chart-BchCw7Hj.js +1 -0
- app/static/assets/apis-page-BvXz9c6i.js +2 -0
- app/static/assets/arrow-up-Dqh_KzJo.js +1 -0
- app/static/assets/arrow-up-down-LBRgVDGY.js +1 -0
- app/static/assets/automations-page-DQgNAJOR.js +3 -0
- app/static/assets/badge-DqMb9es0.js +1 -0
- app/static/assets/blur-email-HEvKagG2.js +1 -0
- app/static/assets/button-BjVmmiGW.js +1 -0
- app/static/assets/checkbox-apsl9vzk.js +1 -0
- app/static/assets/chevron-left-1d_pHVa5.js +1 -0
- app/static/assets/circle-alert-BS0xd_o1.js +1 -0
- app/static/assets/clsx-DB0hHKMi.js +1 -0
- app/static/assets/colors-BBy8GEtr.js +1 -0
- app/static/assets/copy-button-D7nAvOKR.js +1 -0
- app/static/assets/cost-per-day-chart-YdqlkZVM.js +1 -0
- app/static/assets/createLucideIcon-DBLzLNh2.js +1 -0
- app/static/assets/dashboard-page-CSdhBy0f.js +2 -0
- app/static/assets/distribution-metric-format-yZTy1ZEu.js +1 -0
- app/static/assets/donut-chart-Bxc69cra.js +1 -0
- app/static/assets/download-kQteIcsB.js +1 -0
- app/static/assets/ellipsis-B_5OgIre.js +1 -0
- app/static/assets/empty-state-JcA7tc8m.js +1 -0
- app/static/assets/es2015-bMH8YKZT.js +41 -0
- app/static/assets/es6-B264ni6W.js +69 -0
- app/static/assets/external-link-UVAOgMQ0.js +1 -0
- app/static/assets/i18n-BWqZ9Ua2.js +1 -0
- app/static/assets/inbox-B_RM2s1J.js +1 -0
- app/static/assets/index-DIpd264Z.js +23 -0
- app/static/assets/index-DMeLKRJx.css +2 -0
- app/static/assets/inherit-badge-QhTYVaU6.js +1 -0
- app/static/assets/invite-dialog-0CYh0GiQ.js +1 -0
- app/static/assets/iso-Bpe5Ihbb.js +1 -0
- app/static/assets/label-CGaLmLNf.js +1 -0
- app/static/assets/lazy-recharts-Pv8p9ArM.js +2 -0
- app/static/assets/loading-overlay-CzKQfHLI.js +1 -0
- app/static/assets/mini-quota-bar-cGT_t8Ae.js +1 -0
- app/static/assets/model-distribution-donut-BKjAfT48.js +1 -0
- app/static/assets/model-source-multi-select-D2d06Vc7.js +1 -0
- app/static/assets/multi-select-filter-KIA5qU6Z.js +1 -0
- app/static/assets/oauth-dialog-XeaGGDjS.js +1 -0
- app/static/assets/pagination-controls-D1Ypd0RU.js +1 -0
- app/static/assets/payload-CIYnGZz9.js +1 -0
- app/static/assets/queue-wait-chart-VoV7Yg9L.js +1 -0
- app/static/assets/report-chart-card-BXDliwcc.js +1 -0
- app/static/assets/reports-page-CGCJeMHG.js +3 -0
- app/static/assets/reset-credit-confirm-dialog-QOifTnVB.js +1 -0
- app/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- app/static/assets/search-HfhqwERt.js +1 -0
- app/static/assets/select-Cl33bz-g.js +1 -0
- app/static/assets/settings-page-mZj01HYl.js +2 -0
- app/static/assets/shim-CZwyM4Q7.js +1 -0
- app/static/assets/skeleton-C_EUc4Rp.js +1 -0
- app/static/assets/sparkline-chart-DdNNsddM.js +1 -0
- app/static/assets/status-badge-CHHA5X6c.js +1 -0
- app/static/assets/switch-CzxgokcM.js +1 -0
- app/static/assets/table-DXaNSQ0G.js +1 -0
- app/static/assets/time-to-first-token-chart--68L0u_v.js +1 -0
- app/static/assets/tokens-per-day-chart-Cs82BFuE.js +1 -0
- app/static/assets/tokens-per-second-chart-AD2dfOn6.js +1 -0
- app/static/assets/totp-settings-CRHtxQ7j.js +1 -0
- app/static/assets/trash-D3KyWr1h.js +1 -0
- app/static/assets/use-account-quota-display-BPmrTBTC.js +1 -0
- app/static/assets/use-accounts-PWlVGw3z.js +1 -0
- app/static/assets/use-auth-CuDlW3iC.js +1 -0
- app/static/assets/use-chart-colors-DIu6tClI.js +1 -0
- app/static/assets/use-model-sources-Bul24JBu.js +1 -0
- app/static/assets/use-models-DUCPUuZg.js +1 -0
- app/static/assets/use-reduced-motion-hAm5r6_d.js +1 -0
- app/static/assets/useTranslation--56nHhlH.js +1 -0
- app/static/assets/useragent-distribution-donut-Dup6sf_n.js +1 -0
- app/static/assets/users-uIC3TIlE.js +1 -0
- app/static/assets/vendor-query-_bHZuxEx.js +1 -0
- app/static/assets/vendor-react-BVL0YZPW.js +9 -0
- app/static/favicon.svg +10 -0
- app/static/fonts/GeistSans-Variable.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin.woff2 +0 -0
- app/static/index.html +29 -0
- codexlb_up-1.25.0b10.dist-info/METADATA +124 -0
- codexlb_up-1.25.0b10.dist-info/RECORD +827 -0
- codexlb_up-1.25.0b10.dist-info/WHEEL +4 -0
- codexlb_up-1.25.0b10.dist-info/entry_points.txt +3 -0
- codexlb_up-1.25.0b10.dist-info/licenses/LICENSE +21 -0
- config/additional_quota_registry.json +11 -0
app/db/models.py
ADDED
|
@@ -0,0 +1,3100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import (
|
|
8
|
+
BigInteger,
|
|
9
|
+
Boolean,
|
|
10
|
+
CheckConstraint,
|
|
11
|
+
DateTime,
|
|
12
|
+
Float,
|
|
13
|
+
ForeignKey,
|
|
14
|
+
Index,
|
|
15
|
+
Integer,
|
|
16
|
+
LargeBinary,
|
|
17
|
+
String,
|
|
18
|
+
Text,
|
|
19
|
+
UniqueConstraint,
|
|
20
|
+
false,
|
|
21
|
+
func,
|
|
22
|
+
literal_column,
|
|
23
|
+
text,
|
|
24
|
+
true,
|
|
25
|
+
)
|
|
26
|
+
from sqlalchemy import Enum as SqlEnum
|
|
27
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
28
|
+
|
|
29
|
+
from app.core.auth.dashboard_session_ttl import DEFAULT_DASHBOARD_SESSION_TTL_SECONDS
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Base(DeclarativeBase):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _enum_values(enum_cls: type[Enum]) -> list[str]:
|
|
37
|
+
return [str(member.value) for member in enum_cls]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def new_codex_installation_id() -> str:
|
|
41
|
+
return str(uuid.uuid4())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AccountStatus(str, Enum):
|
|
45
|
+
ACTIVE = "active"
|
|
46
|
+
RATE_LIMITED = "rate_limited"
|
|
47
|
+
QUOTA_EXCEEDED = "quota_exceeded"
|
|
48
|
+
PAUSED = "paused"
|
|
49
|
+
REAUTH_REQUIRED = "reauth_required"
|
|
50
|
+
DEACTIVATED = "deactivated"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AccountRoutingPolicy(str, Enum):
|
|
54
|
+
NORMAL = "normal"
|
|
55
|
+
BURN_FIRST = "burn_first"
|
|
56
|
+
PRESERVE = "preserve"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class StickySessionKind(str, Enum):
|
|
60
|
+
CODEX_SESSION = "codex_session"
|
|
61
|
+
STICKY_THREAD = "sticky_thread"
|
|
62
|
+
PROMPT_CACHE = "prompt_cache"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class RequestKind(str, Enum):
|
|
66
|
+
NORMAL = "normal"
|
|
67
|
+
WARMUP = "warmup"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class FileAccountPin(Base):
|
|
71
|
+
__tablename__ = "file_account_pins"
|
|
72
|
+
|
|
73
|
+
file_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
74
|
+
account_id: Mapped[str] = mapped_column(String, nullable=False)
|
|
75
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
76
|
+
|
|
77
|
+
__table_args__ = (Index("ix_file_account_pins_expires_at", "expires_at"),)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Account(Base):
|
|
81
|
+
__tablename__ = "accounts"
|
|
82
|
+
|
|
83
|
+
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
84
|
+
chatgpt_account_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
85
|
+
# Stable per-seat OpenAI principal identity (chatgpt_user_id / auth sub).
|
|
86
|
+
# Distinct from chatgpt_account_id, which is the shared Team/Business
|
|
87
|
+
# WORKSPACE identity. Two seats in one workspace share chatgpt_account_id
|
|
88
|
+
# but have different chatgpt_user_id. Used to target and verify reauth so
|
|
89
|
+
# repairing one seat cannot overwrite another seat sharing the workspace.
|
|
90
|
+
chatgpt_user_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
91
|
+
codex_installation_id: Mapped[str] = mapped_column(
|
|
92
|
+
String(36),
|
|
93
|
+
default=new_codex_installation_id,
|
|
94
|
+
nullable=False,
|
|
95
|
+
)
|
|
96
|
+
email: Mapped[str] = mapped_column(String, nullable=False)
|
|
97
|
+
alias: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
98
|
+
workspace_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
99
|
+
workspace_label: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
100
|
+
seat_type: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
101
|
+
plan_type: Mapped[str] = mapped_column(String, nullable=False)
|
|
102
|
+
routing_policy: Mapped[str] = mapped_column(
|
|
103
|
+
String,
|
|
104
|
+
default="normal",
|
|
105
|
+
server_default=text("'normal'"),
|
|
106
|
+
nullable=False,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
access_token_encrypted: Mapped[bytes] = mapped_column(LargeBinary, nullable=False)
|
|
110
|
+
refresh_token_encrypted: Mapped[bytes] = mapped_column(LargeBinary, nullable=False)
|
|
111
|
+
id_token_encrypted: Mapped[bytes] = mapped_column(LargeBinary, nullable=False)
|
|
112
|
+
|
|
113
|
+
last_refresh: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
114
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
115
|
+
|
|
116
|
+
status: Mapped[AccountStatus] = mapped_column(
|
|
117
|
+
SqlEnum(
|
|
118
|
+
AccountStatus,
|
|
119
|
+
name="account_status",
|
|
120
|
+
validate_strings=True,
|
|
121
|
+
values_callable=_enum_values,
|
|
122
|
+
),
|
|
123
|
+
default=AccountStatus.ACTIVE,
|
|
124
|
+
nullable=False,
|
|
125
|
+
)
|
|
126
|
+
deactivation_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
127
|
+
reset_at: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
128
|
+
blocked_at: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
129
|
+
limit_warmup_enabled: Mapped[bool] = mapped_column(
|
|
130
|
+
Boolean,
|
|
131
|
+
default=False,
|
|
132
|
+
server_default=false(),
|
|
133
|
+
nullable=False,
|
|
134
|
+
)
|
|
135
|
+
security_work_authorized: Mapped[bool] = mapped_column(
|
|
136
|
+
Boolean,
|
|
137
|
+
default=False,
|
|
138
|
+
server_default=false(),
|
|
139
|
+
nullable=False,
|
|
140
|
+
)
|
|
141
|
+
# Pending-deletion marker: set by the fast DELETE path, consumed by the
|
|
142
|
+
# background deletion worker, cleared only by a credential replacement
|
|
143
|
+
# (re-import/reauth) that supersedes the deletion. Non-NULL rows are
|
|
144
|
+
# hidden from account listings and are already unroutable (the fast path
|
|
145
|
+
# also sets status=DEACTIVATED).
|
|
146
|
+
delete_requested_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
147
|
+
# Frozen at the first delete request (repeat requests do not escalate):
|
|
148
|
+
# True selects the history-deleting variant in the background worker.
|
|
149
|
+
delete_history_requested: Mapped[bool] = mapped_column(
|
|
150
|
+
Boolean,
|
|
151
|
+
default=False,
|
|
152
|
+
server_default=false(),
|
|
153
|
+
nullable=False,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
api_key_assignments: Mapped[list["ApiKeyAccountAssignment"]] = relationship(
|
|
157
|
+
"ApiKeyAccountAssignment",
|
|
158
|
+
back_populates="account",
|
|
159
|
+
cascade="all, delete-orphan",
|
|
160
|
+
)
|
|
161
|
+
request_logs: Mapped[list["RequestLog"]] = relationship(
|
|
162
|
+
"RequestLog",
|
|
163
|
+
back_populates="account",
|
|
164
|
+
)
|
|
165
|
+
limit_warmups: Mapped[list["AccountLimitWarmup"]] = relationship(
|
|
166
|
+
"AccountLimitWarmup",
|
|
167
|
+
back_populates="account",
|
|
168
|
+
cascade="all, delete-orphan",
|
|
169
|
+
)
|
|
170
|
+
proxy_binding: Mapped["AccountProxyBinding | None"] = relationship(
|
|
171
|
+
"AccountProxyBinding",
|
|
172
|
+
back_populates="account",
|
|
173
|
+
cascade="all, delete-orphan",
|
|
174
|
+
uselist=False,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class UsageHistory(Base):
|
|
179
|
+
__tablename__ = "usage_history"
|
|
180
|
+
|
|
181
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
182
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
|
183
|
+
recorded_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
184
|
+
window: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
185
|
+
used_percent: Mapped[float] = mapped_column(Float, nullable=False)
|
|
186
|
+
input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
187
|
+
output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
188
|
+
reset_at: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
189
|
+
window_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
190
|
+
credits_has: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
191
|
+
credits_unlimited: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
192
|
+
credits_balance: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class AccountUsageRollup(Base):
|
|
196
|
+
"""Folded lifetime request-usage sums per account.
|
|
197
|
+
|
|
198
|
+
Request-log rows older than the fold watermark (the single
|
|
199
|
+
``account_usage_rollup_state`` row) are summed here; account usage
|
|
200
|
+
summaries add a live aggregate over only the newer request-log tail.
|
|
201
|
+
Sums are stored unclamped; the ``cached_input_tokens <= input_tokens``
|
|
202
|
+
clamp applies after merging.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
__tablename__ = "account_usage_rollups"
|
|
206
|
+
|
|
207
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), primary_key=True)
|
|
208
|
+
request_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
209
|
+
input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
210
|
+
output_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
211
|
+
cached_input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
212
|
+
total_cost_usd: Mapped[float] = mapped_column(Float, default=0.0, server_default=text("0"), nullable=False)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class ApiKeyUsageRollup(Base):
|
|
216
|
+
"""Folded lifetime request-usage sums per API key.
|
|
217
|
+
|
|
218
|
+
Governed by the same fold watermark as ``AccountUsageRollup`` (the single
|
|
219
|
+
``account_usage_rollup_state`` row); sums use the API-key summary
|
|
220
|
+
semantics (no dedupe, soft-deleted rows included, warmup kinds excluded).
|
|
221
|
+
Stored unclamped; the ``cached <= input`` clamp applies after merging
|
|
222
|
+
with the live tail.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
__tablename__ = "api_key_usage_rollups"
|
|
226
|
+
|
|
227
|
+
api_key_id: Mapped[str] = mapped_column(String, ForeignKey("api_keys.id", ondelete="CASCADE"), primary_key=True)
|
|
228
|
+
request_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
229
|
+
input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
230
|
+
output_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
231
|
+
cached_input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
232
|
+
total_cost_usd: Mapped[float] = mapped_column(Float, default=0.0, server_default=text("0"), nullable=False)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class AccountUsageRollupState(Base):
|
|
236
|
+
"""Single-row fold watermark (naive-UTC), seeded by the migration.
|
|
237
|
+
|
|
238
|
+
Keeping the watermark on a dedicated always-present row gives fold passes
|
|
239
|
+
something to ``SELECT ... FOR UPDATE`` even before any rollup rows exist,
|
|
240
|
+
serializing concurrent backfills, and lets reads fetch sums + watermark in
|
|
241
|
+
one statement (one snapshot).
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
__tablename__ = "account_usage_rollup_state"
|
|
245
|
+
|
|
246
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
|
|
247
|
+
folded_through: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
248
|
+
# Hour-aligned watermark for the time-axis rollups below. Kept as a
|
|
249
|
+
# SEPARATE column (not shared with `folded_through`) so the hourly
|
|
250
|
+
# backfill never resets or rewrites the lifetime rollups, which cannot
|
|
251
|
+
# be recomputed once retention has pruned raw request logs. Invariant:
|
|
252
|
+
# always a whole UTC hour (epoch % 3600 == 0); rows with
|
|
253
|
+
# `requested_at < hourly_folded_through` are fully folded, newer rows
|
|
254
|
+
# are the live tail.
|
|
255
|
+
hourly_folded_through: Mapped[datetime] = mapped_column(
|
|
256
|
+
DateTime,
|
|
257
|
+
nullable=False,
|
|
258
|
+
server_default=text("'1970-01-01 00:00:00'"),
|
|
259
|
+
)
|
|
260
|
+
# Watermark for the conversation presence satellite. Separate from
|
|
261
|
+
# `hourly_folded_through` (same alignment invariant, same state row and
|
|
262
|
+
# row lock) so the satellite added later backfills from epoch without
|
|
263
|
+
# rewinding — and without being gated on — the other time-axis rollups.
|
|
264
|
+
conversation_folded_through: Mapped[datetime] = mapped_column(
|
|
265
|
+
DateTime,
|
|
266
|
+
nullable=False,
|
|
267
|
+
server_default=text("'1970-01-01 00:00:00'"),
|
|
268
|
+
)
|
|
269
|
+
# Start of the hourly range a legacy (pre-cancelled_count) fold pass may
|
|
270
|
+
# have written after the migration ran (#1552 rolling-upgrade fence).
|
|
271
|
+
# The migration stamps existing rows with their hourly_folded_through;
|
|
272
|
+
# the epoch server default covers a state row bootstrapped by an OLD
|
|
273
|
+
# replica after the migration (its entire backfill is legacy-folded).
|
|
274
|
+
# Only NEW code writes NULL, after refolding [marker, watermark) from
|
|
275
|
+
# raw — so NULL always means "no legacy-suspect range outstanding".
|
|
276
|
+
upgrade_repair_from: Mapped[datetime | None] = mapped_column(
|
|
277
|
+
DateTime,
|
|
278
|
+
nullable=True,
|
|
279
|
+
server_default=text("'1970-01-01 00:00:00'"),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
reports_folded_through: Mapped[datetime] = mapped_column(
|
|
283
|
+
DateTime,
|
|
284
|
+
nullable=False,
|
|
285
|
+
server_default=text("'1970-01-01 00:00:00'"),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class RequestReportHourlyRollup(Base):
|
|
290
|
+
"""Permanent report measures; conversation remains a dimension for exact distinct counts.
|
|
291
|
+
|
|
292
|
+
Hours are assembled into timezone days at read time. Normal traffic only,
|
|
293
|
+
including detached/deleted accounts, matching the reports contract.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
__tablename__ = "request_report_hourly_rollups"
|
|
297
|
+
|
|
298
|
+
bucket_epoch: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
299
|
+
account_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
300
|
+
api_key_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
301
|
+
model: Mapped[str] = mapped_column(String, primary_key=True)
|
|
302
|
+
useragent_group: Mapped[str] = mapped_column(String, primary_key=True)
|
|
303
|
+
conversation_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
304
|
+
first_requested_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
305
|
+
request_count: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
306
|
+
error_count: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
307
|
+
cancelled_count: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
308
|
+
input_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
309
|
+
output_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
310
|
+
reasoning_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
311
|
+
reasoning_usage_known_requests: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
312
|
+
cached_input_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default=text("0"))
|
|
313
|
+
cost_usd: Mapped[float] = mapped_column(Float, nullable=False, server_default=text("0"))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class RequestUsageHourlyRollup(Base):
|
|
317
|
+
"""Hour-bucketed request-usage sums (time-axis rollup).
|
|
318
|
+
|
|
319
|
+
One row per UTC hour x dimension combination, folded from raw
|
|
320
|
+
``request_logs`` below the ``hourly_folded_through`` watermark. Rows are
|
|
321
|
+
written by the hourly fold pass (DELETE-then-INSERT per slice, so
|
|
322
|
+
re-folds always converge) and mutated afterwards only by the account
|
|
323
|
+
lifecycle mirrors (soft/hard delete, consolidation) — never recomputed
|
|
324
|
+
from raw, so buckets survive request-log retention pruning.
|
|
325
|
+
|
|
326
|
+
Nullable raw dimensions (``account_id``/``api_key_id``/``service_tier``)
|
|
327
|
+
are stored via the collision-free NULL-sentinel encoding
|
|
328
|
+
(``usage_time_rollup.to_dimension``) so they can participate in the
|
|
329
|
+
primary key on both dialects (UNIQUE treats NULLs as distinct) without
|
|
330
|
+
conflating NULL with a legitimate empty string. ``request_kind`` is
|
|
331
|
+
NOT NULL at the source and stored verbatim (warmup kinds included; reads
|
|
332
|
+
filter by dimension). No FKs: rollup rows must outlive account deletion.
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
__tablename__ = "request_usage_hourly_rollups"
|
|
336
|
+
|
|
337
|
+
bucket_epoch: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
338
|
+
account_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
339
|
+
api_key_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
340
|
+
model: Mapped[str] = mapped_column(String, primary_key=True)
|
|
341
|
+
service_tier: Mapped[str] = mapped_column(String, primary_key=True)
|
|
342
|
+
request_kind: Mapped[str] = mapped_column(String, primary_key=True)
|
|
343
|
+
is_deleted: Mapped[bool] = mapped_column(Boolean, primary_key=True, default=False, server_default=false())
|
|
344
|
+
request_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
345
|
+
# sum(status NOT IN ('success', 'cancelled')) — status is folded as a
|
|
346
|
+
# measure, not a dimension. Rows folded before cancelled_count existed
|
|
347
|
+
# keep the legacy sum(status != 'success') fold (no backfill; a disclosed
|
|
348
|
+
# step change on error-rate trends).
|
|
349
|
+
error_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
350
|
+
# sum(status = 'cancelled') — client-side disconnect terminals, split out
|
|
351
|
+
# of error_count so dashboards can show success/cancelled/error distinctly.
|
|
352
|
+
# 0 (server default) on rows folded before the measure existed.
|
|
353
|
+
cancelled_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
354
|
+
input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
355
|
+
output_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
356
|
+
reasoning_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
357
|
+
# sum(coalesce(output_tokens, reasoning_tokens, 0)) — not derivable from
|
|
358
|
+
# the two sums above (planner / trends / usage-summary semantics).
|
|
359
|
+
output_or_reasoning_tokens: Mapped[int] = mapped_column(
|
|
360
|
+
BigInteger, default=0, server_default=text("0"), nullable=False
|
|
361
|
+
)
|
|
362
|
+
cached_input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
363
|
+
# sum(max(0, min(coalesce(cached, 0), coalesce(input, 0)))) — pre-folded
|
|
364
|
+
# for the future usage-summary switch (one-way door once raw is pruned).
|
|
365
|
+
cached_input_tokens_clamped: Mapped[int] = mapped_column(
|
|
366
|
+
BigInteger, default=0, server_default=text("0"), nullable=False
|
|
367
|
+
)
|
|
368
|
+
cost_usd: Mapped[float] = mapped_column(Float, default=0.0, server_default=text("0"), nullable=False)
|
|
369
|
+
# count(cost_usd IS NOT NULL) — preserves the "all-NULL model excluded"
|
|
370
|
+
# rule of cost-by-model aggregations.
|
|
371
|
+
cost_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class RequestUsageHourlyErrorRollup(Base):
|
|
375
|
+
"""Hour-bucketed error-code counts (top-error satellite).
|
|
376
|
+
|
|
377
|
+
``error_code`` has unbounded cardinality, so it is isolated from the main
|
|
378
|
+
hourly rollup. Fold filter reproduces the top-error read exactly:
|
|
379
|
+
non-warmup kinds, ``status NOT IN ('success', 'cancelled')``,
|
|
380
|
+
``error_code IS NOT NULL`` (soft-deleted rows included). Rows folded
|
|
381
|
+
before cancelled rows left the error fold still carry
|
|
382
|
+
``client_disconnected`` counts; the top-error reads exclude that code.
|
|
383
|
+
``account_id`` is carried only so account hard-deletion can mirror raw
|
|
384
|
+
row removal.
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
__tablename__ = "request_usage_hourly_error_rollups"
|
|
388
|
+
|
|
389
|
+
bucket_epoch: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
390
|
+
account_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
391
|
+
error_code: Mapped[str] = mapped_column(String, primary_key=True)
|
|
392
|
+
error_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class RequestDemandQuarterRollup(Base):
|
|
396
|
+
"""Quarter-hour demand sums for the quota planner (the only sub-hour
|
|
397
|
+
consumer, ``DEFAULT_SLOT_SECONDS = 900``).
|
|
398
|
+
|
|
399
|
+
The FULL legacy demand grain (account, api_key, model, reasoning_effort,
|
|
400
|
+
request_kind, status) is preserved as dimensions: the planner's
|
|
401
|
+
``_bin_demand_units`` applies ``max(token, cost, request units)`` per bin
|
|
402
|
+
BEFORE summing (nonlinear), so a coarser fold would change forecasts
|
|
403
|
+
wherever a slot mixes groups with different dominant components. The row
|
|
404
|
+
count equals what the legacy runtime ``GROUP BY`` returned per query.
|
|
405
|
+
|
|
406
|
+
``is_deleted`` is a dimension (not a fold-time filter) and ``account_id``
|
|
407
|
+
a carried key so account soft/hard deletion — which retroactively detaches
|
|
408
|
+
or removes the account's entire raw history — can be mirrored here instead
|
|
409
|
+
of permanently diverging from the planner's ``deleted_at IS NULL`` view.
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
__tablename__ = "request_demand_quarter_rollups"
|
|
413
|
+
|
|
414
|
+
slot_epoch: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
415
|
+
account_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
416
|
+
api_key_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
417
|
+
model: Mapped[str] = mapped_column(String, primary_key=True)
|
|
418
|
+
reasoning_effort: Mapped[str] = mapped_column(String, primary_key=True)
|
|
419
|
+
request_kind: Mapped[str] = mapped_column(String, primary_key=True)
|
|
420
|
+
status: Mapped[str] = mapped_column(String, primary_key=True)
|
|
421
|
+
is_deleted: Mapped[bool] = mapped_column(Boolean, primary_key=True, default=False, server_default=false())
|
|
422
|
+
request_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
423
|
+
input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
424
|
+
output_or_reasoning_tokens: Mapped[int] = mapped_column(
|
|
425
|
+
BigInteger, default=0, server_default=text("0"), nullable=False
|
|
426
|
+
)
|
|
427
|
+
cached_input_tokens: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
428
|
+
cost_usd: Mapped[float] = mapped_column(Float, default=0.0, server_default=text("0"), nullable=False)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class RequestConversationHourlyRollup(Base):
|
|
432
|
+
"""Hour-bucketed conversation presence (distinct-conversation satellite).
|
|
433
|
+
|
|
434
|
+
One row means "this conversation had ``request_count`` countable requests
|
|
435
|
+
in this UTC hour under this (account, is_deleted) attribution". Distinct
|
|
436
|
+
conversation counts are not additive across buckets, so the reads UNION
|
|
437
|
+
the folded ``conversation_id`` values with the raw live tail and count
|
|
438
|
+
distinct over the merge; ``request_count`` stays additive for the
|
|
439
|
+
conversation-request totals.
|
|
440
|
+
|
|
441
|
+
``conversation_id`` is the normalized value (whitespace-trimmed,
|
|
442
|
+
non-empty — NULL/blank rows are excluded by the fold filter, matching
|
|
443
|
+
every reader). ``is_deleted`` is a dimension because the dashboard
|
|
444
|
+
conversation reads exclude soft-deleted rows while the reports reads
|
|
445
|
+
include them; ``account_id`` (NULL-sentinel encoded) is carried only so
|
|
446
|
+
the account lifecycle mirrors can re-attribute or remove folded presence
|
|
447
|
+
exactly as the raw mutation does. No FKs: rows must outlive accounts.
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
__tablename__ = "request_conversation_hourly_rollups"
|
|
451
|
+
|
|
452
|
+
bucket_epoch: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
453
|
+
conversation_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
454
|
+
account_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
455
|
+
is_deleted: Mapped[bool] = mapped_column(Boolean, primary_key=True, default=False, server_default=false())
|
|
456
|
+
request_count: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class AdditionalUsageHistory(Base):
|
|
460
|
+
__tablename__ = "additional_usage_history"
|
|
461
|
+
|
|
462
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
463
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
|
464
|
+
quota_key: Mapped[str] = mapped_column(String, nullable=False)
|
|
465
|
+
limit_name: Mapped[str] = mapped_column(String, nullable=False)
|
|
466
|
+
metered_feature: Mapped[str] = mapped_column(String, nullable=False)
|
|
467
|
+
window: Mapped[str] = mapped_column(String, nullable=False)
|
|
468
|
+
used_percent: Mapped[float] = mapped_column(Float, nullable=False)
|
|
469
|
+
reset_at: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
470
|
+
window_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
471
|
+
recorded_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class RequestLog(Base):
|
|
475
|
+
__tablename__ = "request_logs"
|
|
476
|
+
__table_args__ = (
|
|
477
|
+
Index("idx_logs_useragent_group", "useragent_group"),
|
|
478
|
+
Index("idx_logs_conversation_id", "conversation_id"),
|
|
479
|
+
Index("idx_logs_client_ip", "client_ip"),
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
sticky_key_source: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
483
|
+
sticky_kind: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
484
|
+
sticky_key_hash: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
485
|
+
|
|
486
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
487
|
+
account_id: Mapped[str | None] = mapped_column(
|
|
488
|
+
String,
|
|
489
|
+
ForeignKey("accounts.id", ondelete="SET NULL"),
|
|
490
|
+
nullable=True,
|
|
491
|
+
)
|
|
492
|
+
model_source_id: Mapped[str | None] = mapped_column(
|
|
493
|
+
String,
|
|
494
|
+
nullable=True,
|
|
495
|
+
)
|
|
496
|
+
model_source_kind: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
497
|
+
api_key_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
498
|
+
session_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
499
|
+
request_id: Mapped[str] = mapped_column(String, nullable=False)
|
|
500
|
+
archive_request_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
501
|
+
request_kind: Mapped[str] = mapped_column(
|
|
502
|
+
String,
|
|
503
|
+
default=RequestKind.NORMAL.value,
|
|
504
|
+
server_default=text("'normal'"),
|
|
505
|
+
nullable=False,
|
|
506
|
+
)
|
|
507
|
+
connection_request_kind: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
508
|
+
requested_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
509
|
+
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
510
|
+
model: Mapped[str] = mapped_column(String, nullable=False)
|
|
511
|
+
plan_type: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
512
|
+
source: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
513
|
+
useragent: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
514
|
+
useragent_group: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
515
|
+
conversation_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
516
|
+
client_ip: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
517
|
+
transport: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
518
|
+
service_tier: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
519
|
+
requested_service_tier: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
520
|
+
actual_service_tier: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
521
|
+
input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
522
|
+
output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
523
|
+
cached_input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
524
|
+
reasoning_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
525
|
+
cost_usd: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
526
|
+
reasoning_effort: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
527
|
+
latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
528
|
+
latency_first_token_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
529
|
+
# Pre-attempt wait (account selection, admission waits, failed failover
|
|
530
|
+
# attempts) — kept out of latency_ms/latency_first_token_ms so those two
|
|
531
|
+
# always share the successful attempt's anchor.
|
|
532
|
+
latency_queue_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
533
|
+
latency_response_created_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
534
|
+
latency_first_upstream_event_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
535
|
+
latency_response_create_gate_wait_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
536
|
+
latency_bridge_queue_wait_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
537
|
+
prewarm_status: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
538
|
+
prewarm_latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
539
|
+
session_previous_gap_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
540
|
+
status: Mapped[str] = mapped_column(String, nullable=False)
|
|
541
|
+
error_code: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
542
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
543
|
+
failure_phase: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
544
|
+
failure_detail: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
545
|
+
failure_exception_type: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
546
|
+
upstream_status_code: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
547
|
+
upstream_error_code: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
548
|
+
bridge_stage: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
549
|
+
upstream_proxy_route_mode: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
550
|
+
upstream_transport: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
551
|
+
upstream_proxy_pool_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
552
|
+
upstream_proxy_endpoint_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
553
|
+
upstream_proxy_fallback_used: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
554
|
+
upstream_proxy_fail_closed_reason: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
555
|
+
account: Mapped[Account | None] = relationship(
|
|
556
|
+
"Account",
|
|
557
|
+
back_populates="request_logs",
|
|
558
|
+
)
|
|
559
|
+
model_source: Mapped["ModelSource | None"] = relationship(
|
|
560
|
+
"ModelSource",
|
|
561
|
+
back_populates="request_logs",
|
|
562
|
+
primaryjoin="foreign(RequestLog.model_source_id) == ModelSource.id",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class ProxyEndpoint(Base):
|
|
567
|
+
__tablename__ = "proxy_endpoints"
|
|
568
|
+
|
|
569
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
570
|
+
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
571
|
+
scheme: Mapped[str] = mapped_column(String, nullable=False)
|
|
572
|
+
host: Mapped[str] = mapped_column(String, nullable=False)
|
|
573
|
+
port: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
574
|
+
username: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
575
|
+
password_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
576
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
577
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
578
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
579
|
+
DateTime,
|
|
580
|
+
server_default=func.now(),
|
|
581
|
+
onupdate=func.now(),
|
|
582
|
+
nullable=False,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
pool_memberships: Mapped[list["ProxyPoolMember"]] = relationship(
|
|
586
|
+
"ProxyPoolMember",
|
|
587
|
+
back_populates="endpoint",
|
|
588
|
+
cascade="all, delete-orphan",
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
class ProxyPool(Base):
|
|
593
|
+
__tablename__ = "proxy_pools"
|
|
594
|
+
|
|
595
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
596
|
+
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
597
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
598
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
599
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
600
|
+
DateTime,
|
|
601
|
+
server_default=func.now(),
|
|
602
|
+
onupdate=func.now(),
|
|
603
|
+
nullable=False,
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
members: Mapped[list["ProxyPoolMember"]] = relationship(
|
|
607
|
+
"ProxyPoolMember",
|
|
608
|
+
back_populates="pool",
|
|
609
|
+
cascade="all, delete-orphan",
|
|
610
|
+
)
|
|
611
|
+
account_bindings: Mapped[list["AccountProxyBinding"]] = relationship(
|
|
612
|
+
"AccountProxyBinding",
|
|
613
|
+
back_populates="pool",
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class ProxyPoolMember(Base):
|
|
618
|
+
__tablename__ = "proxy_pool_members"
|
|
619
|
+
|
|
620
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
621
|
+
pool_id: Mapped[str] = mapped_column(String, ForeignKey("proxy_pools.id", ondelete="CASCADE"), nullable=False)
|
|
622
|
+
endpoint_id: Mapped[str] = mapped_column(
|
|
623
|
+
String,
|
|
624
|
+
ForeignKey("proxy_endpoints.id", ondelete="CASCADE"),
|
|
625
|
+
nullable=False,
|
|
626
|
+
)
|
|
627
|
+
sort_order: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"), nullable=False)
|
|
628
|
+
weight: Mapped[int] = mapped_column(Integer, default=1, server_default=text("1"), nullable=False)
|
|
629
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
630
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
631
|
+
|
|
632
|
+
pool: Mapped[ProxyPool] = relationship("ProxyPool", back_populates="members")
|
|
633
|
+
endpoint: Mapped[ProxyEndpoint] = relationship("ProxyEndpoint", back_populates="pool_memberships")
|
|
634
|
+
|
|
635
|
+
__table_args__ = (
|
|
636
|
+
UniqueConstraint("pool_id", "endpoint_id", name="uq_proxy_pool_members_pool_endpoint"),
|
|
637
|
+
Index("idx_proxy_pool_members_pool_order", "pool_id", "is_active", "sort_order", "id"),
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class AccountProxyBinding(Base):
|
|
642
|
+
__tablename__ = "account_proxy_bindings"
|
|
643
|
+
|
|
644
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
645
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
|
646
|
+
pool_id: Mapped[str] = mapped_column(String, ForeignKey("proxy_pools.id", ondelete="RESTRICT"), nullable=False)
|
|
647
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
648
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
649
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
650
|
+
DateTime,
|
|
651
|
+
server_default=func.now(),
|
|
652
|
+
onupdate=func.now(),
|
|
653
|
+
nullable=False,
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
account: Mapped[Account] = relationship("Account", back_populates="proxy_binding")
|
|
657
|
+
pool: Mapped[ProxyPool] = relationship("ProxyPool", back_populates="account_bindings")
|
|
658
|
+
|
|
659
|
+
__table_args__ = (UniqueConstraint("account_id", name="uq_account_proxy_bindings_account"),)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
class AccountLimitWarmup(Base):
|
|
663
|
+
__tablename__ = "account_limit_warmups"
|
|
664
|
+
|
|
665
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
666
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
|
667
|
+
window: Mapped[str] = mapped_column(String, nullable=False)
|
|
668
|
+
reset_at: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
669
|
+
status: Mapped[str] = mapped_column(String, nullable=False)
|
|
670
|
+
model: Mapped[str] = mapped_column(String, nullable=False)
|
|
671
|
+
attempted_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
672
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
673
|
+
error_code: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
674
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
675
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
676
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
677
|
+
DateTime,
|
|
678
|
+
server_default=func.now(),
|
|
679
|
+
onupdate=func.now(),
|
|
680
|
+
nullable=False,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
account: Mapped[Account] = relationship(
|
|
684
|
+
"Account",
|
|
685
|
+
back_populates="limit_warmups",
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
__table_args__ = (
|
|
689
|
+
UniqueConstraint(
|
|
690
|
+
"account_id",
|
|
691
|
+
"window",
|
|
692
|
+
"reset_at",
|
|
693
|
+
name="uq_account_limit_warmups_account_window_reset",
|
|
694
|
+
),
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
class AuditLog(Base):
|
|
699
|
+
"""Append-only record of dashboard actions.
|
|
700
|
+
|
|
701
|
+
The ``actor_*`` columns are a snapshot of the acting account, not foreign
|
|
702
|
+
keys: a row must survive the deletion of the account it names. They are all
|
|
703
|
+
NULL for rows written before attribution existed and for principals without
|
|
704
|
+
an account row (implicit local admin, trusted header, guest).
|
|
705
|
+
"""
|
|
706
|
+
|
|
707
|
+
__tablename__ = "audit_logs"
|
|
708
|
+
__table_args__ = (
|
|
709
|
+
Index("idx_audit_logs_actor_user_id", "actor_user_id"),
|
|
710
|
+
Index("idx_audit_logs_target", "target_type", "target_id"),
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
714
|
+
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=func.now(), nullable=False, index=True)
|
|
715
|
+
action: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
|
716
|
+
actor_ip: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
717
|
+
details: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
718
|
+
request_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
719
|
+
actor_user_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
720
|
+
actor_username: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
721
|
+
actor_role_slug: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
722
|
+
auth_method: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
723
|
+
target_type: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
724
|
+
target_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
725
|
+
severity: Mapped[str] = mapped_column(String(16), nullable=False, server_default=text("'info'"))
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
class SchedulerLeader(Base):
|
|
729
|
+
__tablename__ = "scheduler_leader"
|
|
730
|
+
|
|
731
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
|
|
732
|
+
leader_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
733
|
+
acquired_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
734
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
class ResetCreditRedeemRequest(Base):
|
|
738
|
+
"""Durable (account, redeem_request_id) -> credit_id idempotency ledger.
|
|
739
|
+
|
|
740
|
+
Written inside the per-account serialized redeem section BEFORE the
|
|
741
|
+
upstream consume call so a retry carrying the same redeem_request_id —
|
|
742
|
+
served by ANY replica — resolves to the originally selected credit and
|
|
743
|
+
never burns a second one. Rows are purged opportunistically after 24h.
|
|
744
|
+
"""
|
|
745
|
+
|
|
746
|
+
__tablename__ = "reset_credit_redeem_requests"
|
|
747
|
+
|
|
748
|
+
account_id: Mapped[str] = mapped_column(
|
|
749
|
+
String,
|
|
750
|
+
ForeignKey("accounts.id", ondelete="CASCADE"),
|
|
751
|
+
primary_key=True,
|
|
752
|
+
)
|
|
753
|
+
redeem_request_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
754
|
+
credit_id: Mapped[str] = mapped_column(String, nullable=False)
|
|
755
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
class ResetCreditRedeemClaim(Base):
|
|
759
|
+
"""Cross-process per-account redeem serialization claim for SQLite.
|
|
760
|
+
|
|
761
|
+
A single atomic conditional upsert (INSERT ... ON CONFLICT DO UPDATE ...
|
|
762
|
+
WHERE expires_at < now) under SQLite's single-writer lock gives real
|
|
763
|
+
multi-process mutual exclusion; the lease expiry recovers crashed holders.
|
|
764
|
+
PostgreSQL deployments keep using pg_advisory_xact_lock instead.
|
|
765
|
+
"""
|
|
766
|
+
|
|
767
|
+
__tablename__ = "reset_credit_redeem_claims"
|
|
768
|
+
|
|
769
|
+
account_id: Mapped[str] = mapped_column(
|
|
770
|
+
String,
|
|
771
|
+
ForeignKey("accounts.id", ondelete="CASCADE"),
|
|
772
|
+
primary_key=True,
|
|
773
|
+
)
|
|
774
|
+
holder_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
775
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
class OAuthFlowState(Base):
|
|
779
|
+
"""Durable dashboard OAuth add-account / reauth flow state.
|
|
780
|
+
|
|
781
|
+
The dashboard OAuth flow (PKCE `code_verifier`, `state` token, device-code
|
|
782
|
+
poll metadata, and status) is persisted here keyed by `flow_id` so that any
|
|
783
|
+
replica behind a load balancer can complete a flow it did not start: the
|
|
784
|
+
browser callback, a manually pasted callback URL, or a device-code status
|
|
785
|
+
poll can land on a different replica than the one that ran `start`. The
|
|
786
|
+
`code_verifier` is stored encrypted with the same key material as account
|
|
787
|
+
tokens. Abandoned pending flows expire via `expires_at` and are purged
|
|
788
|
+
opportunistically on write.
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
__tablename__ = "oauth_flow_states"
|
|
792
|
+
|
|
793
|
+
flow_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
794
|
+
state_token: Mapped[str | None] = mapped_column(String, nullable=True, unique=True, index=True)
|
|
795
|
+
method: Mapped[str] = mapped_column(String, nullable=False)
|
|
796
|
+
status: Mapped[str] = mapped_column(String, nullable=False)
|
|
797
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
798
|
+
intended_account_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
799
|
+
code_verifier_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
800
|
+
device_auth_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
801
|
+
user_code: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
802
|
+
interval_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
803
|
+
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
804
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
|
|
805
|
+
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
class OAuthDeviceFlowSlot(Base):
|
|
809
|
+
"""Single-active-device-flow coordination slot (cross-replica).
|
|
810
|
+
|
|
811
|
+
At most one dashboard device-code OAuth flow is "current" at a time. A
|
|
812
|
+
device ``start`` atomically REPLACES the slot via a single conditional
|
|
813
|
+
UPSERT on the fixed ``slot_key``, so two replicas starting device OAuth
|
|
814
|
+
simultaneously leave exactly one current ``flow_id`` instead of two orphaned
|
|
815
|
+
pending rows that both believe they are current. A poller atomically
|
|
816
|
+
CONSUMES the slot (delete-if-mine) as its point of no return immediately
|
|
817
|
+
before persisting tokens: a poller whose flow was superseded (the slot now
|
|
818
|
+
names a different ``flow_id``) loses the consume and MUST abort without
|
|
819
|
+
adding or re-authenticating an account. ``generation`` is a monotonic claim
|
|
820
|
+
token bumped on every replacement, retained for observability.
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
__tablename__ = "oauth_device_flow_slots"
|
|
824
|
+
|
|
825
|
+
slot_key: Mapped[str] = mapped_column(String, primary_key=True)
|
|
826
|
+
flow_id: Mapped[str] = mapped_column(String, nullable=False)
|
|
827
|
+
generation: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
828
|
+
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
class StickySession(Base):
|
|
832
|
+
__tablename__ = "sticky_sessions"
|
|
833
|
+
|
|
834
|
+
key: Mapped[str] = mapped_column(String, primary_key=True)
|
|
835
|
+
kind: Mapped[StickySessionKind] = mapped_column(
|
|
836
|
+
SqlEnum(
|
|
837
|
+
StickySessionKind,
|
|
838
|
+
name="sticky_session_kind",
|
|
839
|
+
validate_strings=True,
|
|
840
|
+
values_callable=_enum_values,
|
|
841
|
+
),
|
|
842
|
+
primary_key=True,
|
|
843
|
+
default=StickySessionKind.STICKY_THREAD,
|
|
844
|
+
server_default=text("'sticky_thread'"),
|
|
845
|
+
nullable=False,
|
|
846
|
+
)
|
|
847
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
|
848
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
849
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
850
|
+
DateTime,
|
|
851
|
+
server_default=func.now(),
|
|
852
|
+
onupdate=func.now(),
|
|
853
|
+
nullable=False,
|
|
854
|
+
)
|
|
855
|
+
# A non-null timestamp with NULL scope is the historical global tombstone.
|
|
856
|
+
# Source-scoped abandonment instead leaves this timestamp NULL and stores
|
|
857
|
+
# the typed scope below. That asymmetry is intentional: binaries predating
|
|
858
|
+
# the scope column see a live hard owner during rollout/rollback, while new
|
|
859
|
+
# binaries can let a process-session restart ignore the ambiguous raw row
|
|
860
|
+
# without erasing its explicit-turn-state ownership. Stale-hard cleanup
|
|
861
|
+
# may later promote the scoped marker to a timestamped global tombstone.
|
|
862
|
+
continuity_abandoned_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None)
|
|
863
|
+
continuity_abandonment_scope: Mapped[str | None] = mapped_column(String(32), nullable=True, default=None)
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
class CapabilityLineageMarker(Base):
|
|
867
|
+
__tablename__ = "capability_lineage_markers"
|
|
868
|
+
|
|
869
|
+
marker_hash: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
870
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
871
|
+
DateTime(timezone=True),
|
|
872
|
+
server_default=func.now(),
|
|
873
|
+
nullable=False,
|
|
874
|
+
)
|
|
875
|
+
last_seen_at: Mapped[datetime] = mapped_column(
|
|
876
|
+
DateTime(timezone=True),
|
|
877
|
+
server_default=func.now(),
|
|
878
|
+
onupdate=func.now(),
|
|
879
|
+
nullable=False,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
class DashboardUserStatus(str, Enum):
|
|
884
|
+
ACTIVE = "active"
|
|
885
|
+
DISABLED = "disabled"
|
|
886
|
+
INVITED = "invited"
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
class DashboardUserRoleSource(str, Enum):
|
|
890
|
+
MANUAL = "manual"
|
|
891
|
+
MAPPING = "mapping"
|
|
892
|
+
SCIM = "scim"
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
class ApiKeyDeactivatedReason(str, Enum):
|
|
896
|
+
MANUAL = "manual"
|
|
897
|
+
OWNER_DISABLED = "owner_disabled"
|
|
898
|
+
EXPIRED = "expired"
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
class AuthProviderKind(str, Enum):
|
|
902
|
+
PASSWORD = "password"
|
|
903
|
+
TRUSTED_HEADER = "trusted_header"
|
|
904
|
+
OIDC = "oidc"
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
class LocalLoginPolicy(str, Enum):
|
|
908
|
+
"""Who may still sign in with a local password (PLAN §4.6, DB only).
|
|
909
|
+
|
|
910
|
+
``ENABLED`` is today's behaviour and the default: every active account that
|
|
911
|
+
holds a password may sign in. The two tightened values are the switch a
|
|
912
|
+
company throws once its people arrive through a sign-in provider; both are
|
|
913
|
+
guarded by the qualifying break-glass invariant so the switch can never be
|
|
914
|
+
a lockout.
|
|
915
|
+
"""
|
|
916
|
+
|
|
917
|
+
ENABLED = "enabled"
|
|
918
|
+
ADMINS_ONLY = "admins_only"
|
|
919
|
+
BREAK_GLASS_ONLY = "break_glass_only"
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
#: Name the install's first (break-glass) account is created under, and the one
|
|
923
|
+
#: name no other account may take. It is a reservation of the *name*: the
|
|
924
|
+
#: account itself may be renamed, so nothing may identify it by this string.
|
|
925
|
+
COMPAT_ADMIN_USERNAME = "admin"
|
|
926
|
+
|
|
927
|
+
#: Deterministic id of that account, so the migration and the runtime bootstrap
|
|
928
|
+
#: path create the same row, re-runs stay idempotent, and every path that has to
|
|
929
|
+
#: find the bootstrapped account after a rename has a stable handle.
|
|
930
|
+
COMPAT_ADMIN_USER_ID = str(
|
|
931
|
+
uuid.uuid5(uuid.UUID("6f1c0e4e-2b4a-4c1e-9c3b-7a5d2e8f0a11"), "codex-lb:dashboard-user:compat-admin")
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
class DashboardUser(Base):
|
|
936
|
+
"""A person (or service) who signs in to the dashboard.
|
|
937
|
+
|
|
938
|
+
``status``/``role_source`` are plain strings validated by the enums above
|
|
939
|
+
(no database enum type, so adding values needs no type migration). The
|
|
940
|
+
``role_id`` foreign key is RESTRICT: a role in use cannot be deleted.
|
|
941
|
+
"""
|
|
942
|
+
|
|
943
|
+
__tablename__ = "dashboard_users"
|
|
944
|
+
|
|
945
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
946
|
+
username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
|
947
|
+
display_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
948
|
+
email: Mapped[str | None] = mapped_column(String(320), unique=True, nullable=True)
|
|
949
|
+
role_id: Mapped[str] = mapped_column(
|
|
950
|
+
String,
|
|
951
|
+
ForeignKey("dashboard_roles.id", ondelete="RESTRICT"),
|
|
952
|
+
nullable=False,
|
|
953
|
+
)
|
|
954
|
+
role_source: Mapped[str] = mapped_column(
|
|
955
|
+
String(16),
|
|
956
|
+
default=DashboardUserRoleSource.MANUAL.value,
|
|
957
|
+
server_default=text("'manual'"),
|
|
958
|
+
nullable=False,
|
|
959
|
+
)
|
|
960
|
+
status: Mapped[str] = mapped_column(
|
|
961
|
+
String(16),
|
|
962
|
+
default=DashboardUserStatus.ACTIVE.value,
|
|
963
|
+
server_default=text("'active'"),
|
|
964
|
+
nullable=False,
|
|
965
|
+
)
|
|
966
|
+
password_hash: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
967
|
+
totp_secret_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
968
|
+
totp_last_verified_step: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
969
|
+
session_generation: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"), nullable=False)
|
|
970
|
+
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
971
|
+
is_break_glass: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
972
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
973
|
+
created_by_user_id: Mapped[str | None] = mapped_column(
|
|
974
|
+
String,
|
|
975
|
+
ForeignKey("dashboard_users.id", ondelete="SET NULL"),
|
|
976
|
+
nullable=True,
|
|
977
|
+
)
|
|
978
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
979
|
+
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
|
980
|
+
)
|
|
981
|
+
last_login_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
982
|
+
|
|
983
|
+
role: Mapped["DashboardRoleRecord"] = relationship("DashboardRoleRecord")
|
|
984
|
+
identities: Mapped[list["DashboardIdentity"]] = relationship(
|
|
985
|
+
"DashboardIdentity",
|
|
986
|
+
back_populates="user",
|
|
987
|
+
cascade="all, delete-orphan",
|
|
988
|
+
passive_deletes=True,
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
class DashboardIdentity(Base):
|
|
993
|
+
"""An external identity (reverse-proxy header, OIDC, SCIM) linked to a user.
|
|
994
|
+
|
|
995
|
+
One user may hold several identities from several providers; the
|
|
996
|
+
``(provider, provider_key, subject)`` triple is unique.
|
|
997
|
+
"""
|
|
998
|
+
|
|
999
|
+
__tablename__ = "dashboard_identities"
|
|
1000
|
+
__table_args__ = (
|
|
1001
|
+
UniqueConstraint("provider", "provider_key", "subject", name="uq_dashboard_identities_subject"),
|
|
1002
|
+
Index("idx_dashboard_identities_user_id", "user_id"),
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
1006
|
+
user_id: Mapped[str] = mapped_column(
|
|
1007
|
+
String,
|
|
1008
|
+
ForeignKey("dashboard_users.id", ondelete="CASCADE"),
|
|
1009
|
+
nullable=False,
|
|
1010
|
+
)
|
|
1011
|
+
provider: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
1012
|
+
provider_key: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
1013
|
+
subject: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
1014
|
+
email: Mapped[str | None] = mapped_column(String(320), nullable=True)
|
|
1015
|
+
display_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
1016
|
+
groups_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
1017
|
+
#: What the provider calls this person, as pushed and unslugified. Only the
|
|
1018
|
+
#: SCIM path writes it, because only SCIM has to answer a filter on the
|
|
1019
|
+
#: provider's own spelling: our usernames have no ``@``, so an address can
|
|
1020
|
+
#: never equal one and an identity provider reconciling its own resources
|
|
1021
|
+
#: would otherwise match nothing it created.
|
|
1022
|
+
user_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
1023
|
+
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
1024
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1025
|
+
|
|
1026
|
+
user: Mapped["DashboardUser"] = relationship("DashboardUser", back_populates="identities")
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
class DashboardUserInvite(Base):
|
|
1030
|
+
"""A one-time invitation link for a pre-created (``status=invited``) account.
|
|
1031
|
+
|
|
1032
|
+
Only the SHA-256 of the token is stored; the plaintext is returned once when
|
|
1033
|
+
the invite is issued or resent. A user holds at most one invite row
|
|
1034
|
+
(resending rotates the token in place). Revoking or lazily expiring the
|
|
1035
|
+
invite of an ``invited`` account deletes the account row with it, so no
|
|
1036
|
+
orphan "invited" accounts remain. ``created_by_user_id`` is a snapshot, not
|
|
1037
|
+
a foreign key: the invite must survive the inviter's deletion.
|
|
1038
|
+
"""
|
|
1039
|
+
|
|
1040
|
+
__tablename__ = "dashboard_user_invites"
|
|
1041
|
+
__table_args__ = (
|
|
1042
|
+
# One open invite per expected identity: two pre-created accounts must not wait for the same person.
|
|
1043
|
+
Index(
|
|
1044
|
+
"uq_dashboard_user_invites_expected_identity",
|
|
1045
|
+
"expected_provider",
|
|
1046
|
+
"expected_provider_key",
|
|
1047
|
+
"expected_subject",
|
|
1048
|
+
unique=True,
|
|
1049
|
+
postgresql_where=text("consumed_at IS NULL AND revoked_at IS NULL"),
|
|
1050
|
+
sqlite_where=text("consumed_at IS NULL AND revoked_at IS NULL"),
|
|
1051
|
+
),
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
1055
|
+
user_id: Mapped[str] = mapped_column(
|
|
1056
|
+
String,
|
|
1057
|
+
ForeignKey("dashboard_users.id", ondelete="CASCADE"),
|
|
1058
|
+
unique=True,
|
|
1059
|
+
nullable=False,
|
|
1060
|
+
)
|
|
1061
|
+
token_hash: Mapped[bytes] = mapped_column(LargeBinary, unique=True, nullable=False)
|
|
1062
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
1063
|
+
consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
1064
|
+
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
1065
|
+
created_by_user_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
1066
|
+
sso_only: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1067
|
+
username_locked: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1068
|
+
#: The external identity this pre-created account waits for; the identity
|
|
1069
|
+
#: resolver links it (and activates the account) on an exact triple match.
|
|
1070
|
+
expected_provider: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
1071
|
+
expected_provider_key: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
1072
|
+
expected_subject: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
1073
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
1074
|
+
|
|
1075
|
+
user: Mapped["DashboardUser"] = relationship("DashboardUser")
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
class DashboardAuthProvider(Base):
|
|
1079
|
+
"""One way of signing in to the dashboard (password, trusted header, later OIDC).
|
|
1080
|
+
|
|
1081
|
+
Rows carry the settings the identity resolver reads: which role an unknown
|
|
1082
|
+
identity gets (``NULL`` = refuse), where a mapped user without a matching
|
|
1083
|
+
mapping lands, whether e-mail linking is allowed, and whether the IdP is
|
|
1084
|
+
trusted for MFA. The password and trusted-header providers have one row
|
|
1085
|
+
each (``provider_key`` ``default``). ``enabled`` is not a copy of the auth
|
|
1086
|
+
mode: a provider is *active* when its row is enabled and the mode allows
|
|
1087
|
+
it. ``config_encrypted`` holds provider secrets (OIDC) and is never returned
|
|
1088
|
+
in clear.
|
|
1089
|
+
"""
|
|
1090
|
+
|
|
1091
|
+
__tablename__ = "dashboard_auth_providers"
|
|
1092
|
+
__table_args__ = (UniqueConstraint("kind", "provider_key", name="uq_dashboard_auth_providers_kind_key"),)
|
|
1093
|
+
|
|
1094
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
1095
|
+
kind: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
1096
|
+
provider_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1097
|
+
enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
1098
|
+
label: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1099
|
+
config_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
1100
|
+
unknown_identity_role_id: Mapped[str | None] = mapped_column(
|
|
1101
|
+
String(36),
|
|
1102
|
+
ForeignKey("dashboard_roles.id", ondelete="SET NULL"),
|
|
1103
|
+
nullable=True,
|
|
1104
|
+
)
|
|
1105
|
+
no_match_role_id: Mapped[str | None] = mapped_column(
|
|
1106
|
+
String(36),
|
|
1107
|
+
ForeignKey("dashboard_roles.id", ondelete="SET NULL"),
|
|
1108
|
+
nullable=True,
|
|
1109
|
+
)
|
|
1110
|
+
link_by_email: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1111
|
+
skip_role_sync: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1112
|
+
idp_mfa_enforced: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1113
|
+
#: Proof that the acting admin's own browser completed a round trip through
|
|
1114
|
+
#: this exact configuration. Enabling a redirect-style provider requires one
|
|
1115
|
+
#: no older than ten minutes; freshness is computed on read, never stored as
|
|
1116
|
+
#: a deadline, and a connection-field write clears it.
|
|
1117
|
+
test_login_user_id: Mapped[str | None] = mapped_column(
|
|
1118
|
+
String(36),
|
|
1119
|
+
ForeignKey("dashboard_users.id", ondelete="SET NULL"),
|
|
1120
|
+
nullable=True,
|
|
1121
|
+
)
|
|
1122
|
+
test_login_verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
1123
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
1124
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1125
|
+
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
class DashboardOidcLoginFlow(Base):
|
|
1130
|
+
"""One in-flight OIDC round trip, held where every replica can see it.
|
|
1131
|
+
|
|
1132
|
+
The callback routinely lands on a replica that did not serve the start, so
|
|
1133
|
+
the flow cannot live in process memory. Neither the ``state`` nor the
|
|
1134
|
+
``nonce`` is stored in clear: the ``state`` arrives in the callback URL and
|
|
1135
|
+
the ``nonce`` arrives inside the ID token, so both can be hashed and
|
|
1136
|
+
compared, and a copy that is never needed in clear is only a liability. The
|
|
1137
|
+
row is consumed by one conditional ``DELETE``, which is what makes a state
|
|
1138
|
+
single-use across the fleet.
|
|
1139
|
+
"""
|
|
1140
|
+
|
|
1141
|
+
__tablename__ = "dashboard_oidc_login_flows"
|
|
1142
|
+
__table_args__ = (Index("idx_dashboard_oidc_login_flows_expires_at", "expires_at"),)
|
|
1143
|
+
|
|
1144
|
+
state_hash: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
1145
|
+
provider_id: Mapped[str] = mapped_column(
|
|
1146
|
+
String(36),
|
|
1147
|
+
ForeignKey("dashboard_auth_providers.id", ondelete="CASCADE"),
|
|
1148
|
+
nullable=False,
|
|
1149
|
+
)
|
|
1150
|
+
nonce_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1151
|
+
code_verifier_encrypted: Mapped[bytes] = mapped_column(LargeBinary, nullable=False)
|
|
1152
|
+
#: ``login``, ``test`` or ``step_up``; it decides where the browser lands,
|
|
1153
|
+
#: which is why no destination is ever accepted from the caller.
|
|
1154
|
+
purpose: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
1155
|
+
acting_user_id: Mapped[str | None] = mapped_column(
|
|
1156
|
+
String(36),
|
|
1157
|
+
ForeignKey("dashboard_users.id", ondelete="CASCADE"),
|
|
1158
|
+
nullable=True,
|
|
1159
|
+
)
|
|
1160
|
+
#: The redirect URI exactly as sent, so the token exchange repeats it
|
|
1161
|
+
#: byte-identically even if the configuration changes mid-flow.
|
|
1162
|
+
redirect_uri: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
1163
|
+
#: A digest of the connection document this flow was started against. A
|
|
1164
|
+
#: pre-flight proves *a configuration*, so the stamp it leaves must name the
|
|
1165
|
+
#: one it actually reached: without this a configuration write that commits
|
|
1166
|
+
#: while the callback is exchanging its code would be handed the proof that
|
|
1167
|
+
#: the previous issuer worked.
|
|
1168
|
+
config_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1169
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
1170
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
class DashboardScimToken(Base):
|
|
1174
|
+
"""A bearer credential that may reach ``/scim/v2`` and nothing else.
|
|
1175
|
+
|
|
1176
|
+
Only the SHA-256 digest of the secret is stored, in a unique index, so the
|
|
1177
|
+
lookup is an index equality and no copy of the credential exists after it
|
|
1178
|
+
is issued; ``token_prefix`` is the non-secret head of the value, kept in
|
|
1179
|
+
clear so an operator can tell two tokens apart. Rotation replaces the
|
|
1180
|
+
digest and the prefix on the same row, so the id, the label and the sync
|
|
1181
|
+
history survive and the previous secret stops working the moment the write
|
|
1182
|
+
commits. ``provider_key`` is the identity namespace the token writes and
|
|
1183
|
+
reads: it comes from this row on every request and never from the caller.
|
|
1184
|
+
"""
|
|
1185
|
+
|
|
1186
|
+
__tablename__ = "dashboard_scim_tokens"
|
|
1187
|
+
|
|
1188
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
1189
|
+
label: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1190
|
+
token_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
|
1191
|
+
token_prefix: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
1192
|
+
provider_key: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
1193
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
1194
|
+
#: A snapshot link, cleared rather than cascaded: revoking a token is an
|
|
1195
|
+
#: act of its own and must not be a side effect of deleting its issuer.
|
|
1196
|
+
created_by_user_id: Mapped[str | None] = mapped_column(
|
|
1197
|
+
String(36),
|
|
1198
|
+
ForeignKey("dashboard_users.id", ondelete="SET NULL"),
|
|
1199
|
+
nullable=True,
|
|
1200
|
+
)
|
|
1201
|
+
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
1202
|
+
rotated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
class DashboardRoleMappingClaim(str, Enum):
|
|
1206
|
+
"""Which fact about an identity a rule matches on.
|
|
1207
|
+
|
|
1208
|
+
A plain string column validated here, not a database enum: a later claim
|
|
1209
|
+
(an OIDC claim name, a SCIM attribute) must not need a type migration.
|
|
1210
|
+
"""
|
|
1211
|
+
|
|
1212
|
+
GROUPS = "groups"
|
|
1213
|
+
EMAIL_DOMAIN = "email_domain"
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
class DashboardRoleMapping(Base):
|
|
1217
|
+
"""One "identities like this get that role" rule of a sign-in provider.
|
|
1218
|
+
|
|
1219
|
+
Rules are evaluated in descending ``priority`` and the first match wins;
|
|
1220
|
+
``UNIQUE(provider, provider_key, priority)`` makes a tie impossible and the
|
|
1221
|
+
order server-owned (the rows of one provider are always the contiguous
|
|
1222
|
+
integers ``N..1``). ``role_id`` is RESTRICT: a role a rule hands out cannot
|
|
1223
|
+
be deleted while the rule exists.
|
|
1224
|
+
"""
|
|
1225
|
+
|
|
1226
|
+
__tablename__ = "dashboard_role_mappings"
|
|
1227
|
+
__table_args__ = (
|
|
1228
|
+
UniqueConstraint("provider", "provider_key", "priority", name="uq_dashboard_role_mappings_priority"),
|
|
1229
|
+
Index("idx_dashboard_role_mappings_provider", "provider", "provider_key"),
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
1233
|
+
provider: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
1234
|
+
provider_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1235
|
+
claim_name: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
1236
|
+
#: Normalized (trimmed, case-folded; an ``email_domain`` value carries no leading ``@``).
|
|
1237
|
+
claim_value: Mapped[str] = mapped_column(String(320), nullable=False)
|
|
1238
|
+
role_id: Mapped[str] = mapped_column(
|
|
1239
|
+
String(36),
|
|
1240
|
+
ForeignKey("dashboard_roles.id", ondelete="RESTRICT"),
|
|
1241
|
+
nullable=False,
|
|
1242
|
+
)
|
|
1243
|
+
priority: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
1244
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
1245
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1246
|
+
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
class DashboardRoleRecord(Base):
|
|
1251
|
+
"""A dashboard role: one of the five presets or an operator-defined custom role.
|
|
1252
|
+
|
|
1253
|
+
Preset rows exist so users, invites, mappings and policies can reference a
|
|
1254
|
+
role by foreign key and so listings have one source; their grants are the
|
|
1255
|
+
code table ``PRESET_ROLE_GRANTS`` and are never stored. Only custom roles
|
|
1256
|
+
carry ``dashboard_role_grants`` rows. ``kind`` is a plain string validated
|
|
1257
|
+
by the application (adding a value must not need a database type change).
|
|
1258
|
+
"""
|
|
1259
|
+
|
|
1260
|
+
__tablename__ = "dashboard_roles"
|
|
1261
|
+
|
|
1262
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
1263
|
+
slug: Mapped[str] = mapped_column(String(32), unique=True, nullable=False)
|
|
1264
|
+
name: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
1265
|
+
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
1266
|
+
kind: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
1267
|
+
assignable_to_users: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
1268
|
+
cloned_from_role_id: Mapped[str | None] = mapped_column(
|
|
1269
|
+
String,
|
|
1270
|
+
ForeignKey("dashboard_roles.id", ondelete="SET NULL"),
|
|
1271
|
+
nullable=True,
|
|
1272
|
+
)
|
|
1273
|
+
permissions_version: Mapped[int] = mapped_column(Integer, default=1, server_default=text("1"), nullable=False)
|
|
1274
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1275
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1276
|
+
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
|
1277
|
+
)
|
|
1278
|
+
|
|
1279
|
+
grants: Mapped[list["DashboardRoleGrant"]] = relationship(
|
|
1280
|
+
"DashboardRoleGrant",
|
|
1281
|
+
back_populates="role",
|
|
1282
|
+
cascade="all, delete-orphan",
|
|
1283
|
+
passive_deletes=True,
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
class DashboardRoleGrant(Base):
|
|
1288
|
+
"""One (permission, scope) grant of a custom dashboard role."""
|
|
1289
|
+
|
|
1290
|
+
__tablename__ = "dashboard_role_grants"
|
|
1291
|
+
|
|
1292
|
+
role_id: Mapped[str] = mapped_column(
|
|
1293
|
+
String,
|
|
1294
|
+
ForeignKey("dashboard_roles.id", ondelete="CASCADE"),
|
|
1295
|
+
primary_key=True,
|
|
1296
|
+
)
|
|
1297
|
+
permission: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
1298
|
+
scope: Mapped[str] = mapped_column(String(8), nullable=False)
|
|
1299
|
+
|
|
1300
|
+
role: Mapped["DashboardRoleRecord"] = relationship("DashboardRoleRecord", back_populates="grants")
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
class DashboardSettings(Base):
|
|
1304
|
+
__tablename__ = "dashboard_settings"
|
|
1305
|
+
|
|
1306
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
|
|
1307
|
+
sticky_threads_enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
1308
|
+
upstream_stream_transport: Mapped[str] = mapped_column(
|
|
1309
|
+
String,
|
|
1310
|
+
default="auto",
|
|
1311
|
+
server_default=text("'auto'"),
|
|
1312
|
+
nullable=False,
|
|
1313
|
+
)
|
|
1314
|
+
prohibit_fast_mode: Mapped[bool] = mapped_column(
|
|
1315
|
+
Boolean,
|
|
1316
|
+
default=False,
|
|
1317
|
+
server_default=false(),
|
|
1318
|
+
nullable=False,
|
|
1319
|
+
)
|
|
1320
|
+
http_downstream_transport_policy: Mapped[str] = mapped_column(
|
|
1321
|
+
String,
|
|
1322
|
+
default="smart",
|
|
1323
|
+
server_default=text("'smart'"),
|
|
1324
|
+
nullable=False,
|
|
1325
|
+
)
|
|
1326
|
+
# T3, tri-state: NULL inherits the environment value and then the ``shared``
|
|
1327
|
+
# code default. Never seeded from the environment (configuration-tiers,
|
|
1328
|
+
# "Environment values are fallbacks, never seeds").
|
|
1329
|
+
thread_cache_identity_mode: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1330
|
+
proxy_account_response_create_limit: Mapped[int | None] = mapped_column(
|
|
1331
|
+
Integer,
|
|
1332
|
+
nullable=True,
|
|
1333
|
+
)
|
|
1334
|
+
proxy_account_stream_limit: Mapped[int | None] = mapped_column(
|
|
1335
|
+
Integer,
|
|
1336
|
+
nullable=True,
|
|
1337
|
+
)
|
|
1338
|
+
proxy_account_stream_recovery_reserve: Mapped[int | None] = mapped_column(
|
|
1339
|
+
Integer,
|
|
1340
|
+
nullable=True,
|
|
1341
|
+
)
|
|
1342
|
+
proxy_api_key_fair_share_congestion_threshold_pct: Mapped[int | None] = mapped_column(
|
|
1343
|
+
Integer,
|
|
1344
|
+
nullable=True,
|
|
1345
|
+
)
|
|
1346
|
+
# C2-1 timeouts: dashboard-managed upstream timeouts and request budgets.
|
|
1347
|
+
# NULL = inherit the ``Settings`` field (environment value or code default).
|
|
1348
|
+
upstream_connect_timeout_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1349
|
+
proxy_request_budget_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1350
|
+
compact_request_budget_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1351
|
+
transcription_request_budget_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1352
|
+
stream_idle_timeout_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1353
|
+
proxy_downstream_websocket_idle_timeout_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1354
|
+
sse_keepalive_interval_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1355
|
+
# end C2-1 timeouts
|
|
1356
|
+
# M1 stream/bridge budgets: dashboard-managed Responses stream and HTTP
|
|
1357
|
+
# session bridge request budgets. NULL = inherit the ``Settings`` field.
|
|
1358
|
+
http_responses_stream_request_budget_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1359
|
+
http_responses_session_bridge_request_budget_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1360
|
+
# end M1 stream/bridge budgets
|
|
1361
|
+
# C2-2 routing/overload: dashboard-managed routing weights and overload
|
|
1362
|
+
# isolation. NULL inherits the process environment value (or the code
|
|
1363
|
+
# default) at read time; a non-NULL value wins over the environment.
|
|
1364
|
+
proxy_overload_isolation_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
1365
|
+
proxy_account_error_rate_weighting_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1366
|
+
proxy_account_inflight_penalty_pct: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1367
|
+
proxy_account_lease_token_weight: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1368
|
+
proxy_account_lease_ttl_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1369
|
+
# end C2-2 routing/overload
|
|
1370
|
+
prefer_earlier_reset_accounts: Mapped[bool] = mapped_column(
|
|
1371
|
+
Boolean, default=True, server_default=true(), nullable=False
|
|
1372
|
+
)
|
|
1373
|
+
prefer_earlier_reset_window: Mapped[str] = mapped_column(
|
|
1374
|
+
String,
|
|
1375
|
+
default="secondary",
|
|
1376
|
+
server_default=text("'secondary'"),
|
|
1377
|
+
nullable=False,
|
|
1378
|
+
)
|
|
1379
|
+
show_reset_credit_badges: Mapped[bool] = mapped_column(
|
|
1380
|
+
Boolean,
|
|
1381
|
+
default=True,
|
|
1382
|
+
server_default=true(),
|
|
1383
|
+
nullable=False,
|
|
1384
|
+
)
|
|
1385
|
+
auto_redeem_reset_credits_before_expiry: Mapped[bool] = mapped_column(
|
|
1386
|
+
Boolean,
|
|
1387
|
+
default=False,
|
|
1388
|
+
server_default=false(),
|
|
1389
|
+
nullable=False,
|
|
1390
|
+
)
|
|
1391
|
+
show_reset_credit_expiry_badge: Mapped[bool] = mapped_column(
|
|
1392
|
+
Boolean,
|
|
1393
|
+
default=True,
|
|
1394
|
+
server_default=true(),
|
|
1395
|
+
nullable=False,
|
|
1396
|
+
)
|
|
1397
|
+
routing_strategy: Mapped[str] = mapped_column(
|
|
1398
|
+
String,
|
|
1399
|
+
default="capacity_weighted",
|
|
1400
|
+
server_default=text("'capacity_weighted'"),
|
|
1401
|
+
nullable=False,
|
|
1402
|
+
)
|
|
1403
|
+
relative_availability_power: Mapped[float] = mapped_column(
|
|
1404
|
+
Float,
|
|
1405
|
+
default=2.0,
|
|
1406
|
+
server_default=text("2.0"),
|
|
1407
|
+
nullable=False,
|
|
1408
|
+
)
|
|
1409
|
+
relative_availability_top_k: Mapped[int] = mapped_column(
|
|
1410
|
+
Integer,
|
|
1411
|
+
default=5,
|
|
1412
|
+
server_default=text("5"),
|
|
1413
|
+
nullable=False,
|
|
1414
|
+
)
|
|
1415
|
+
single_account_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1416
|
+
# Subscription-exhaustion overflow designation (#2123). No foreign key on
|
|
1417
|
+
# purpose: a dangling id means "off", mirroring single_account_id. The drain
|
|
1418
|
+
# deadline is armed when the designation is cleared and compared against
|
|
1419
|
+
# utcnow() (naive UTC) like every other dashboard_settings timestamp.
|
|
1420
|
+
openai_cache_affinity_max_age_seconds: Mapped[int] = mapped_column(
|
|
1421
|
+
Integer,
|
|
1422
|
+
default=1800,
|
|
1423
|
+
server_default=text("1800"),
|
|
1424
|
+
nullable=False,
|
|
1425
|
+
)
|
|
1426
|
+
dashboard_session_ttl_seconds: Mapped[int] = mapped_column(
|
|
1427
|
+
Integer,
|
|
1428
|
+
default=DEFAULT_DASHBOARD_SESSION_TTL_SECONDS,
|
|
1429
|
+
server_default=text(str(DEFAULT_DASHBOARD_SESSION_TTL_SECONDS)),
|
|
1430
|
+
nullable=False,
|
|
1431
|
+
)
|
|
1432
|
+
import_without_overwrite: Mapped[bool] = mapped_column(
|
|
1433
|
+
Boolean,
|
|
1434
|
+
default=True,
|
|
1435
|
+
server_default=true(),
|
|
1436
|
+
nullable=False,
|
|
1437
|
+
)
|
|
1438
|
+
totp_required_on_login: Mapped[bool] = mapped_column(
|
|
1439
|
+
Boolean,
|
|
1440
|
+
default=False,
|
|
1441
|
+
nullable=False,
|
|
1442
|
+
)
|
|
1443
|
+
# D9: TOTP required for admin-level accounts only (admin preset or a custom
|
|
1444
|
+
# role holding a privileged permission); independent of the global toggle.
|
|
1445
|
+
totp_required_for_admin_role: Mapped[bool] = mapped_column(
|
|
1446
|
+
Boolean,
|
|
1447
|
+
default=False,
|
|
1448
|
+
server_default=false(),
|
|
1449
|
+
nullable=False,
|
|
1450
|
+
)
|
|
1451
|
+
# PLAN §4.6: who may still use the local password form. Deliberately has no
|
|
1452
|
+
# environment variable -- a redeploy must not silently re-open local
|
|
1453
|
+
# sign-in a company closed. Tightening it is gated on a qualifying
|
|
1454
|
+
# break-glass account; the host CLI is the way back.
|
|
1455
|
+
local_login_policy: Mapped[str] = mapped_column(
|
|
1456
|
+
String(32),
|
|
1457
|
+
default=LocalLoginPolicy.ENABLED.value,
|
|
1458
|
+
server_default=text(f"'{LocalLoginPolicy.ENABLED.value}'"),
|
|
1459
|
+
nullable=False,
|
|
1460
|
+
)
|
|
1461
|
+
guest_access_enabled: Mapped[bool] = mapped_column(
|
|
1462
|
+
Boolean,
|
|
1463
|
+
default=False,
|
|
1464
|
+
server_default=false(),
|
|
1465
|
+
nullable=False,
|
|
1466
|
+
)
|
|
1467
|
+
guest_password_hash: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
1468
|
+
# Bumped whenever guest credentials or guest access change so every
|
|
1469
|
+
# outstanding guest session cookie (which carries the generation it was
|
|
1470
|
+
# issued under) stops validating. Sessions are stateless Fernet cookies, so
|
|
1471
|
+
# this counter is the only server-side revocation handle for guests.
|
|
1472
|
+
guest_session_generation: Mapped[int] = mapped_column(
|
|
1473
|
+
Integer,
|
|
1474
|
+
default=0,
|
|
1475
|
+
server_default=text("0"),
|
|
1476
|
+
nullable=False,
|
|
1477
|
+
)
|
|
1478
|
+
bootstrap_token_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
1479
|
+
bootstrap_token_hash: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
1480
|
+
api_key_auth_enabled: Mapped[bool] = mapped_column(
|
|
1481
|
+
Boolean,
|
|
1482
|
+
default=False,
|
|
1483
|
+
nullable=False,
|
|
1484
|
+
)
|
|
1485
|
+
hide_upstream_quota_from_api_keys: Mapped[bool] = mapped_column(
|
|
1486
|
+
Boolean,
|
|
1487
|
+
default=False,
|
|
1488
|
+
server_default=false(),
|
|
1489
|
+
nullable=False,
|
|
1490
|
+
)
|
|
1491
|
+
telemetry_consent: Mapped[str] = mapped_column(
|
|
1492
|
+
String(16),
|
|
1493
|
+
default="undecided",
|
|
1494
|
+
server_default=text("'undecided'"),
|
|
1495
|
+
nullable=False,
|
|
1496
|
+
)
|
|
1497
|
+
telemetry_instance_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
1498
|
+
telemetry_private_key_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
1499
|
+
http_responses_session_bridge_prompt_cache_idle_ttl_seconds: Mapped[int] = mapped_column(
|
|
1500
|
+
Integer,
|
|
1501
|
+
default=3600,
|
|
1502
|
+
server_default=text("3600"),
|
|
1503
|
+
nullable=False,
|
|
1504
|
+
)
|
|
1505
|
+
http_responses_session_bridge_gateway_safe_mode: Mapped[bool] = mapped_column(
|
|
1506
|
+
Boolean,
|
|
1507
|
+
default=False,
|
|
1508
|
+
server_default=false(),
|
|
1509
|
+
nullable=False,
|
|
1510
|
+
)
|
|
1511
|
+
# M3 codex prewarm: dashboard-managed Codex HTTP-bridge session prewarm.
|
|
1512
|
+
# NULL inherits the deprecated ``CODEX_LB_*`` env alias (then the code
|
|
1513
|
+
# default, off); a non-NULL value is dashboard-owned.
|
|
1514
|
+
http_responses_session_bridge_codex_prewarm_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1515
|
+
# end M3 codex prewarm
|
|
1516
|
+
upstream_proxy_routing_enabled: Mapped[bool] = mapped_column(
|
|
1517
|
+
Boolean,
|
|
1518
|
+
default=False,
|
|
1519
|
+
server_default=false(),
|
|
1520
|
+
nullable=False,
|
|
1521
|
+
)
|
|
1522
|
+
upstream_proxy_default_pool_id: Mapped[str | None] = mapped_column(
|
|
1523
|
+
String,
|
|
1524
|
+
ForeignKey("proxy_pools.id", ondelete="SET NULL"),
|
|
1525
|
+
nullable=True,
|
|
1526
|
+
)
|
|
1527
|
+
sticky_reallocation_budget_threshold_pct: Mapped[float] = mapped_column(
|
|
1528
|
+
Float,
|
|
1529
|
+
default=95.0,
|
|
1530
|
+
server_default=text("95.0"),
|
|
1531
|
+
nullable=False,
|
|
1532
|
+
)
|
|
1533
|
+
sticky_reallocation_primary_budget_threshold_pct: Mapped[float] = mapped_column(
|
|
1534
|
+
Float,
|
|
1535
|
+
default=95.0,
|
|
1536
|
+
server_default=text("95.0"),
|
|
1537
|
+
nullable=False,
|
|
1538
|
+
)
|
|
1539
|
+
sticky_reallocation_secondary_budget_threshold_pct: Mapped[float] = mapped_column(
|
|
1540
|
+
Float,
|
|
1541
|
+
default=100.0,
|
|
1542
|
+
server_default=text("100.0"),
|
|
1543
|
+
nullable=False,
|
|
1544
|
+
)
|
|
1545
|
+
additional_quota_routing_policies_json: Mapped[str] = mapped_column(
|
|
1546
|
+
Text,
|
|
1547
|
+
default="{}",
|
|
1548
|
+
server_default=text("'{}'"),
|
|
1549
|
+
nullable=False,
|
|
1550
|
+
)
|
|
1551
|
+
limit_warmup_enabled: Mapped[bool] = mapped_column(
|
|
1552
|
+
Boolean,
|
|
1553
|
+
default=False,
|
|
1554
|
+
server_default=false(),
|
|
1555
|
+
nullable=False,
|
|
1556
|
+
)
|
|
1557
|
+
limit_warmup_windows: Mapped[str] = mapped_column(
|
|
1558
|
+
String,
|
|
1559
|
+
default="both",
|
|
1560
|
+
server_default=text("'both'"),
|
|
1561
|
+
nullable=False,
|
|
1562
|
+
)
|
|
1563
|
+
limit_warmup_model: Mapped[str] = mapped_column(
|
|
1564
|
+
String,
|
|
1565
|
+
default="auto",
|
|
1566
|
+
server_default=text("'auto'"),
|
|
1567
|
+
nullable=False,
|
|
1568
|
+
)
|
|
1569
|
+
limit_warmup_prompt: Mapped[str] = mapped_column(
|
|
1570
|
+
Text,
|
|
1571
|
+
default="Say OK.",
|
|
1572
|
+
server_default=text("'Say OK.'"),
|
|
1573
|
+
nullable=False,
|
|
1574
|
+
)
|
|
1575
|
+
limit_warmup_cooldown_seconds: Mapped[int] = mapped_column(
|
|
1576
|
+
Integer,
|
|
1577
|
+
default=3600,
|
|
1578
|
+
server_default=text("3600"),
|
|
1579
|
+
nullable=False,
|
|
1580
|
+
)
|
|
1581
|
+
limit_warmup_exhausted_threshold_percent: Mapped[float] = mapped_column(
|
|
1582
|
+
Float,
|
|
1583
|
+
default=99.0,
|
|
1584
|
+
server_default=text("99.0"),
|
|
1585
|
+
nullable=False,
|
|
1586
|
+
)
|
|
1587
|
+
limit_warmup_idle_threshold_percent: Mapped[float] = mapped_column(
|
|
1588
|
+
Float,
|
|
1589
|
+
default=1.0,
|
|
1590
|
+
server_default=text("1.0"),
|
|
1591
|
+
nullable=False,
|
|
1592
|
+
)
|
|
1593
|
+
limit_warmup_min_available_percent: Mapped[float] = mapped_column(
|
|
1594
|
+
Float,
|
|
1595
|
+
default=100.0,
|
|
1596
|
+
server_default=text("100.0"),
|
|
1597
|
+
)
|
|
1598
|
+
weekly_pace_working_days: Mapped[str] = mapped_column(
|
|
1599
|
+
String,
|
|
1600
|
+
default="0,1,2,3,4,5,6",
|
|
1601
|
+
server_default=text("'0,1,2,3,4,5,6'"),
|
|
1602
|
+
nullable=False,
|
|
1603
|
+
)
|
|
1604
|
+
weekly_pace_smoothing_minutes: Mapped[int] = mapped_column(
|
|
1605
|
+
Integer,
|
|
1606
|
+
default=30,
|
|
1607
|
+
server_default=text("30"),
|
|
1608
|
+
nullable=False,
|
|
1609
|
+
)
|
|
1610
|
+
limit_warmup_staggered_idle_enabled: Mapped[bool] = mapped_column(
|
|
1611
|
+
Boolean,
|
|
1612
|
+
default=False,
|
|
1613
|
+
server_default=false(),
|
|
1614
|
+
nullable=False,
|
|
1615
|
+
)
|
|
1616
|
+
warmup_model: Mapped[str] = mapped_column(
|
|
1617
|
+
String,
|
|
1618
|
+
default="gpt-5.4-mini",
|
|
1619
|
+
server_default=text("'gpt-5.4-mini'"),
|
|
1620
|
+
nullable=False,
|
|
1621
|
+
)
|
|
1622
|
+
additional_quota_routing_policies_json: Mapped[str] = mapped_column(
|
|
1623
|
+
Text,
|
|
1624
|
+
default="{}",
|
|
1625
|
+
server_default=text("'{}'"),
|
|
1626
|
+
nullable=False,
|
|
1627
|
+
)
|
|
1628
|
+
# Data retention windows in days; NULL = never set from the dashboard
|
|
1629
|
+
# (treated as disabled), 0 = explicitly disabled.
|
|
1630
|
+
request_log_retention_days: Mapped[int | None] = mapped_column(
|
|
1631
|
+
Integer,
|
|
1632
|
+
nullable=True,
|
|
1633
|
+
)
|
|
1634
|
+
usage_history_retention_days: Mapped[int | None] = mapped_column(
|
|
1635
|
+
Integer,
|
|
1636
|
+
nullable=True,
|
|
1637
|
+
)
|
|
1638
|
+
# C2-3 resilience toggles: NULL inherits the deprecated ``CODEX_LB_*`` env
|
|
1639
|
+
# alias (then the code default); a non-NULL value is dashboard-owned.
|
|
1640
|
+
soft_drain_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1641
|
+
deterministic_failover_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1642
|
+
circuit_breaker_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1643
|
+
# M2 background jobs: NULL inherits the deprecated ``CODEX_LB_*`` env alias
|
|
1644
|
+
# (then the code default); schedulers read the value at every cycle entry.
|
|
1645
|
+
auth_guardian_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1646
|
+
automations_scheduler_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1647
|
+
rate_limit_reset_credits_refresh_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1648
|
+
# end M2 background jobs
|
|
1649
|
+
# M5 conversation archive: NULL inherits the deprecated
|
|
1650
|
+
# ``CODEX_LB_CONVERSATION_ARCHIVE_ENABLED`` env alias (then the code
|
|
1651
|
+
# default, off); a non-NULL value is dashboard-owned.
|
|
1652
|
+
conversation_archive_enabled: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
1653
|
+
# end M5 conversation archive
|
|
1654
|
+
# R2 spool retention: how long the durable HTTP-bridge operation spool --
|
|
1655
|
+
# raw request payloads and their spooled response events -- is kept before
|
|
1656
|
+
# the retention sweep deletes it. NULL inherits the deprecated
|
|
1657
|
+
# ``CODEX_LB_HTTP_RESPONSES_SESSION_BRIDGE_OPERATION_SPOOL_RETENTION_SECONDS``
|
|
1658
|
+
# env alias (then the code default, 7 days); a non-NULL value is
|
|
1659
|
+
# dashboard-owned and must stay at or above the replay floor.
|
|
1660
|
+
http_responses_session_bridge_operation_spool_retention_seconds: Mapped[float | None] = mapped_column(
|
|
1661
|
+
Float, nullable=True
|
|
1662
|
+
)
|
|
1663
|
+
# end R2 spool retention
|
|
1664
|
+
version: Mapped[int] = mapped_column(
|
|
1665
|
+
Integer,
|
|
1666
|
+
default=1,
|
|
1667
|
+
server_default=text("1"),
|
|
1668
|
+
nullable=False,
|
|
1669
|
+
)
|
|
1670
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1671
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1672
|
+
DateTime,
|
|
1673
|
+
server_default=func.now(),
|
|
1674
|
+
onupdate=func.now(),
|
|
1675
|
+
nullable=False,
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1678
|
+
__mapper_args__ = {"version_id_col": version}
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
# M4 model catalogue: dashboard-managed per-model context window overrides.
|
|
1682
|
+
class ModelContextWindowOverride(Base):
|
|
1683
|
+
"""One dashboard-stored context window override for a model slug.
|
|
1684
|
+
|
|
1685
|
+
A row wins over the ``CODEX_LB_MODEL_CONTEXT_WINDOW_OVERRIDES`` entry for
|
|
1686
|
+
the same slug; slugs without a row inherit the environment entry (or have
|
|
1687
|
+
no override). The migration never copies the environment into rows.
|
|
1688
|
+
"""
|
|
1689
|
+
|
|
1690
|
+
__tablename__ = "model_context_window_overrides"
|
|
1691
|
+
|
|
1692
|
+
slug: Mapped[str] = mapped_column(String, primary_key=True)
|
|
1693
|
+
context_window: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
1694
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1695
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1696
|
+
DateTime,
|
|
1697
|
+
server_default=func.now(),
|
|
1698
|
+
onupdate=func.now(),
|
|
1699
|
+
nullable=False,
|
|
1700
|
+
)
|
|
1701
|
+
|
|
1702
|
+
|
|
1703
|
+
# end M4 model catalogue
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
class RuntimeSentinel(Base):
|
|
1707
|
+
"""Cross-replica consistency sentinels stamped into the shared database.
|
|
1708
|
+
|
|
1709
|
+
Each row records a value every replica must agree on (for example the
|
|
1710
|
+
encryption-key fingerprint). Rows are written with an atomic
|
|
1711
|
+
insert-if-absent so the first replica to boot wins and later replicas
|
|
1712
|
+
verify against the stored value.
|
|
1713
|
+
"""
|
|
1714
|
+
|
|
1715
|
+
__tablename__ = "runtime_sentinels"
|
|
1716
|
+
|
|
1717
|
+
name: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
1718
|
+
value: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
1719
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1720
|
+
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
class ApiFirewallAllowlist(Base):
|
|
1724
|
+
__tablename__ = "api_firewall_allowlist"
|
|
1725
|
+
|
|
1726
|
+
ip_address: Mapped[str] = mapped_column(String, primary_key=True)
|
|
1727
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
class ApiKey(Base):
|
|
1731
|
+
__tablename__ = "api_keys"
|
|
1732
|
+
__table_args__ = (
|
|
1733
|
+
CheckConstraint(
|
|
1734
|
+
"allowed_reasoning_efforts IS NULL OR enforced_reasoning_effort IS NULL",
|
|
1735
|
+
name="ck_api_keys_reasoning_policy_exclusive",
|
|
1736
|
+
),
|
|
1737
|
+
)
|
|
1738
|
+
|
|
1739
|
+
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
1740
|
+
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
1741
|
+
key_hash: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
|
1742
|
+
key_prefix: Mapped[str] = mapped_column(String, nullable=False)
|
|
1743
|
+
allowed_models: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
1744
|
+
apply_to_codex_model: Mapped[bool] = mapped_column(
|
|
1745
|
+
Boolean,
|
|
1746
|
+
default=False,
|
|
1747
|
+
server_default=false(),
|
|
1748
|
+
nullable=False,
|
|
1749
|
+
)
|
|
1750
|
+
enforced_model: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1751
|
+
enforced_reasoning_effort: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1752
|
+
allowed_reasoning_efforts: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
1753
|
+
enforced_service_tier: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1754
|
+
traffic_class: Mapped[str] = mapped_column(
|
|
1755
|
+
String,
|
|
1756
|
+
default="foreground",
|
|
1757
|
+
server_default=text("'foreground'"),
|
|
1758
|
+
nullable=False,
|
|
1759
|
+
)
|
|
1760
|
+
transport_policy_override: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1761
|
+
# NULL = follow the fleet (dashboard, then environment, then ``shared``).
|
|
1762
|
+
thread_cache_identity_override: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1763
|
+
account_assignment_scope_enabled: Mapped[bool] = mapped_column(
|
|
1764
|
+
Boolean,
|
|
1765
|
+
default=False,
|
|
1766
|
+
server_default=false(),
|
|
1767
|
+
nullable=False,
|
|
1768
|
+
)
|
|
1769
|
+
source_assignment_scope_enabled: Mapped[bool] = mapped_column(
|
|
1770
|
+
Boolean,
|
|
1771
|
+
default=False,
|
|
1772
|
+
server_default=false(),
|
|
1773
|
+
nullable=False,
|
|
1774
|
+
)
|
|
1775
|
+
usage_sections: Mapped[str | None] = mapped_column(
|
|
1776
|
+
Text,
|
|
1777
|
+
nullable=False,
|
|
1778
|
+
default="upstream_limits,account_pool_usage",
|
|
1779
|
+
server_default="upstream_limits,account_pool_usage",
|
|
1780
|
+
)
|
|
1781
|
+
expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
1782
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
1783
|
+
# Ownership (per-user accounts). NULL owner = shared/service key. Populated
|
|
1784
|
+
# once principals carry a user id; declared here so the schema lands once.
|
|
1785
|
+
owner_user_id: Mapped[str | None] = mapped_column(
|
|
1786
|
+
String,
|
|
1787
|
+
ForeignKey("dashboard_users.id", ondelete="SET NULL"),
|
|
1788
|
+
nullable=True,
|
|
1789
|
+
)
|
|
1790
|
+
created_by_user_id: Mapped[str | None] = mapped_column(
|
|
1791
|
+
String,
|
|
1792
|
+
ForeignKey("dashboard_users.id", ondelete="SET NULL"),
|
|
1793
|
+
nullable=True,
|
|
1794
|
+
)
|
|
1795
|
+
# Why an inactive key is inactive, so re-enabling a user restores only the
|
|
1796
|
+
# keys that were disabled because of that user (never manual blocks).
|
|
1797
|
+
deactivated_reason: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
1798
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1799
|
+
last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
1800
|
+
|
|
1801
|
+
limits: Mapped[list["ApiKeyLimit"]] = relationship(
|
|
1802
|
+
"ApiKeyLimit",
|
|
1803
|
+
back_populates="api_key",
|
|
1804
|
+
cascade="all, delete-orphan",
|
|
1805
|
+
lazy="selectin",
|
|
1806
|
+
)
|
|
1807
|
+
account_assignments: Mapped[list["ApiKeyAccountAssignment"]] = relationship(
|
|
1808
|
+
"ApiKeyAccountAssignment",
|
|
1809
|
+
back_populates="api_key",
|
|
1810
|
+
cascade="all, delete-orphan",
|
|
1811
|
+
lazy="selectin",
|
|
1812
|
+
)
|
|
1813
|
+
source_assignments: Mapped[list["ApiKeyModelSourceAssignment"]] = relationship(
|
|
1814
|
+
"ApiKeyModelSourceAssignment",
|
|
1815
|
+
back_populates="api_key",
|
|
1816
|
+
cascade="all, delete-orphan",
|
|
1817
|
+
lazy="selectin",
|
|
1818
|
+
)
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
class ApiKeyAccountAssignment(Base):
|
|
1822
|
+
__tablename__ = "api_key_accounts"
|
|
1823
|
+
|
|
1824
|
+
api_key_id: Mapped[str] = mapped_column(
|
|
1825
|
+
String,
|
|
1826
|
+
ForeignKey("api_keys.id", ondelete="CASCADE"),
|
|
1827
|
+
primary_key=True,
|
|
1828
|
+
)
|
|
1829
|
+
account_id: Mapped[str] = mapped_column(
|
|
1830
|
+
String,
|
|
1831
|
+
ForeignKey("accounts.id", ondelete="CASCADE"),
|
|
1832
|
+
primary_key=True,
|
|
1833
|
+
)
|
|
1834
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1835
|
+
|
|
1836
|
+
api_key: Mapped["ApiKey"] = relationship("ApiKey", back_populates="account_assignments")
|
|
1837
|
+
account: Mapped["Account"] = relationship("Account", back_populates="api_key_assignments")
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
class ModelSource(Base):
|
|
1841
|
+
__tablename__ = "model_sources"
|
|
1842
|
+
|
|
1843
|
+
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
1844
|
+
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
1845
|
+
kind: Mapped[str] = mapped_column(
|
|
1846
|
+
String,
|
|
1847
|
+
default="openai_compatible",
|
|
1848
|
+
server_default=text("'openai_compatible'"),
|
|
1849
|
+
nullable=False,
|
|
1850
|
+
)
|
|
1851
|
+
base_url: Mapped[str] = mapped_column(String, nullable=False)
|
|
1852
|
+
api_key_encrypted: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
|
1853
|
+
is_enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
1854
|
+
health_status: Mapped[str] = mapped_column(
|
|
1855
|
+
String,
|
|
1856
|
+
default="unknown",
|
|
1857
|
+
server_default=text("'unknown'"),
|
|
1858
|
+
nullable=False,
|
|
1859
|
+
)
|
|
1860
|
+
supports_chat_completions: Mapped[bool] = mapped_column(
|
|
1861
|
+
Boolean,
|
|
1862
|
+
default=True,
|
|
1863
|
+
server_default=true(),
|
|
1864
|
+
nullable=False,
|
|
1865
|
+
)
|
|
1866
|
+
supports_responses: Mapped[bool] = mapped_column(
|
|
1867
|
+
Boolean,
|
|
1868
|
+
default=False,
|
|
1869
|
+
server_default=false(),
|
|
1870
|
+
nullable=False,
|
|
1871
|
+
)
|
|
1872
|
+
supports_audio_transcriptions: Mapped[bool] = mapped_column(
|
|
1873
|
+
Boolean,
|
|
1874
|
+
default=False,
|
|
1875
|
+
server_default=false(),
|
|
1876
|
+
nullable=False,
|
|
1877
|
+
)
|
|
1878
|
+
supports_embeddings: Mapped[bool] = mapped_column(
|
|
1879
|
+
Boolean,
|
|
1880
|
+
default=False,
|
|
1881
|
+
server_default=false(),
|
|
1882
|
+
nullable=False,
|
|
1883
|
+
)
|
|
1884
|
+
timeout_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
1885
|
+
max_concurrency: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
1886
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1887
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1888
|
+
DateTime,
|
|
1889
|
+
server_default=func.now(),
|
|
1890
|
+
onupdate=func.now(),
|
|
1891
|
+
nullable=False,
|
|
1892
|
+
)
|
|
1893
|
+
|
|
1894
|
+
models: Mapped[list["ModelSourceModel"]] = relationship(
|
|
1895
|
+
"ModelSourceModel",
|
|
1896
|
+
back_populates="source",
|
|
1897
|
+
cascade="all, delete-orphan",
|
|
1898
|
+
lazy="selectin",
|
|
1899
|
+
)
|
|
1900
|
+
api_key_assignments: Mapped[list["ApiKeyModelSourceAssignment"]] = relationship(
|
|
1901
|
+
"ApiKeyModelSourceAssignment",
|
|
1902
|
+
back_populates="source",
|
|
1903
|
+
cascade="all, delete-orphan",
|
|
1904
|
+
)
|
|
1905
|
+
request_logs: Mapped[list["RequestLog"]] = relationship(
|
|
1906
|
+
"RequestLog",
|
|
1907
|
+
back_populates="model_source",
|
|
1908
|
+
primaryjoin="ModelSource.id == foreign(RequestLog.model_source_id)",
|
|
1909
|
+
viewonly=True,
|
|
1910
|
+
)
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
class ModelSourceModel(Base):
|
|
1914
|
+
__tablename__ = "model_source_models"
|
|
1915
|
+
__table_args__ = (UniqueConstraint("source_id", "model", name="uq_model_source_models_source_model"),)
|
|
1916
|
+
|
|
1917
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
1918
|
+
source_id: Mapped[str] = mapped_column(String, ForeignKey("model_sources.id", ondelete="CASCADE"), nullable=False)
|
|
1919
|
+
model: Mapped[str] = mapped_column(String, nullable=False)
|
|
1920
|
+
display_name: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
1921
|
+
context_window: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
1922
|
+
max_output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
1923
|
+
supports_streaming: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
1924
|
+
supports_tools: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1925
|
+
supports_vision: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false(), nullable=False)
|
|
1926
|
+
input_per_1m: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1927
|
+
cached_input_per_1m: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1928
|
+
output_per_1m: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1929
|
+
audio_per_minute: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
1930
|
+
raw_metadata_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
1931
|
+
is_enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
1932
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1933
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
1934
|
+
DateTime,
|
|
1935
|
+
server_default=func.now(),
|
|
1936
|
+
onupdate=func.now(),
|
|
1937
|
+
nullable=False,
|
|
1938
|
+
)
|
|
1939
|
+
|
|
1940
|
+
source: Mapped["ModelSource"] = relationship("ModelSource", back_populates="models")
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
class ApiKeyModelSourceAssignment(Base):
|
|
1944
|
+
__tablename__ = "api_key_model_sources"
|
|
1945
|
+
|
|
1946
|
+
api_key_id: Mapped[str] = mapped_column(
|
|
1947
|
+
String,
|
|
1948
|
+
ForeignKey("api_keys.id", ondelete="CASCADE"),
|
|
1949
|
+
primary_key=True,
|
|
1950
|
+
)
|
|
1951
|
+
source_id: Mapped[str] = mapped_column(
|
|
1952
|
+
String,
|
|
1953
|
+
ForeignKey("model_sources.id", ondelete="CASCADE"),
|
|
1954
|
+
primary_key=True,
|
|
1955
|
+
)
|
|
1956
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
1957
|
+
|
|
1958
|
+
api_key: Mapped["ApiKey"] = relationship("ApiKey", back_populates="source_assignments")
|
|
1959
|
+
source: Mapped["ModelSource"] = relationship("ModelSource", back_populates="api_key_assignments")
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
class LimitType(str, Enum):
|
|
1963
|
+
TOTAL_TOKENS = "total_tokens"
|
|
1964
|
+
INPUT_TOKENS = "input_tokens"
|
|
1965
|
+
OUTPUT_TOKENS = "output_tokens"
|
|
1966
|
+
COST_USD = "cost_usd"
|
|
1967
|
+
CREDITS = "credits"
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
class LimitWindow(str, Enum):
|
|
1971
|
+
DAILY = "daily"
|
|
1972
|
+
WEEKLY = "weekly"
|
|
1973
|
+
MONTHLY = "monthly"
|
|
1974
|
+
FIVE_HOURS = "5h"
|
|
1975
|
+
SEVEN_DAYS = "7d"
|
|
1976
|
+
|
|
1977
|
+
|
|
1978
|
+
class ApiKeyLimit(Base):
|
|
1979
|
+
__tablename__ = "api_key_limits"
|
|
1980
|
+
|
|
1981
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
1982
|
+
api_key_id: Mapped[str] = mapped_column(
|
|
1983
|
+
String,
|
|
1984
|
+
ForeignKey("api_keys.id", ondelete="CASCADE"),
|
|
1985
|
+
nullable=False,
|
|
1986
|
+
)
|
|
1987
|
+
limit_type: Mapped[LimitType] = mapped_column(
|
|
1988
|
+
SqlEnum(
|
|
1989
|
+
LimitType,
|
|
1990
|
+
name="limit_type",
|
|
1991
|
+
validate_strings=True,
|
|
1992
|
+
values_callable=_enum_values,
|
|
1993
|
+
),
|
|
1994
|
+
nullable=False,
|
|
1995
|
+
)
|
|
1996
|
+
limit_window: Mapped[LimitWindow] = mapped_column(
|
|
1997
|
+
SqlEnum(
|
|
1998
|
+
LimitWindow,
|
|
1999
|
+
name="limit_window",
|
|
2000
|
+
validate_strings=True,
|
|
2001
|
+
values_callable=_enum_values,
|
|
2002
|
+
),
|
|
2003
|
+
nullable=False,
|
|
2004
|
+
)
|
|
2005
|
+
max_value: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
2006
|
+
current_value: Mapped[int] = mapped_column(BigInteger, default=0, server_default=text("0"), nullable=False)
|
|
2007
|
+
model_filter: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2008
|
+
reset_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2009
|
+
|
|
2010
|
+
api_key: Mapped["ApiKey"] = relationship("ApiKey", back_populates="limits")
|
|
2011
|
+
|
|
2012
|
+
|
|
2013
|
+
class ApiKeyUsageReservation(Base):
|
|
2014
|
+
__tablename__ = "api_key_usage_reservations"
|
|
2015
|
+
|
|
2016
|
+
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
2017
|
+
api_key_id: Mapped[str] = mapped_column(
|
|
2018
|
+
String,
|
|
2019
|
+
ForeignKey("api_keys.id", ondelete="CASCADE"),
|
|
2020
|
+
nullable=False,
|
|
2021
|
+
)
|
|
2022
|
+
model: Mapped[str] = mapped_column(String, nullable=False)
|
|
2023
|
+
status: Mapped[str] = mapped_column(String, nullable=False, default="reserved")
|
|
2024
|
+
input_tokens: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
|
2025
|
+
output_tokens: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
|
2026
|
+
cached_input_tokens: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
|
2027
|
+
cost_microdollars: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
|
2028
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2029
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2030
|
+
DateTime,
|
|
2031
|
+
server_default=func.now(),
|
|
2032
|
+
onupdate=func.now(),
|
|
2033
|
+
nullable=False,
|
|
2034
|
+
)
|
|
2035
|
+
|
|
2036
|
+
items: Mapped[list["ApiKeyUsageReservationItem"]] = relationship(
|
|
2037
|
+
"ApiKeyUsageReservationItem",
|
|
2038
|
+
back_populates="reservation",
|
|
2039
|
+
cascade="all, delete-orphan",
|
|
2040
|
+
lazy="selectin",
|
|
2041
|
+
)
|
|
2042
|
+
|
|
2043
|
+
|
|
2044
|
+
class ApiKeyUsageReservationItem(Base):
|
|
2045
|
+
__tablename__ = "api_key_usage_reservation_items"
|
|
2046
|
+
__table_args__ = (UniqueConstraint("reservation_id", "limit_id", name="uq_reservation_limit"),)
|
|
2047
|
+
|
|
2048
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
2049
|
+
reservation_id: Mapped[str] = mapped_column(
|
|
2050
|
+
String,
|
|
2051
|
+
ForeignKey("api_key_usage_reservations.id", ondelete="CASCADE"),
|
|
2052
|
+
nullable=False,
|
|
2053
|
+
)
|
|
2054
|
+
limit_id: Mapped[int] = mapped_column(
|
|
2055
|
+
Integer,
|
|
2056
|
+
ForeignKey("api_key_limits.id", ondelete="CASCADE"),
|
|
2057
|
+
nullable=False,
|
|
2058
|
+
)
|
|
2059
|
+
limit_type: Mapped[str] = mapped_column(String, nullable=False)
|
|
2060
|
+
reserved_delta: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
2061
|
+
actual_delta: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
|
2062
|
+
expected_reset_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2063
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2064
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2065
|
+
DateTime,
|
|
2066
|
+
server_default=func.now(),
|
|
2067
|
+
onupdate=func.now(),
|
|
2068
|
+
nullable=False,
|
|
2069
|
+
)
|
|
2070
|
+
|
|
2071
|
+
reservation: Mapped[ApiKeyUsageReservation] = relationship(
|
|
2072
|
+
"ApiKeyUsageReservation",
|
|
2073
|
+
back_populates="items",
|
|
2074
|
+
)
|
|
2075
|
+
limit: Mapped[ApiKeyLimit] = relationship("ApiKeyLimit")
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
class AutomationJob(Base):
|
|
2079
|
+
__tablename__ = "automation_jobs"
|
|
2080
|
+
|
|
2081
|
+
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
2082
|
+
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
2083
|
+
enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
2084
|
+
schedule_type: Mapped[str] = mapped_column(
|
|
2085
|
+
String(32),
|
|
2086
|
+
nullable=False,
|
|
2087
|
+
default="daily",
|
|
2088
|
+
server_default=text("'daily'"),
|
|
2089
|
+
)
|
|
2090
|
+
schedule_time: Mapped[str] = mapped_column(String(5), nullable=False)
|
|
2091
|
+
schedule_timezone: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2092
|
+
schedule_days: Mapped[str] = mapped_column(
|
|
2093
|
+
String(64),
|
|
2094
|
+
nullable=False,
|
|
2095
|
+
default="mon,tue,wed,thu,fri,sat,sun",
|
|
2096
|
+
server_default=text("'mon,tue,wed,thu,fri,sat,sun'"),
|
|
2097
|
+
)
|
|
2098
|
+
schedule_threshold_minutes: Mapped[int] = mapped_column(
|
|
2099
|
+
Integer,
|
|
2100
|
+
nullable=False,
|
|
2101
|
+
default=0,
|
|
2102
|
+
server_default=text("0"),
|
|
2103
|
+
)
|
|
2104
|
+
include_paused_accounts: Mapped[bool] = mapped_column(
|
|
2105
|
+
Boolean,
|
|
2106
|
+
nullable=False,
|
|
2107
|
+
default=False,
|
|
2108
|
+
server_default=false(),
|
|
2109
|
+
)
|
|
2110
|
+
account_scope_all: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=true())
|
|
2111
|
+
model: Mapped[str] = mapped_column(String, nullable=False)
|
|
2112
|
+
reasoning_effort: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
2113
|
+
prompt: Mapped[str] = mapped_column(Text, nullable=False, default="ping", server_default=text("'ping'"))
|
|
2114
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2115
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2116
|
+
DateTime,
|
|
2117
|
+
server_default=func.now(),
|
|
2118
|
+
onupdate=func.now(),
|
|
2119
|
+
nullable=False,
|
|
2120
|
+
)
|
|
2121
|
+
|
|
2122
|
+
account_links: Mapped[list["AutomationJobAccount"]] = relationship(
|
|
2123
|
+
"AutomationJobAccount",
|
|
2124
|
+
back_populates="job",
|
|
2125
|
+
cascade="all, delete-orphan",
|
|
2126
|
+
)
|
|
2127
|
+
runs: Mapped[list["AutomationRun"]] = relationship(
|
|
2128
|
+
"AutomationRun",
|
|
2129
|
+
back_populates="job",
|
|
2130
|
+
cascade="all, delete-orphan",
|
|
2131
|
+
)
|
|
2132
|
+
run_cycles: Mapped[list["AutomationRunCycle"]] = relationship(
|
|
2133
|
+
"AutomationRunCycle",
|
|
2134
|
+
back_populates="job",
|
|
2135
|
+
cascade="all, delete-orphan",
|
|
2136
|
+
)
|
|
2137
|
+
|
|
2138
|
+
|
|
2139
|
+
class AutomationJobAccount(Base):
|
|
2140
|
+
__tablename__ = "automation_job_accounts"
|
|
2141
|
+
__table_args__ = (UniqueConstraint("job_id", "position", name="uq_automation_job_accounts_position"),)
|
|
2142
|
+
|
|
2143
|
+
job_id: Mapped[str] = mapped_column(
|
|
2144
|
+
String,
|
|
2145
|
+
ForeignKey("automation_jobs.id", ondelete="CASCADE"),
|
|
2146
|
+
primary_key=True,
|
|
2147
|
+
)
|
|
2148
|
+
account_id: Mapped[str] = mapped_column(
|
|
2149
|
+
String,
|
|
2150
|
+
ForeignKey("accounts.id", ondelete="CASCADE"),
|
|
2151
|
+
primary_key=True,
|
|
2152
|
+
)
|
|
2153
|
+
position: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2154
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2155
|
+
|
|
2156
|
+
job: Mapped[AutomationJob] = relationship("AutomationJob", back_populates="account_links")
|
|
2157
|
+
account: Mapped[Account] = relationship("Account")
|
|
2158
|
+
|
|
2159
|
+
|
|
2160
|
+
class AutomationRun(Base):
|
|
2161
|
+
__tablename__ = "automation_runs"
|
|
2162
|
+
|
|
2163
|
+
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
2164
|
+
job_id: Mapped[str] = mapped_column(
|
|
2165
|
+
String,
|
|
2166
|
+
ForeignKey("automation_jobs.id", ondelete="CASCADE"),
|
|
2167
|
+
nullable=False,
|
|
2168
|
+
)
|
|
2169
|
+
trigger: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
2170
|
+
slot_key: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
|
|
2171
|
+
cycle_key: Mapped[str] = mapped_column(String(160), nullable=False)
|
|
2172
|
+
cycle_expected_accounts: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
2173
|
+
cycle_window_end: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
2174
|
+
model: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2175
|
+
reasoning_effort: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
2176
|
+
prompt: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2177
|
+
scheduled_for: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2178
|
+
started_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2179
|
+
finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
2180
|
+
status: Mapped[str] = mapped_column(String(16), nullable=False, default="running", server_default=text("'running'"))
|
|
2181
|
+
account_id: Mapped[str | None] = mapped_column(
|
|
2182
|
+
String,
|
|
2183
|
+
ForeignKey("accounts.id", ondelete="SET NULL"),
|
|
2184
|
+
nullable=True,
|
|
2185
|
+
)
|
|
2186
|
+
error_code: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
2187
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2188
|
+
attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default=text("0"))
|
|
2189
|
+
# Compact request budget (seconds) in effect when this row was last
|
|
2190
|
+
# claimed; the stale-claim reclaim window covers the larger of this value
|
|
2191
|
+
# and the current budget so a later dashboard change cannot reclaim an
|
|
2192
|
+
# in-flight run early. NULL on rows claimed before the column existed
|
|
2193
|
+
# (they use the current budget).
|
|
2194
|
+
claim_budget_seconds: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
2195
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2196
|
+
|
|
2197
|
+
job: Mapped[AutomationJob] = relationship("AutomationJob", back_populates="runs")
|
|
2198
|
+
account: Mapped[Account | None] = relationship("Account")
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
class AutomationRunCycle(Base):
|
|
2202
|
+
__tablename__ = "automation_run_cycles"
|
|
2203
|
+
|
|
2204
|
+
cycle_key: Mapped[str] = mapped_column(String(160), primary_key=True)
|
|
2205
|
+
job_id: Mapped[str] = mapped_column(
|
|
2206
|
+
String,
|
|
2207
|
+
ForeignKey("automation_jobs.id", ondelete="CASCADE"),
|
|
2208
|
+
nullable=False,
|
|
2209
|
+
)
|
|
2210
|
+
trigger: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
2211
|
+
cycle_expected_accounts: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default=text("0"))
|
|
2212
|
+
cycle_window_end: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
2213
|
+
include_paused_accounts: Mapped[bool] = mapped_column(
|
|
2214
|
+
Boolean,
|
|
2215
|
+
nullable=False,
|
|
2216
|
+
default=False,
|
|
2217
|
+
server_default=false(),
|
|
2218
|
+
)
|
|
2219
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2220
|
+
|
|
2221
|
+
job: Mapped[AutomationJob] = relationship("AutomationJob", back_populates="run_cycles")
|
|
2222
|
+
cycle_accounts: Mapped[list["AutomationRunCycleAccount"]] = relationship(
|
|
2223
|
+
"AutomationRunCycleAccount",
|
|
2224
|
+
back_populates="cycle",
|
|
2225
|
+
cascade="all, delete-orphan",
|
|
2226
|
+
)
|
|
2227
|
+
|
|
2228
|
+
|
|
2229
|
+
class AutomationRunCycleAccount(Base):
|
|
2230
|
+
__tablename__ = "automation_run_cycle_accounts"
|
|
2231
|
+
__table_args__ = (UniqueConstraint("cycle_key", "position", name="uq_automation_run_cycle_accounts_position"),)
|
|
2232
|
+
|
|
2233
|
+
cycle_key: Mapped[str] = mapped_column(
|
|
2234
|
+
String(160),
|
|
2235
|
+
ForeignKey("automation_run_cycles.cycle_key", ondelete="CASCADE"),
|
|
2236
|
+
primary_key=True,
|
|
2237
|
+
)
|
|
2238
|
+
account_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
2239
|
+
slot_key: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
2240
|
+
position: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2241
|
+
scheduled_for: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2242
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2243
|
+
|
|
2244
|
+
cycle: Mapped[AutomationRunCycle] = relationship("AutomationRunCycle", back_populates="cycle_accounts")
|
|
2245
|
+
|
|
2246
|
+
|
|
2247
|
+
class RateLimitAttempt(Base):
|
|
2248
|
+
__tablename__ = "rate_limit_attempts"
|
|
2249
|
+
|
|
2250
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
2251
|
+
key: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
2252
|
+
attempted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=func.now(), nullable=False)
|
|
2253
|
+
type: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
class QuotaPlannerSettings(Base):
|
|
2257
|
+
__tablename__ = "quota_planner_settings"
|
|
2258
|
+
|
|
2259
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
|
|
2260
|
+
mode: Mapped[str] = mapped_column(String, default="shadow", server_default=text("'shadow'"), nullable=False)
|
|
2261
|
+
timezone: Mapped[str] = mapped_column(String, default="UTC", server_default=text("'UTC'"), nullable=False)
|
|
2262
|
+
working_days_json: Mapped[str] = mapped_column(
|
|
2263
|
+
Text,
|
|
2264
|
+
default="[0,1,2,3,4]",
|
|
2265
|
+
server_default=text("'[0,1,2,3,4]'"),
|
|
2266
|
+
nullable=False,
|
|
2267
|
+
)
|
|
2268
|
+
working_hours_start: Mapped[str] = mapped_column(
|
|
2269
|
+
String,
|
|
2270
|
+
default="09:00",
|
|
2271
|
+
server_default=text("'09:00'"),
|
|
2272
|
+
nullable=False,
|
|
2273
|
+
)
|
|
2274
|
+
working_hours_end: Mapped[str] = mapped_column(
|
|
2275
|
+
String,
|
|
2276
|
+
default="18:00",
|
|
2277
|
+
server_default=text("'18:00'"),
|
|
2278
|
+
nullable=False,
|
|
2279
|
+
)
|
|
2280
|
+
prewarm_enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
2281
|
+
prewarm_lead_minutes: Mapped[int] = mapped_column(Integer, default=300, server_default=text("300"), nullable=False)
|
|
2282
|
+
max_warmups_per_day: Mapped[int] = mapped_column(Integer, default=3, server_default=text("3"), nullable=False)
|
|
2283
|
+
max_warmup_credits_per_day: Mapped[float] = mapped_column(
|
|
2284
|
+
Float,
|
|
2285
|
+
default=0.0,
|
|
2286
|
+
server_default=text("0.0"),
|
|
2287
|
+
nullable=False,
|
|
2288
|
+
)
|
|
2289
|
+
min_expected_gain: Mapped[float] = mapped_column(Float, default=1.0, server_default=text("1.0"), nullable=False)
|
|
2290
|
+
forecast_quantile: Mapped[str] = mapped_column(String, default="p75", server_default=text("'p75'"), nullable=False)
|
|
2291
|
+
allow_synthetic_traffic: Mapped[bool] = mapped_column(
|
|
2292
|
+
Boolean,
|
|
2293
|
+
default=False,
|
|
2294
|
+
server_default=false(),
|
|
2295
|
+
nullable=False,
|
|
2296
|
+
)
|
|
2297
|
+
warmup_model_preference: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2298
|
+
dry_run: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true(), nullable=False)
|
|
2299
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2300
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2301
|
+
DateTime,
|
|
2302
|
+
server_default=func.now(),
|
|
2303
|
+
onupdate=func.now(),
|
|
2304
|
+
nullable=False,
|
|
2305
|
+
)
|
|
2306
|
+
|
|
2307
|
+
|
|
2308
|
+
class QuotaPlannerDecision(Base):
|
|
2309
|
+
__tablename__ = "quota_planner_decisions"
|
|
2310
|
+
|
|
2311
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
2312
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2313
|
+
mode: Mapped[str] = mapped_column(String, nullable=False)
|
|
2314
|
+
account_id: Mapped[str | None] = mapped_column(
|
|
2315
|
+
String,
|
|
2316
|
+
ForeignKey("accounts.id", ondelete="SET NULL"),
|
|
2317
|
+
nullable=True,
|
|
2318
|
+
)
|
|
2319
|
+
action: Mapped[str] = mapped_column(String, nullable=False)
|
|
2320
|
+
scheduled_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
2321
|
+
executed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
2322
|
+
lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
2323
|
+
score: Mapped[float] = mapped_column(Float, default=0.0, server_default=text("0.0"), nullable=False)
|
|
2324
|
+
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2325
|
+
forecast_snapshot_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
2326
|
+
state_before_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2327
|
+
state_after_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2328
|
+
status: Mapped[str] = mapped_column(String, default="planned", server_default=text("'planned'"), nullable=False)
|
|
2329
|
+
idempotency_key: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
|
2330
|
+
|
|
2331
|
+
|
|
2332
|
+
class QuotaWindowObservation(Base):
|
|
2333
|
+
__tablename__ = "quota_window_observations"
|
|
2334
|
+
|
|
2335
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
2336
|
+
account_id: Mapped[str] = mapped_column(String, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
|
2337
|
+
observed_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
|
2338
|
+
model: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2339
|
+
primary_remaining_percent: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
2340
|
+
primary_reset_at: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
2341
|
+
secondary_remaining_percent: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
2342
|
+
secondary_reset_at: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
2343
|
+
source: Mapped[str] = mapped_column(String, nullable=False)
|
|
2344
|
+
confidence: Mapped[str] = mapped_column(String, default="unknown", server_default=text("'unknown'"), nullable=False)
|
|
2345
|
+
|
|
2346
|
+
|
|
2347
|
+
class CacheInvalidation(Base):
|
|
2348
|
+
__tablename__ = "cache_invalidation"
|
|
2349
|
+
|
|
2350
|
+
namespace: Mapped[str] = mapped_column(String(50), primary_key=True)
|
|
2351
|
+
version: Mapped[int] = mapped_column(Integer, nullable=False, server_default="0")
|
|
2352
|
+
|
|
2353
|
+
|
|
2354
|
+
class ModelRegistrySnapshotRecord(Base):
|
|
2355
|
+
"""Single-row (id=1) persisted serialization of the refreshed model registry.
|
|
2356
|
+
|
|
2357
|
+
Written by the leader's model refresh cycle and loaded by every replica via
|
|
2358
|
+
the cache-invalidation bus so the catalog stays replica-coherent.
|
|
2359
|
+
"""
|
|
2360
|
+
|
|
2361
|
+
__tablename__ = "model_registry_snapshot"
|
|
2362
|
+
|
|
2363
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
|
|
2364
|
+
schema_version: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2365
|
+
content_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2366
|
+
payload: Mapped[str] = mapped_column(Text, nullable=False)
|
|
2367
|
+
refreshed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
2368
|
+
leader_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
2369
|
+
|
|
2370
|
+
|
|
2371
|
+
class AccountRefreshClaim(Base):
|
|
2372
|
+
"""Cross-replica, per-account token-refresh claim.
|
|
2373
|
+
|
|
2374
|
+
One row per account marks which claimant (replica/process) currently owns
|
|
2375
|
+
the right to run the upstream OAuth token exchange. Rows are acquired via a
|
|
2376
|
+
conditional upsert that only succeeds when no unexpired claim by another
|
|
2377
|
+
claimant exists, and carry a TTL (`claim_expires_at`) so a crashed claimant
|
|
2378
|
+
can never block refresh indefinitely. The claim is pure coordination state:
|
|
2379
|
+
it holds no token material and is deleted after the refreshed tokens are
|
|
2380
|
+
persisted.
|
|
2381
|
+
"""
|
|
2382
|
+
|
|
2383
|
+
__tablename__ = "account_refresh_claims"
|
|
2384
|
+
|
|
2385
|
+
account_id: Mapped[str] = mapped_column(
|
|
2386
|
+
String,
|
|
2387
|
+
ForeignKey("accounts.id", ondelete="CASCADE"),
|
|
2388
|
+
primary_key=True,
|
|
2389
|
+
)
|
|
2390
|
+
claimed_by: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
2391
|
+
claimed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2392
|
+
claim_expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2393
|
+
|
|
2394
|
+
|
|
2395
|
+
class AccountPlanDowngradeObservation(Base):
|
|
2396
|
+
"""Pending workspace-less paid -> free plan-downgrade evidence per account.
|
|
2397
|
+
|
|
2398
|
+
A workspace-less usage payload reporting ``free`` for a paid account is only
|
|
2399
|
+
trusted once two consecutive refreshes agree (issue #1456). The observation
|
|
2400
|
+
count lives here rather than in process memory so the sequence is coherent
|
|
2401
|
+
across replicas sharing one database: an intervening paid payload observed by
|
|
2402
|
+
any replica clears the evidence for all of them, and two ``free`` samples
|
|
2403
|
+
split across replicas still converge.
|
|
2404
|
+
|
|
2405
|
+
``credential_fingerprint`` pins the evidence to the credential lineage that
|
|
2406
|
+
produced it: a fixed-salt digest over the account's stable seat identity
|
|
2407
|
+
(workspace and principal identifiers, email, ``codex_installation_id``),
|
|
2408
|
+
deliberately not over token material -- refresh tokens rotate on every
|
|
2409
|
+
token refresh, and rotation must not read as a credential replacement.
|
|
2410
|
+
Account ids are deterministic, so a delete-and-re-import or an in-place
|
|
2411
|
+
reauthentication reuses the same id with new credentials; those replacements
|
|
2412
|
+
discard this row explicitly (the accounts repository deletes it in the same
|
|
2413
|
+
transaction that applies the fresh credentials), and the fingerprint
|
|
2414
|
+
comparison restarts the count for any remaining path that rebinds the row's
|
|
2415
|
+
seat identity.
|
|
2416
|
+
|
|
2417
|
+
The row holds no secrets: a count, a plan value, timestamps, and the
|
|
2418
|
+
non-reversible identity digest, stored outside the encrypted token columns.
|
|
2419
|
+
It is deleted as soon as the downgrade is applied or the evidence is
|
|
2420
|
+
invalidated. ``ondelete="CASCADE"`` drops it with the account.
|
|
2421
|
+
"""
|
|
2422
|
+
|
|
2423
|
+
__tablename__ = "account_plan_downgrade_observations"
|
|
2424
|
+
|
|
2425
|
+
account_id: Mapped[str] = mapped_column(
|
|
2426
|
+
String,
|
|
2427
|
+
ForeignKey("accounts.id", ondelete="CASCADE"),
|
|
2428
|
+
primary_key=True,
|
|
2429
|
+
)
|
|
2430
|
+
observations: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2431
|
+
credential_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2432
|
+
observed_plan_type: Mapped[str] = mapped_column(String, nullable=False)
|
|
2433
|
+
first_observed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2434
|
+
last_observed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
2435
|
+
|
|
2436
|
+
|
|
2437
|
+
class BridgeRingMember(Base):
|
|
2438
|
+
__tablename__ = "bridge_ring_members"
|
|
2439
|
+
|
|
2440
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
2441
|
+
instance_id: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
|
2442
|
+
registered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now())
|
|
2443
|
+
last_heartbeat_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now())
|
|
2444
|
+
metadata_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2445
|
+
|
|
2446
|
+
|
|
2447
|
+
class HttpBridgeSessionState(str, Enum):
|
|
2448
|
+
ACTIVE = "active"
|
|
2449
|
+
DRAINING = "draining"
|
|
2450
|
+
CLOSED = "closed"
|
|
2451
|
+
|
|
2452
|
+
|
|
2453
|
+
class HttpBridgeRecoveryAttemptState(str, Enum):
|
|
2454
|
+
UNKNOWN = "unknown"
|
|
2455
|
+
REPLAYED = "replayed"
|
|
2456
|
+
|
|
2457
|
+
|
|
2458
|
+
HTTP_BRIDGE_SPOOL_FORMAT_ROWS_V1 = "rows_v1"
|
|
2459
|
+
HTTP_BRIDGE_SPOOL_FORMAT_CHUNKS_V2 = "chunks_v2"
|
|
2460
|
+
|
|
2461
|
+
# Where one dispatch of an operation stands in the two-phase terminal write.
|
|
2462
|
+
# This is deliberately separate from ``event_spool_complete``: an ordinary
|
|
2463
|
+
# operation carries an incomplete spool under a terminal ``state`` for the whole
|
|
2464
|
+
# window in which its terminal append runs, because the relay publishes the
|
|
2465
|
+
# operation state before appending the terminal transcript block.
|
|
2466
|
+
#
|
|
2467
|
+
# PENDING -> no terminal transcript outcome recorded yet; appends allowed.
|
|
2468
|
+
# APPENDED -> a terminal append committed and is awaiting fenced
|
|
2469
|
+
# finalization; further terminal appends must not rewrite the
|
|
2470
|
+
# outcome, but finalization may still mark it replayable.
|
|
2471
|
+
# SETTLED -> the terminal outcome was published without a confirmed append
|
|
2472
|
+
# (fallback settlement). The row is final and never replayable,
|
|
2473
|
+
# so both later appends and finalization are refused.
|
|
2474
|
+
HTTP_BRIDGE_TERMINAL_APPEND_PHASE_PENDING = "pending"
|
|
2475
|
+
HTTP_BRIDGE_TERMINAL_APPEND_PHASE_APPENDED = "appended"
|
|
2476
|
+
HTTP_BRIDGE_TERMINAL_APPEND_PHASE_SETTLED = "settled"
|
|
2477
|
+
|
|
2478
|
+
|
|
2479
|
+
class HttpBridgeOperationState(str, Enum):
|
|
2480
|
+
SUBMITTED = "submitted"
|
|
2481
|
+
UNKNOWN = "unknown"
|
|
2482
|
+
ACKNOWLEDGED = "acknowledged"
|
|
2483
|
+
ABANDONED = "abandoned"
|
|
2484
|
+
COMPLETED = "completed"
|
|
2485
|
+
FAILED = "failed"
|
|
2486
|
+
|
|
2487
|
+
|
|
2488
|
+
class HttpBridgeSessionRecord(Base):
|
|
2489
|
+
__tablename__ = "http_bridge_sessions"
|
|
2490
|
+
|
|
2491
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
2492
|
+
session_key_kind: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2493
|
+
session_key_value: Mapped[str] = mapped_column(Text, nullable=False)
|
|
2494
|
+
session_key_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2495
|
+
api_key_scope: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
2496
|
+
owner_instance_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
2497
|
+
owner_process_epoch: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
2498
|
+
owner_epoch: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default=text("0"))
|
|
2499
|
+
lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
2500
|
+
state: Mapped[HttpBridgeSessionState] = mapped_column(
|
|
2501
|
+
SqlEnum(
|
|
2502
|
+
HttpBridgeSessionState,
|
|
2503
|
+
name="http_bridge_session_state",
|
|
2504
|
+
validate_strings=True,
|
|
2505
|
+
values_callable=_enum_values,
|
|
2506
|
+
),
|
|
2507
|
+
default=HttpBridgeSessionState.ACTIVE,
|
|
2508
|
+
server_default=text("'active'"),
|
|
2509
|
+
nullable=False,
|
|
2510
|
+
)
|
|
2511
|
+
account_id: Mapped[str | None] = mapped_column(
|
|
2512
|
+
String, ForeignKey("accounts.id", ondelete="SET NULL"), nullable=True
|
|
2513
|
+
)
|
|
2514
|
+
model: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2515
|
+
service_tier: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2516
|
+
latest_turn_state: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2517
|
+
latest_response_id: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2518
|
+
latest_input_item_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
2519
|
+
latest_input_full_fingerprint: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
2520
|
+
latest_pending_tool_calls_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2521
|
+
# Continuity-owner retirement, mirroring sticky_sessions' pair: a non-NULL
|
|
2522
|
+
# scope retires ownership only for the matching typed source, while a
|
|
2523
|
+
# non-NULL timestamp with NULL scope retires it globally. Deleting the row
|
|
2524
|
+
# instead would be indistinguishable from "never seen" and would leave the
|
|
2525
|
+
# lookup failing closed forever; a marker says the owner was deliberately
|
|
2526
|
+
# abandoned, so picking a fresh one is authorized.
|
|
2527
|
+
continuity_abandoned_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
2528
|
+
continuity_abandonment_scope: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
2529
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
2530
|
+
DateTime(timezone=True),
|
|
2531
|
+
nullable=False,
|
|
2532
|
+
default=func.now(),
|
|
2533
|
+
server_default=func.now(),
|
|
2534
|
+
)
|
|
2535
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2536
|
+
DateTime(timezone=True),
|
|
2537
|
+
nullable=False,
|
|
2538
|
+
default=func.now(),
|
|
2539
|
+
server_default=func.now(),
|
|
2540
|
+
onupdate=func.now(),
|
|
2541
|
+
)
|
|
2542
|
+
last_seen_at: Mapped[datetime] = mapped_column(
|
|
2543
|
+
DateTime(timezone=True),
|
|
2544
|
+
nullable=False,
|
|
2545
|
+
default=func.now(),
|
|
2546
|
+
server_default=func.now(),
|
|
2547
|
+
)
|
|
2548
|
+
closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
2549
|
+
|
|
2550
|
+
aliases: Mapped[list["HttpBridgeSessionAlias"]] = relationship(
|
|
2551
|
+
"HttpBridgeSessionAlias",
|
|
2552
|
+
back_populates="session",
|
|
2553
|
+
cascade="all, delete-orphan",
|
|
2554
|
+
)
|
|
2555
|
+
|
|
2556
|
+
__table_args__ = (
|
|
2557
|
+
UniqueConstraint(
|
|
2558
|
+
"session_key_kind",
|
|
2559
|
+
"session_key_hash",
|
|
2560
|
+
"api_key_scope",
|
|
2561
|
+
name="uq_http_bridge_sessions_session_key",
|
|
2562
|
+
),
|
|
2563
|
+
)
|
|
2564
|
+
|
|
2565
|
+
|
|
2566
|
+
class HttpBridgeRecoveryAttemptRecord(Base):
|
|
2567
|
+
__tablename__ = "http_bridge_recovery_attempts"
|
|
2568
|
+
|
|
2569
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
2570
|
+
session_id: Mapped[str] = mapped_column(
|
|
2571
|
+
String(36),
|
|
2572
|
+
ForeignKey("http_bridge_sessions.id", ondelete="CASCADE"),
|
|
2573
|
+
nullable=False,
|
|
2574
|
+
)
|
|
2575
|
+
request_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2576
|
+
request_id: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
2577
|
+
account_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2578
|
+
model: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2579
|
+
replay_safe: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
|
2580
|
+
state: Mapped[HttpBridgeRecoveryAttemptState] = mapped_column(
|
|
2581
|
+
SqlEnum(
|
|
2582
|
+
HttpBridgeRecoveryAttemptState,
|
|
2583
|
+
name="http_bridge_recovery_attempt_state",
|
|
2584
|
+
validate_strings=True,
|
|
2585
|
+
values_callable=_enum_values,
|
|
2586
|
+
),
|
|
2587
|
+
default=HttpBridgeRecoveryAttemptState.UNKNOWN,
|
|
2588
|
+
server_default=text("'unknown'"),
|
|
2589
|
+
nullable=False,
|
|
2590
|
+
)
|
|
2591
|
+
response_id: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2592
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
2593
|
+
DateTime(timezone=True), nullable=False, default=func.now(), server_default=func.now()
|
|
2594
|
+
)
|
|
2595
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2596
|
+
DateTime(timezone=True), nullable=False, default=func.now(), server_default=func.now(), onupdate=func.now()
|
|
2597
|
+
)
|
|
2598
|
+
|
|
2599
|
+
__table_args__ = (
|
|
2600
|
+
UniqueConstraint(
|
|
2601
|
+
"session_id",
|
|
2602
|
+
"request_fingerprint",
|
|
2603
|
+
name="uq_http_bridge_recovery_attempts_session_fingerprint",
|
|
2604
|
+
),
|
|
2605
|
+
Index("idx_http_bridge_recovery_attempts_state", "state", "updated_at"),
|
|
2606
|
+
)
|
|
2607
|
+
|
|
2608
|
+
|
|
2609
|
+
class HttpBridgeOperationRecord(Base):
|
|
2610
|
+
"""Durable identity and outcome for a continuity-bound response.create."""
|
|
2611
|
+
|
|
2612
|
+
__tablename__ = "http_bridge_operations"
|
|
2613
|
+
|
|
2614
|
+
operation_id: Mapped[str] = mapped_column(String(80), primary_key=True)
|
|
2615
|
+
session_id: Mapped[str] = mapped_column(
|
|
2616
|
+
String(36),
|
|
2617
|
+
ForeignKey("http_bridge_sessions.id", ondelete="CASCADE"),
|
|
2618
|
+
nullable=False,
|
|
2619
|
+
)
|
|
2620
|
+
request_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2621
|
+
account_id: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2622
|
+
model: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
2623
|
+
parent_response_id: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2624
|
+
request_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2625
|
+
state: Mapped[str] = mapped_column(String(32), nullable=False, server_default=text("'submitted'"))
|
|
2626
|
+
response_id: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
2627
|
+
recovery_dispatch_count: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
2628
|
+
event_bytes: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
2629
|
+
event_spool_complete: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("false"))
|
|
2630
|
+
terminal_append_phase: Mapped[str] = mapped_column(
|
|
2631
|
+
String(16),
|
|
2632
|
+
nullable=False,
|
|
2633
|
+
default=HTTP_BRIDGE_TERMINAL_APPEND_PHASE_PENDING,
|
|
2634
|
+
server_default=text("'pending'"),
|
|
2635
|
+
)
|
|
2636
|
+
spool_format: Mapped[str] = mapped_column(
|
|
2637
|
+
String(16),
|
|
2638
|
+
nullable=False,
|
|
2639
|
+
default=HTTP_BRIDGE_SPOOL_FORMAT_ROWS_V1,
|
|
2640
|
+
server_default=text("'rows_v1'"),
|
|
2641
|
+
)
|
|
2642
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
2643
|
+
DateTime(timezone=True), nullable=False, default=func.now(), server_default=func.now()
|
|
2644
|
+
)
|
|
2645
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2646
|
+
DateTime(timezone=True), nullable=False, default=func.now(), server_default=func.now(), onupdate=func.now()
|
|
2647
|
+
)
|
|
2648
|
+
|
|
2649
|
+
__table_args__ = (
|
|
2650
|
+
UniqueConstraint(
|
|
2651
|
+
"session_id",
|
|
2652
|
+
"request_fingerprint",
|
|
2653
|
+
name="uq_http_bridge_operations_session_fingerprint",
|
|
2654
|
+
),
|
|
2655
|
+
Index(
|
|
2656
|
+
"uq_http_bridge_operations_request_fingerprint",
|
|
2657
|
+
"request_fingerprint",
|
|
2658
|
+
unique=True,
|
|
2659
|
+
),
|
|
2660
|
+
Index("idx_http_bridge_operations_session_parent_state", "session_id", "parent_response_id", "state"),
|
|
2661
|
+
Index("idx_http_bridge_operations_parent_state", "parent_response_id", "state", "updated_at"),
|
|
2662
|
+
Index("idx_http_bridge_operations_state_updated", "state", "updated_at"),
|
|
2663
|
+
)
|
|
2664
|
+
|
|
2665
|
+
|
|
2666
|
+
class HttpBridgeOperationEvent(Base):
|
|
2667
|
+
"""Replayable upstream SSE blocks for a durable bridge operation."""
|
|
2668
|
+
|
|
2669
|
+
__tablename__ = "http_bridge_operation_events"
|
|
2670
|
+
|
|
2671
|
+
event_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
2672
|
+
operation_id: Mapped[str] = mapped_column(
|
|
2673
|
+
String(80),
|
|
2674
|
+
ForeignKey("http_bridge_operations.operation_id", ondelete="CASCADE"),
|
|
2675
|
+
nullable=False,
|
|
2676
|
+
)
|
|
2677
|
+
sequence_number: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2678
|
+
event_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2679
|
+
event_text: Mapped[str] = mapped_column(Text, nullable=False)
|
|
2680
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
2681
|
+
DateTime(timezone=True), nullable=False, default=func.now(), server_default=func.now()
|
|
2682
|
+
)
|
|
2683
|
+
|
|
2684
|
+
__table_args__ = (
|
|
2685
|
+
UniqueConstraint(
|
|
2686
|
+
"operation_id",
|
|
2687
|
+
"event_fingerprint",
|
|
2688
|
+
name="uq_http_bridge_operation_events_operation_fingerprint",
|
|
2689
|
+
),
|
|
2690
|
+
Index("idx_http_bridge_operation_events_operation_sequence", "operation_id", "sequence_number"),
|
|
2691
|
+
)
|
|
2692
|
+
|
|
2693
|
+
|
|
2694
|
+
class HttpBridgeOperationEventChunk(Base):
|
|
2695
|
+
"""Compressed, ordered SSE blocks for a future chunk-format operation."""
|
|
2696
|
+
|
|
2697
|
+
__tablename__ = "http_bridge_operation_event_chunks"
|
|
2698
|
+
|
|
2699
|
+
operation_id: Mapped[str] = mapped_column(
|
|
2700
|
+
String(80),
|
|
2701
|
+
ForeignKey("http_bridge_operations.operation_id", ondelete="CASCADE"),
|
|
2702
|
+
primary_key=True,
|
|
2703
|
+
)
|
|
2704
|
+
first_sequence_number: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
2705
|
+
event_count: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2706
|
+
codec: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2707
|
+
uncompressed_bytes: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
2708
|
+
payload: Mapped[bytes] = mapped_column(LargeBinary, nullable=False)
|
|
2709
|
+
payload_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2710
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
2711
|
+
DateTime(timezone=True), nullable=False, default=func.now(), server_default=func.now()
|
|
2712
|
+
)
|
|
2713
|
+
|
|
2714
|
+
__table_args__ = (
|
|
2715
|
+
CheckConstraint("first_sequence_number > 0", name="ck_http_bridge_event_chunks_first_sequence_positive"),
|
|
2716
|
+
CheckConstraint("event_count > 0", name="ck_http_bridge_event_chunks_event_count_positive"),
|
|
2717
|
+
CheckConstraint("uncompressed_bytes >= 0", name="ck_http_bridge_event_chunks_bytes_nonnegative"),
|
|
2718
|
+
)
|
|
2719
|
+
|
|
2720
|
+
|
|
2721
|
+
class HttpBridgeSessionAlias(Base):
|
|
2722
|
+
__tablename__ = "http_bridge_session_aliases"
|
|
2723
|
+
|
|
2724
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
2725
|
+
session_id: Mapped[str] = mapped_column(
|
|
2726
|
+
String(36),
|
|
2727
|
+
ForeignKey("http_bridge_sessions.id", ondelete="CASCADE"),
|
|
2728
|
+
nullable=False,
|
|
2729
|
+
)
|
|
2730
|
+
alias_kind: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2731
|
+
alias_value: Mapped[str] = mapped_column(Text, nullable=False)
|
|
2732
|
+
alias_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
2733
|
+
api_key_scope: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
2734
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
2735
|
+
DateTime(timezone=True),
|
|
2736
|
+
nullable=False,
|
|
2737
|
+
default=func.now(),
|
|
2738
|
+
server_default=func.now(),
|
|
2739
|
+
)
|
|
2740
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
2741
|
+
DateTime(timezone=True),
|
|
2742
|
+
nullable=False,
|
|
2743
|
+
default=func.now(),
|
|
2744
|
+
server_default=func.now(),
|
|
2745
|
+
onupdate=func.now(),
|
|
2746
|
+
)
|
|
2747
|
+
|
|
2748
|
+
session: Mapped[HttpBridgeSessionRecord] = relationship(
|
|
2749
|
+
"HttpBridgeSessionRecord",
|
|
2750
|
+
back_populates="aliases",
|
|
2751
|
+
)
|
|
2752
|
+
|
|
2753
|
+
__table_args__ = (
|
|
2754
|
+
UniqueConstraint(
|
|
2755
|
+
"alias_kind",
|
|
2756
|
+
"alias_hash",
|
|
2757
|
+
"api_key_scope",
|
|
2758
|
+
name="uq_http_bridge_session_aliases_alias",
|
|
2759
|
+
),
|
|
2760
|
+
)
|
|
2761
|
+
|
|
2762
|
+
|
|
2763
|
+
class HttpBridgeRetryCircuit(Base):
|
|
2764
|
+
__tablename__ = "http_bridge_retry_circuits"
|
|
2765
|
+
|
|
2766
|
+
session_key_kind: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
2767
|
+
session_key_hash: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
2768
|
+
api_key_scope: Mapped[str] = mapped_column(String(255), primary_key=True)
|
|
2769
|
+
consecutive_failures: Mapped[int] = mapped_column(
|
|
2770
|
+
Integer,
|
|
2771
|
+
nullable=False,
|
|
2772
|
+
default=0,
|
|
2773
|
+
server_default=text("0"),
|
|
2774
|
+
)
|
|
2775
|
+
cooldown_until_epoch: Mapped[float] = mapped_column(
|
|
2776
|
+
Float,
|
|
2777
|
+
nullable=False,
|
|
2778
|
+
default=0.0,
|
|
2779
|
+
server_default=text("0"),
|
|
2780
|
+
)
|
|
2781
|
+
last_detail: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
2782
|
+
updated_at_epoch: Mapped[float] = mapped_column(Float, nullable=False)
|
|
2783
|
+
admission_generation: Mapped[int] = mapped_column(
|
|
2784
|
+
Integer,
|
|
2785
|
+
nullable=False,
|
|
2786
|
+
default=0,
|
|
2787
|
+
server_default=text("0"),
|
|
2788
|
+
)
|
|
2789
|
+
|
|
2790
|
+
|
|
2791
|
+
_PRIMARY_WINDOW_INDEX_EXPR = func.coalesce(UsageHistory.window, literal_column("'primary'"))
|
|
2792
|
+
|
|
2793
|
+
Index("idx_usage_recorded_at", UsageHistory.recorded_at)
|
|
2794
|
+
Index("idx_usage_account_time", UsageHistory.account_id, UsageHistory.recorded_at)
|
|
2795
|
+
Index(
|
|
2796
|
+
"idx_usage_window_account_time",
|
|
2797
|
+
_PRIMARY_WINDOW_INDEX_EXPR,
|
|
2798
|
+
UsageHistory.account_id,
|
|
2799
|
+
UsageHistory.recorded_at,
|
|
2800
|
+
)
|
|
2801
|
+
Index(
|
|
2802
|
+
"idx_usage_window_account_latest",
|
|
2803
|
+
_PRIMARY_WINDOW_INDEX_EXPR,
|
|
2804
|
+
UsageHistory.account_id,
|
|
2805
|
+
UsageHistory.recorded_at.desc(),
|
|
2806
|
+
UsageHistory.id.desc(),
|
|
2807
|
+
)
|
|
2808
|
+
Index(
|
|
2809
|
+
"idx_usage_window_raw_account_latest",
|
|
2810
|
+
UsageHistory.window,
|
|
2811
|
+
UsageHistory.account_id,
|
|
2812
|
+
UsageHistory.recorded_at.desc(),
|
|
2813
|
+
UsageHistory.id.desc(),
|
|
2814
|
+
)
|
|
2815
|
+
# The raw "window" column rides in the payload because the planner only
|
|
2816
|
+
# considers an index-only scan when every column referenced by the query is
|
|
2817
|
+
# returnable from the index, and the coalesce(...) expression key cannot
|
|
2818
|
+
# return its underlying raw column.
|
|
2819
|
+
Index(
|
|
2820
|
+
"idx_usage_window_account_time_covering",
|
|
2821
|
+
_PRIMARY_WINDOW_INDEX_EXPR,
|
|
2822
|
+
UsageHistory.account_id,
|
|
2823
|
+
UsageHistory.recorded_at,
|
|
2824
|
+
postgresql_include=["used_percent", "reset_at", "window_minutes", "id", "window"],
|
|
2825
|
+
)
|
|
2826
|
+
Index(
|
|
2827
|
+
"idx_usage_window_raw_account_time_covering",
|
|
2828
|
+
UsageHistory.window,
|
|
2829
|
+
UsageHistory.account_id,
|
|
2830
|
+
UsageHistory.recorded_at,
|
|
2831
|
+
postgresql_include=["used_percent", "reset_at", "window_minutes", "id"],
|
|
2832
|
+
)
|
|
2833
|
+
Index("idx_accounts_email", Account.email)
|
|
2834
|
+
# Pending-deletion queue: every replica probes ``delete_requested_at IS NOT
|
|
2835
|
+
# NULL LIMIT 1`` each worker interval and the leader orders the queue by
|
|
2836
|
+
# (delete_requested_at, id); the partial index keeps both reads off the full
|
|
2837
|
+
# accounts table and is empty in the steady state (no pending deletions).
|
|
2838
|
+
Index(
|
|
2839
|
+
"idx_accounts_delete_requested_at",
|
|
2840
|
+
Account.delete_requested_at,
|
|
2841
|
+
Account.id,
|
|
2842
|
+
postgresql_where=text("delete_requested_at IS NOT NULL"),
|
|
2843
|
+
sqlite_where=text("delete_requested_at IS NOT NULL"),
|
|
2844
|
+
)
|
|
2845
|
+
Index("idx_accounts_chatgpt_account_id", Account.chatgpt_account_id)
|
|
2846
|
+
Index("idx_api_keys_name", ApiKey.name)
|
|
2847
|
+
Index("idx_logs_account_time", RequestLog.account_id, RequestLog.requested_at)
|
|
2848
|
+
Index("idx_logs_model_source_time", RequestLog.model_source_id, RequestLog.requested_at)
|
|
2849
|
+
Index("idx_logs_api_key_time", RequestLog.api_key_id, RequestLog.requested_at.desc(), RequestLog.id.desc())
|
|
2850
|
+
Index("idx_logs_request_kind_time", RequestLog.request_kind, RequestLog.requested_at.desc(), RequestLog.id.desc())
|
|
2851
|
+
Index(
|
|
2852
|
+
"idx_logs_account_kind_deleted_latest",
|
|
2853
|
+
RequestLog.account_id,
|
|
2854
|
+
RequestLog.request_kind,
|
|
2855
|
+
RequestLog.deleted_at,
|
|
2856
|
+
RequestLog.requested_at,
|
|
2857
|
+
RequestLog.id,
|
|
2858
|
+
)
|
|
2859
|
+
Index(
|
|
2860
|
+
"idx_logs_account_request_latest",
|
|
2861
|
+
RequestLog.account_id,
|
|
2862
|
+
RequestLog.request_id,
|
|
2863
|
+
RequestLog.requested_at,
|
|
2864
|
+
RequestLog.id,
|
|
2865
|
+
)
|
|
2866
|
+
Index("idx_logs_source_requested_at", RequestLog.source, RequestLog.requested_at.desc())
|
|
2867
|
+
Index("idx_logs_requested_at_id", RequestLog.requested_at.desc(), RequestLog.id.desc())
|
|
2868
|
+
Index(
|
|
2869
|
+
"idx_logs_missing_cost",
|
|
2870
|
+
RequestLog.model_source_id,
|
|
2871
|
+
RequestLog.id,
|
|
2872
|
+
postgresql_where=text(
|
|
2873
|
+
"cost_usd IS NULL AND model_source_id IS NULL AND input_tokens IS NOT NULL "
|
|
2874
|
+
"AND (output_tokens IS NOT NULL OR reasoning_tokens IS NOT NULL) "
|
|
2875
|
+
"AND (model_source_kind IS NULL OR model_source_kind = 'subscription')"
|
|
2876
|
+
),
|
|
2877
|
+
sqlite_where=text(
|
|
2878
|
+
"cost_usd IS NULL AND model_source_id IS NULL AND input_tokens IS NOT NULL "
|
|
2879
|
+
"AND (output_tokens IS NOT NULL OR reasoning_tokens IS NOT NULL) "
|
|
2880
|
+
"AND (model_source_kind IS NULL OR model_source_kind = 'subscription')"
|
|
2881
|
+
),
|
|
2882
|
+
)
|
|
2883
|
+
Index(
|
|
2884
|
+
"idx_logs_deleted_at_requested_at_id",
|
|
2885
|
+
RequestLog.deleted_at,
|
|
2886
|
+
RequestLog.requested_at.desc(),
|
|
2887
|
+
RequestLog.id.desc(),
|
|
2888
|
+
)
|
|
2889
|
+
# Covering partial index for the dashboard usage aggregation hot path. On
|
|
2890
|
+
# PostgreSQL the INCLUDE payload lets the aggregation run as an index-only
|
|
2891
|
+
# scan without touching the heap; on SQLite it degrades to a partial index on
|
|
2892
|
+
# requested_at. Enforced via the manual drift index requirements because
|
|
2893
|
+
# partial-index reflection is not consistent across dialects.
|
|
2894
|
+
Index(
|
|
2895
|
+
"idx_logs_dash_usage_covering",
|
|
2896
|
+
RequestLog.requested_at,
|
|
2897
|
+
postgresql_include=[
|
|
2898
|
+
"account_id",
|
|
2899
|
+
"api_key_id",
|
|
2900
|
+
"model",
|
|
2901
|
+
"reasoning_effort",
|
|
2902
|
+
"request_kind",
|
|
2903
|
+
"status",
|
|
2904
|
+
"input_tokens",
|
|
2905
|
+
"cached_input_tokens",
|
|
2906
|
+
"output_tokens",
|
|
2907
|
+
"reasoning_tokens",
|
|
2908
|
+
"cost_usd",
|
|
2909
|
+
"id",
|
|
2910
|
+
],
|
|
2911
|
+
postgresql_where=text("deleted_at IS NULL"),
|
|
2912
|
+
sqlite_where=text("deleted_at IS NULL"),
|
|
2913
|
+
)
|
|
2914
|
+
Index(
|
|
2915
|
+
"idx_logs_requested_at_model_tier",
|
|
2916
|
+
RequestLog.requested_at.desc(),
|
|
2917
|
+
RequestLog.model,
|
|
2918
|
+
RequestLog.service_tier,
|
|
2919
|
+
)
|
|
2920
|
+
Index(
|
|
2921
|
+
"idx_logs_model_effort_time",
|
|
2922
|
+
RequestLog.model,
|
|
2923
|
+
RequestLog.reasoning_effort,
|
|
2924
|
+
RequestLog.requested_at.desc(),
|
|
2925
|
+
RequestLog.id.desc(),
|
|
2926
|
+
)
|
|
2927
|
+
Index(
|
|
2928
|
+
"idx_logs_status_error_time",
|
|
2929
|
+
RequestLog.status,
|
|
2930
|
+
RequestLog.error_code,
|
|
2931
|
+
RequestLog.requested_at.desc(),
|
|
2932
|
+
RequestLog.id.desc(),
|
|
2933
|
+
)
|
|
2934
|
+
# Live-row partial indexes for the unfiltered request-log facet skip scan
|
|
2935
|
+
# (recursive ``facet_skip`` probes: ``min(column) WHERE deleted_at IS NULL AND
|
|
2936
|
+
# column > previous``). The predicate matches the probe so a probe never walks
|
|
2937
|
+
# the soft-deleted cohort sharing a value. Account ids need none: soft deletion
|
|
2938
|
+
# detaches account_id (NULL), which ``account_id > previous`` never walks.
|
|
2939
|
+
# Enforced via the manual drift index requirements like the covering index.
|
|
2940
|
+
Index(
|
|
2941
|
+
"idx_logs_live_api_key",
|
|
2942
|
+
RequestLog.api_key_id,
|
|
2943
|
+
postgresql_where=text("deleted_at IS NULL"),
|
|
2944
|
+
sqlite_where=text("deleted_at IS NULL"),
|
|
2945
|
+
)
|
|
2946
|
+
Index(
|
|
2947
|
+
"idx_logs_live_model_effort",
|
|
2948
|
+
RequestLog.model,
|
|
2949
|
+
RequestLog.reasoning_effort,
|
|
2950
|
+
postgresql_where=text("deleted_at IS NULL"),
|
|
2951
|
+
sqlite_where=text("deleted_at IS NULL"),
|
|
2952
|
+
)
|
|
2953
|
+
Index(
|
|
2954
|
+
"idx_logs_live_status_error",
|
|
2955
|
+
RequestLog.status,
|
|
2956
|
+
RequestLog.error_code,
|
|
2957
|
+
postgresql_where=text("deleted_at IS NULL"),
|
|
2958
|
+
sqlite_where=text("deleted_at IS NULL"),
|
|
2959
|
+
)
|
|
2960
|
+
Index(
|
|
2961
|
+
"idx_logs_request_status_api_key_time",
|
|
2962
|
+
RequestLog.request_id,
|
|
2963
|
+
RequestLog.status,
|
|
2964
|
+
RequestLog.api_key_id,
|
|
2965
|
+
RequestLog.requested_at.desc(),
|
|
2966
|
+
RequestLog.id.desc(),
|
|
2967
|
+
)
|
|
2968
|
+
Index(
|
|
2969
|
+
"idx_logs_request_status_api_key_session_time",
|
|
2970
|
+
RequestLog.request_id,
|
|
2971
|
+
RequestLog.status,
|
|
2972
|
+
RequestLog.api_key_id,
|
|
2973
|
+
RequestLog.session_id,
|
|
2974
|
+
RequestLog.requested_at.desc(),
|
|
2975
|
+
RequestLog.id.desc(),
|
|
2976
|
+
)
|
|
2977
|
+
Index("idx_sticky_account", StickySession.account_id)
|
|
2978
|
+
Index("idx_sticky_kind_updated_at", StickySession.kind, StickySession.updated_at.desc())
|
|
2979
|
+
Index("idx_api_keys_hash", ApiKey.key_hash)
|
|
2980
|
+
Index(
|
|
2981
|
+
"idx_account_limit_warmups_account_attempted", AccountLimitWarmup.account_id, AccountLimitWarmup.attempted_at.desc()
|
|
2982
|
+
)
|
|
2983
|
+
Index("idx_account_limit_warmups_status_attempted", AccountLimitWarmup.status, AccountLimitWarmup.attempted_at.desc())
|
|
2984
|
+
Index("idx_api_key_accounts_account_id", ApiKeyAccountAssignment.account_id)
|
|
2985
|
+
Index("idx_api_key_model_sources_source_id", ApiKeyModelSourceAssignment.source_id)
|
|
2986
|
+
Index("idx_model_source_models_model_enabled", ModelSourceModel.model, ModelSourceModel.is_enabled)
|
|
2987
|
+
Index("idx_api_key_limits_key_id", ApiKeyLimit.api_key_id)
|
|
2988
|
+
Index("idx_api_key_limits_reset_at", ApiKeyLimit.reset_at)
|
|
2989
|
+
Index("idx_api_key_usage_reservations_key_id", ApiKeyUsageReservation.api_key_id)
|
|
2990
|
+
Index("idx_api_key_usage_reservations_status", ApiKeyUsageReservation.status)
|
|
2991
|
+
Index(
|
|
2992
|
+
"idx_api_key_usage_reservations_status_updated_at", ApiKeyUsageReservation.status, ApiKeyUsageReservation.updated_at
|
|
2993
|
+
)
|
|
2994
|
+
Index("idx_api_key_usage_res_items_reservation_id", ApiKeyUsageReservationItem.reservation_id)
|
|
2995
|
+
Index("idx_quota_planner_decisions_status_created", QuotaPlannerDecision.status, QuotaPlannerDecision.created_at.desc())
|
|
2996
|
+
Index(
|
|
2997
|
+
"idx_quota_planner_decisions_account_created",
|
|
2998
|
+
QuotaPlannerDecision.account_id,
|
|
2999
|
+
QuotaPlannerDecision.created_at.desc(),
|
|
3000
|
+
)
|
|
3001
|
+
Index(
|
|
3002
|
+
"idx_quota_window_observations_account_time",
|
|
3003
|
+
QuotaWindowObservation.account_id,
|
|
3004
|
+
QuotaWindowObservation.observed_at.desc(),
|
|
3005
|
+
)
|
|
3006
|
+
Index("idx_automation_jobs_enabled", AutomationJob.enabled)
|
|
3007
|
+
Index("idx_automation_job_accounts_account_id", AutomationJobAccount.account_id)
|
|
3008
|
+
Index("idx_automation_runs_job_id_started_at", AutomationRun.job_id, AutomationRun.started_at)
|
|
3009
|
+
Index("idx_automation_runs_status_started_at", AutomationRun.status, AutomationRun.started_at)
|
|
3010
|
+
Index("idx_automation_runs_scheduled_for", AutomationRun.scheduled_for)
|
|
3011
|
+
Index("idx_automation_runs_cycle_key_started_at", AutomationRun.cycle_key, AutomationRun.started_at)
|
|
3012
|
+
Index(
|
|
3013
|
+
"idx_http_bridge_sessions_owner_state",
|
|
3014
|
+
HttpBridgeSessionRecord.owner_instance_id,
|
|
3015
|
+
HttpBridgeSessionRecord.owner_process_epoch,
|
|
3016
|
+
HttpBridgeSessionRecord.state,
|
|
3017
|
+
)
|
|
3018
|
+
Index("idx_http_bridge_sessions_lease", HttpBridgeSessionRecord.lease_expires_at)
|
|
3019
|
+
Index("idx_http_bridge_sessions_last_seen", HttpBridgeSessionRecord.last_seen_at.desc())
|
|
3020
|
+
Index(
|
|
3021
|
+
"idx_http_bridge_sessions_latest_turn_scope_state_seen",
|
|
3022
|
+
HttpBridgeSessionRecord.latest_turn_state,
|
|
3023
|
+
HttpBridgeSessionRecord.api_key_scope,
|
|
3024
|
+
HttpBridgeSessionRecord.state,
|
|
3025
|
+
HttpBridgeSessionRecord.last_seen_at.desc(),
|
|
3026
|
+
HttpBridgeSessionRecord.updated_at.desc(),
|
|
3027
|
+
)
|
|
3028
|
+
Index(
|
|
3029
|
+
"idx_http_bridge_sessions_latest_response_scope_state_seen",
|
|
3030
|
+
HttpBridgeSessionRecord.latest_response_id,
|
|
3031
|
+
HttpBridgeSessionRecord.api_key_scope,
|
|
3032
|
+
HttpBridgeSessionRecord.state,
|
|
3033
|
+
HttpBridgeSessionRecord.last_seen_at.desc(),
|
|
3034
|
+
HttpBridgeSessionRecord.updated_at.desc(),
|
|
3035
|
+
)
|
|
3036
|
+
Index(
|
|
3037
|
+
"idx_http_bridge_session_aliases_session_id",
|
|
3038
|
+
HttpBridgeSessionAlias.session_id,
|
|
3039
|
+
)
|
|
3040
|
+
Index(
|
|
3041
|
+
"idx_http_bridge_session_aliases_alias_kind_hash_scope",
|
|
3042
|
+
HttpBridgeSessionAlias.alias_kind,
|
|
3043
|
+
HttpBridgeSessionAlias.alias_hash,
|
|
3044
|
+
HttpBridgeSessionAlias.api_key_scope,
|
|
3045
|
+
)
|
|
3046
|
+
Index("ix_additional_usage_history_recorded_at", AdditionalUsageHistory.recorded_at)
|
|
3047
|
+
Index(
|
|
3048
|
+
"ix_additional_usage_distinct_labels",
|
|
3049
|
+
AdditionalUsageHistory.account_id,
|
|
3050
|
+
AdditionalUsageHistory.quota_key,
|
|
3051
|
+
AdditionalUsageHistory.limit_name,
|
|
3052
|
+
AdditionalUsageHistory.metered_feature,
|
|
3053
|
+
)
|
|
3054
|
+
Index(
|
|
3055
|
+
"ix_rate_limit_attempts_type_key_attempted_at",
|
|
3056
|
+
RateLimitAttempt.type,
|
|
3057
|
+
RateLimitAttempt.key,
|
|
3058
|
+
RateLimitAttempt.attempted_at,
|
|
3059
|
+
)
|
|
3060
|
+
Index(
|
|
3061
|
+
"ix_additional_usage_history_composite",
|
|
3062
|
+
AdditionalUsageHistory.account_id,
|
|
3063
|
+
AdditionalUsageHistory.quota_key,
|
|
3064
|
+
AdditionalUsageHistory.window,
|
|
3065
|
+
AdditionalUsageHistory.recorded_at,
|
|
3066
|
+
)
|
|
3067
|
+
Index(
|
|
3068
|
+
"ix_additional_usage_quota_window",
|
|
3069
|
+
AdditionalUsageHistory.quota_key,
|
|
3070
|
+
AdditionalUsageHistory.window,
|
|
3071
|
+
AdditionalUsageHistory.account_id,
|
|
3072
|
+
AdditionalUsageHistory.recorded_at,
|
|
3073
|
+
)
|
|
3074
|
+
Index(
|
|
3075
|
+
"ix_additional_usage_quota_window_latest",
|
|
3076
|
+
AdditionalUsageHistory.quota_key,
|
|
3077
|
+
AdditionalUsageHistory.window,
|
|
3078
|
+
AdditionalUsageHistory.account_id,
|
|
3079
|
+
AdditionalUsageHistory.recorded_at.desc(),
|
|
3080
|
+
AdditionalUsageHistory.used_percent.desc(),
|
|
3081
|
+
AdditionalUsageHistory.id.desc(),
|
|
3082
|
+
)
|
|
3083
|
+
Index(
|
|
3084
|
+
"ix_additional_usage_alias_limit_latest",
|
|
3085
|
+
func.lower(AdditionalUsageHistory.limit_name),
|
|
3086
|
+
AdditionalUsageHistory.window,
|
|
3087
|
+
AdditionalUsageHistory.account_id,
|
|
3088
|
+
AdditionalUsageHistory.recorded_at.desc(),
|
|
3089
|
+
AdditionalUsageHistory.used_percent.desc(),
|
|
3090
|
+
AdditionalUsageHistory.id.desc(),
|
|
3091
|
+
)
|
|
3092
|
+
Index(
|
|
3093
|
+
"ix_additional_usage_alias_feature_latest",
|
|
3094
|
+
func.lower(AdditionalUsageHistory.metered_feature),
|
|
3095
|
+
AdditionalUsageHistory.window,
|
|
3096
|
+
AdditionalUsageHistory.account_id,
|
|
3097
|
+
AdditionalUsageHistory.recorded_at.desc(),
|
|
3098
|
+
AdditionalUsageHistory.used_percent.desc(),
|
|
3099
|
+
AdditionalUsageHistory.id.desc(),
|
|
3100
|
+
)
|