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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Add dashboard_settings.local_login_policy (PLAN §4.6: who may use the local password form).
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260911_030000_add_local_login_policy
|
|
4
|
+
Revises: 20260911_020000_add_http_bridge_terminal_append_phase
|
|
5
|
+
Create Date: 2026-09-10
|
|
6
|
+
|
|
7
|
+
String, NOT NULL, server default ``enabled``: an upgraded install keeps
|
|
8
|
+
today's behaviour (every active account holding a password may sign in) until
|
|
9
|
+
an operator tightens the policy from the Settings page. No data backfill, and
|
|
10
|
+
deliberately no environment variable.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sqlalchemy as sa
|
|
16
|
+
from alembic import op
|
|
17
|
+
from sqlalchemy.engine import Connection
|
|
18
|
+
|
|
19
|
+
revision = "20260911_030000_add_local_login_policy"
|
|
20
|
+
down_revision = "20260911_020000_add_http_bridge_terminal_append_phase"
|
|
21
|
+
branch_labels = None
|
|
22
|
+
depends_on = None
|
|
23
|
+
|
|
24
|
+
_TABLE = "dashboard_settings"
|
|
25
|
+
_COLUMN = "local_login_policy"
|
|
26
|
+
_DEFAULT = "enabled"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _columns(connection: Connection) -> set[str]:
|
|
30
|
+
if not sa.inspect(connection).has_table(_TABLE):
|
|
31
|
+
return set()
|
|
32
|
+
return {column["name"] for column in sa.inspect(connection).get_columns(_TABLE)}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def upgrade() -> None:
|
|
36
|
+
columns = _columns(op.get_bind())
|
|
37
|
+
if columns and _COLUMN not in columns:
|
|
38
|
+
with op.batch_alter_table(_TABLE) as batch_op:
|
|
39
|
+
batch_op.add_column(
|
|
40
|
+
sa.Column(_COLUMN, sa.String(length=32), server_default=sa.text(f"'{_DEFAULT}'"), nullable=False)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def downgrade() -> None:
|
|
45
|
+
columns = _columns(op.get_bind())
|
|
46
|
+
if columns and _COLUMN in columns:
|
|
47
|
+
with op.batch_alter_table(_TABLE) as batch_op:
|
|
48
|
+
batch_op.drop_column(_COLUMN)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Add the thread cache identity mode setting and its per-API-key override.
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260911_010000_add_thread_cache_identity_mode
|
|
4
|
+
Revises: 20260911_010000_merge_pin_index_and_affinity_heads
|
|
5
|
+
Create Date: 2026-09-11 01:00:00.000000
|
|
6
|
+
|
|
7
|
+
Both columns are nullable and are created without a server default, so an
|
|
8
|
+
upgrade introduces no decision: NULL on ``dashboard_settings`` inherits the
|
|
9
|
+
environment value and then the ``shared`` code default, and NULL on
|
|
10
|
+
``api_keys`` means "follow the fleet". ``shared`` reproduces today's outbound
|
|
11
|
+
bytes exactly, so an upgraded install behaves identically until an operator
|
|
12
|
+
sets a value.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import sqlalchemy as sa
|
|
18
|
+
from alembic import op
|
|
19
|
+
|
|
20
|
+
revision = "20260911_040000_add_thread_cache_identity_mode"
|
|
21
|
+
down_revision = "20260911_030000_add_local_login_policy"
|
|
22
|
+
branch_labels = None
|
|
23
|
+
depends_on = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _has_column(table_name: str, column_name: str) -> bool:
|
|
27
|
+
inspector = sa.inspect(op.get_bind())
|
|
28
|
+
return column_name in {column["name"] for column in inspector.get_columns(table_name)}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def upgrade() -> None:
|
|
32
|
+
if not _has_column("dashboard_settings", "thread_cache_identity_mode"):
|
|
33
|
+
op.add_column(
|
|
34
|
+
"dashboard_settings",
|
|
35
|
+
sa.Column("thread_cache_identity_mode", sa.String(), nullable=True),
|
|
36
|
+
)
|
|
37
|
+
if not _has_column("api_keys", "thread_cache_identity_override"):
|
|
38
|
+
op.add_column(
|
|
39
|
+
"api_keys",
|
|
40
|
+
sa.Column("thread_cache_identity_override", sa.String(), nullable=True),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def downgrade() -> None:
|
|
45
|
+
if _has_column("api_keys", "thread_cache_identity_override"):
|
|
46
|
+
op.drop_column("api_keys", "thread_cache_identity_override")
|
|
47
|
+
if _has_column("dashboard_settings", "thread_cache_identity_mode"):
|
|
48
|
+
op.drop_column("dashboard_settings", "thread_cache_identity_mode")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Add continuity-abandonment markers to durable HTTP bridge sessions.
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260911_060000_add_bridge_session_continuity_abandonment
|
|
4
|
+
Revises: 20260911_030000_add_local_login_policy
|
|
5
|
+
Create Date: 2026-09-11
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision = "20260911_060000_add_bridge_session_continuity_abandonment"
|
|
14
|
+
down_revision = "20260911_030000_add_local_login_policy"
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
# Mirrors sticky_sessions' pair exactly: a scope-only marker is the new
|
|
21
|
+
# reader's tombstone, while a timestamp with NULL scope abandons globally.
|
|
22
|
+
# Both stay NULL on every existing row, so a rolling deploy keeps treating
|
|
23
|
+
# account_id as hard ownership until a writer retires a specific owner.
|
|
24
|
+
bind = op.get_bind()
|
|
25
|
+
columns = {column["name"] for column in sa.inspect(bind).get_columns("http_bridge_sessions")}
|
|
26
|
+
with op.batch_alter_table("http_bridge_sessions") as batch_op:
|
|
27
|
+
if "continuity_abandoned_at" not in columns:
|
|
28
|
+
batch_op.add_column(sa.Column("continuity_abandoned_at", sa.DateTime(timezone=True), nullable=True))
|
|
29
|
+
if "continuity_abandonment_scope" not in columns:
|
|
30
|
+
batch_op.add_column(sa.Column("continuity_abandonment_scope", sa.String(32), nullable=True))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def downgrade() -> None:
|
|
34
|
+
bind = op.get_bind()
|
|
35
|
+
columns = {column["name"] for column in sa.inspect(bind).get_columns("http_bridge_sessions")}
|
|
36
|
+
with op.batch_alter_table("http_bridge_sessions") as batch_op:
|
|
37
|
+
if "continuity_abandonment_scope" in columns:
|
|
38
|
+
batch_op.drop_column("continuity_abandonment_scope")
|
|
39
|
+
if "continuity_abandoned_at" in columns:
|
|
40
|
+
batch_op.drop_column("continuity_abandoned_at")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Merge the thread-cache identity and bridge owner-retirement histories.
|
|
2
|
+
|
|
3
|
+
Both revisions chained onto ``20260911_030000_add_local_login_policy`` and
|
|
4
|
+
merged within minutes of each other, forking the graph. Neither is wrong on
|
|
5
|
+
its own, so converge them rather than re-chaining either.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
revision = "20260912_000000_merge_thread_cache_and_bridge_retirement_heads"
|
|
9
|
+
down_revision = (
|
|
10
|
+
"20260911_040000_add_thread_cache_identity_mode",
|
|
11
|
+
"20260911_060000_add_bridge_session_continuity_abandonment",
|
|
12
|
+
)
|
|
13
|
+
branch_labels = None
|
|
14
|
+
depends_on = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def upgrade() -> None:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def downgrade() -> None:
|
|
22
|
+
pass
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Drop the legacy dashboard credential columns from ``dashboard_settings``.
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260912_010000_drop_legacy_dashboard_credentials
|
|
4
|
+
Revises: 20260912_000000_merge_thread_cache_and_bridge_retirement_heads
|
|
5
|
+
Create Date: 2026-09-12
|
|
6
|
+
|
|
7
|
+
``dashboard_users`` has been the only authority for dashboard credentials since
|
|
8
|
+
``20260909_020000_reproject_compat_admin_credentials`` copied the legacy values
|
|
9
|
+
onto the account row; the three columns below were kept one release longer as a
|
|
10
|
+
write-only projection so a replica of the previous release kept working. That
|
|
11
|
+
projection is removed in this release, so the columns are now an unowned copy of
|
|
12
|
+
a live password hash and an encrypted TOTP secret that no code path clears or
|
|
13
|
+
rotates -- which is why they go in the same change.
|
|
14
|
+
|
|
15
|
+
**This upgrade is not rolling-safe.** Every replica of an earlier release maps
|
|
16
|
+
these columns and loads the settings row as a whole entity, so its settings
|
|
17
|
+
reads (and, on release N, its credential mirror) fail the moment the columns are
|
|
18
|
+
gone. Old replicas must be stopped before this runs, and ``run_upgrade`` says so
|
|
19
|
+
out loud once. It refuses nothing: this revision descends from the reprojection,
|
|
20
|
+
so a database stamped at any older revision re-projects and only then drops, in
|
|
21
|
+
one command.
|
|
22
|
+
|
|
23
|
+
The downgrade re-creates the columns and re-projects them from the account row
|
|
24
|
+
carrying the deterministic compat id -- by id, because after this release the
|
|
25
|
+
bootstrap account's *name* is no longer a stable way to find it.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import sqlalchemy as sa
|
|
31
|
+
from alembic import op
|
|
32
|
+
from sqlalchemy.engine import Connection
|
|
33
|
+
|
|
34
|
+
revision = "20260912_010000_drop_legacy_dashboard_credentials"
|
|
35
|
+
down_revision = "20260912_000000_merge_thread_cache_and_bridge_retirement_heads"
|
|
36
|
+
branch_labels = None
|
|
37
|
+
depends_on = None
|
|
38
|
+
|
|
39
|
+
_TABLE = "dashboard_settings"
|
|
40
|
+
_USERS_TABLE = "dashboard_users"
|
|
41
|
+
_SENTINELS_TABLE = "runtime_sentinels"
|
|
42
|
+
|
|
43
|
+
#: Durable "the legacy credential layer is retired" marker, read by
|
|
44
|
+
#: ``app.db.migrate.run_upgrade`` (which freezes the same literal, because
|
|
45
|
+
#: neither side may import the other).
|
|
46
|
+
#:
|
|
47
|
+
#: The ledger cannot carry this, because the ledger is the thing that goes
|
|
48
|
+
#: wrong: a database whose ledger was lost or rewound has the chain replayed
|
|
49
|
+
#: over it, which re-creates these three columns *empty* a few revisions before
|
|
50
|
+
#: dropping them again -- and the one-shot re-projection in between reads that
|
|
51
|
+
#: emptiness as "the password was removed" and clears the account row, which by
|
|
52
|
+
#: then holds the only credential the install has. A guard inside that
|
|
53
|
+
#: re-projection cannot help: it is a published revision, so an install that
|
|
54
|
+
#: already applied it never applies it again, and editing it would give one
|
|
55
|
+
#: revision id two histories. ``run_upgrade`` sees the marker first and stamps
|
|
56
|
+
#: the ledger at this revision instead of replaying over the schema. Only this
|
|
57
|
+
#: revision writes the marker and only its downgrade removes it, so the marker
|
|
58
|
+
#: means these columns are gone whatever the ledger claims.
|
|
59
|
+
_RETIRED_SENTINEL = "dashboard_legacy_credentials_retired"
|
|
60
|
+
|
|
61
|
+
#: Frozen copy of the runtime identifier (see 20260909_010000): a migration must
|
|
62
|
+
#: not import transient application modules.
|
|
63
|
+
COMPAT_ADMIN_USER_ID = "7a4fc02d-216e-5be7-b974-bc437f23df60"
|
|
64
|
+
|
|
65
|
+
#: The three the projection wrote, and the only three that go. The guest
|
|
66
|
+
#: credential, both bootstrap-token columns, both TOTP requirement flags and
|
|
67
|
+
#: ``local_login_policy`` are live and stay.
|
|
68
|
+
_DROPPED_COLUMN_NAMES: tuple[str, ...] = (
|
|
69
|
+
"password_hash",
|
|
70
|
+
"totp_secret_encrypted",
|
|
71
|
+
"totp_last_verified_step",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _columns(connection: Connection, table: str) -> set[str]:
|
|
76
|
+
inspector = sa.inspect(connection)
|
|
77
|
+
if not inspector.has_table(table):
|
|
78
|
+
return set()
|
|
79
|
+
return {column["name"] for column in inspector.get_columns(table)}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _set_retired_marker(bind: Connection, *, retired: bool) -> None:
|
|
83
|
+
if not sa.inspect(bind).has_table(_SENTINELS_TABLE):
|
|
84
|
+
return
|
|
85
|
+
bind.execute(sa.text(f"DELETE FROM {_SENTINELS_TABLE} WHERE name = :name"), {"name": _RETIRED_SENTINEL})
|
|
86
|
+
if retired:
|
|
87
|
+
bind.execute(
|
|
88
|
+
sa.text(f"INSERT INTO {_SENTINELS_TABLE} (name, value) VALUES (:name, :value)"),
|
|
89
|
+
{"name": _RETIRED_SENTINEL, "value": revision},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def upgrade() -> None:
|
|
94
|
+
bind = op.get_bind()
|
|
95
|
+
present = _columns(bind, _TABLE)
|
|
96
|
+
doomed = [name for name in _DROPPED_COLUMN_NAMES if name in present]
|
|
97
|
+
if doomed:
|
|
98
|
+
with op.batch_alter_table(_TABLE) as batch_op:
|
|
99
|
+
for name in doomed:
|
|
100
|
+
batch_op.drop_column(name)
|
|
101
|
+
_set_retired_marker(bind, retired=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def downgrade() -> None:
|
|
105
|
+
bind = op.get_bind()
|
|
106
|
+
_set_retired_marker(bind, retired=False)
|
|
107
|
+
present = _columns(bind, _TABLE)
|
|
108
|
+
if present and not {*_DROPPED_COLUMN_NAMES} <= present:
|
|
109
|
+
with op.batch_alter_table(_TABLE) as batch_op:
|
|
110
|
+
if "password_hash" not in present:
|
|
111
|
+
batch_op.add_column(sa.Column("password_hash", sa.Text(), nullable=True))
|
|
112
|
+
if "totp_secret_encrypted" not in present:
|
|
113
|
+
batch_op.add_column(sa.Column("totp_secret_encrypted", sa.LargeBinary(), nullable=True))
|
|
114
|
+
if "totp_last_verified_step" not in present:
|
|
115
|
+
batch_op.add_column(sa.Column("totp_last_verified_step", sa.Integer(), nullable=True))
|
|
116
|
+
|
|
117
|
+
if not _columns(bind, _TABLE) or not _columns(bind, _USERS_TABLE):
|
|
118
|
+
return
|
|
119
|
+
account = bind.execute(
|
|
120
|
+
sa.text(
|
|
121
|
+
f"SELECT password_hash, totp_secret_encrypted, totp_last_verified_step FROM {_USERS_TABLE} WHERE id = :id"
|
|
122
|
+
),
|
|
123
|
+
{"id": COMPAT_ADMIN_USER_ID},
|
|
124
|
+
).first()
|
|
125
|
+
if account is None:
|
|
126
|
+
# Nothing deterministic to project from (the account was deleted, or
|
|
127
|
+
# this install never bootstrapped one): leave the columns NULL.
|
|
128
|
+
return
|
|
129
|
+
bind.execute(
|
|
130
|
+
sa.text(
|
|
131
|
+
f"UPDATE {_TABLE} SET password_hash = :password_hash, "
|
|
132
|
+
"totp_secret_encrypted = :totp_secret_encrypted, "
|
|
133
|
+
"totp_last_verified_step = :totp_last_verified_step WHERE id = 1"
|
|
134
|
+
),
|
|
135
|
+
{
|
|
136
|
+
"password_hash": account[0],
|
|
137
|
+
"totp_secret_encrypted": account[1],
|
|
138
|
+
"totp_last_verified_step": account[2],
|
|
139
|
+
},
|
|
140
|
+
)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Add the OIDC login-flow table, the provider's test-login proof and its seeded row.
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260913_000000_add_oidc_provider_flow
|
|
4
|
+
Revises: 20260912_010000_drop_legacy_dashboard_credentials
|
|
5
|
+
Create Date: 2026-09-13
|
|
6
|
+
|
|
7
|
+
The connection settings themselves need no column: ``config_encrypted`` was
|
|
8
|
+
reserved for them when the providers table landed. What is new is durable
|
|
9
|
+
*flow* state -- the callback lands on whichever replica the load balancer picks,
|
|
10
|
+
so the state, the nonce hash and the PKCE verifier cannot live in memory -- and
|
|
11
|
+
the two columns that record an admin's pre-flight test login.
|
|
12
|
+
|
|
13
|
+
The ``oidc/default`` row is seeded disabled with no role for an unmatched
|
|
14
|
+
identity, through the same insert-ignore helper the other built-in rows use, so
|
|
15
|
+
an install that never connects an identity provider is unchanged and a
|
|
16
|
+
connected one denies unknown identities until an operator says otherwise.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import sqlalchemy as sa
|
|
22
|
+
from alembic import op
|
|
23
|
+
|
|
24
|
+
from app.modules.auth_providers.seed import seed_default_auth_providers
|
|
25
|
+
|
|
26
|
+
revision = "20260913_000000_add_oidc_provider_flow"
|
|
27
|
+
down_revision = "20260912_010000_drop_legacy_dashboard_credentials"
|
|
28
|
+
branch_labels = None
|
|
29
|
+
depends_on = None
|
|
30
|
+
|
|
31
|
+
_PROVIDERS = "dashboard_auth_providers"
|
|
32
|
+
_FLOWS = "dashboard_oidc_login_flows"
|
|
33
|
+
_FLOWS_INDEX = "idx_dashboard_oidc_login_flows_expires_at"
|
|
34
|
+
_PROOF_COLUMNS: tuple[str, ...] = ("test_login_user_id", "test_login_verified_at")
|
|
35
|
+
_PROOF_FK = "fk_dashboard_auth_providers_test_login_user_id"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _provider_columns() -> tuple[sa.Column, ...]:
|
|
39
|
+
"""Fresh Column objects per call: a Column may be bound to one table only."""
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
sa.Column("id", sa.String(length=36), primary_key=True),
|
|
43
|
+
sa.Column("kind", sa.String(length=32), nullable=False),
|
|
44
|
+
sa.Column("provider_key", sa.String(length=64), nullable=False),
|
|
45
|
+
sa.Column("enabled", sa.Boolean(), server_default=sa.true(), nullable=False),
|
|
46
|
+
sa.Column("label", sa.String(length=64), nullable=False),
|
|
47
|
+
sa.Column("config_encrypted", sa.LargeBinary(), nullable=True),
|
|
48
|
+
sa.Column("unknown_identity_role_id", sa.String(length=36), nullable=True),
|
|
49
|
+
sa.Column("no_match_role_id", sa.String(length=36), nullable=True),
|
|
50
|
+
sa.Column("link_by_email", sa.Boolean(), server_default=sa.false(), nullable=False),
|
|
51
|
+
sa.Column("skip_role_sync", sa.Boolean(), server_default=sa.false(), nullable=False),
|
|
52
|
+
sa.Column("idp_mfa_enforced", sa.Boolean(), server_default=sa.false(), nullable=False),
|
|
53
|
+
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
54
|
+
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _seed_table() -> sa.Table:
|
|
59
|
+
"""Revision-pinned column list for the seed INSERT (no constraints needed to insert)."""
|
|
60
|
+
|
|
61
|
+
return sa.Table(_PROVIDERS, sa.MetaData(), *_provider_columns())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _proof_columns() -> tuple[sa.Column, ...]:
|
|
65
|
+
"""Fresh Column objects per call: a Column may be bound to one table only."""
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
sa.Column(
|
|
69
|
+
"test_login_user_id",
|
|
70
|
+
sa.String(length=36),
|
|
71
|
+
# Named because SQLite's batch mode rebuilds the table and refuses
|
|
72
|
+
# to re-add an anonymous constraint.
|
|
73
|
+
sa.ForeignKey("dashboard_users.id", ondelete="SET NULL", name=_PROOF_FK),
|
|
74
|
+
nullable=True,
|
|
75
|
+
),
|
|
76
|
+
sa.Column("test_login_verified_at", sa.DateTime(timezone=True), nullable=True),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _existing_columns(inspector: sa.Inspector, table: str) -> set[str]:
|
|
81
|
+
if not inspector.has_table(table):
|
|
82
|
+
return set()
|
|
83
|
+
return {column["name"] for column in inspector.get_columns(table)}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def upgrade() -> None:
|
|
87
|
+
bind = op.get_bind()
|
|
88
|
+
inspector = sa.inspect(bind)
|
|
89
|
+
|
|
90
|
+
existing = _existing_columns(inspector, _PROVIDERS)
|
|
91
|
+
missing = [column for column in _proof_columns() if column.name not in existing]
|
|
92
|
+
if missing:
|
|
93
|
+
with op.batch_alter_table(_PROVIDERS) as batch_op:
|
|
94
|
+
for column in missing:
|
|
95
|
+
batch_op.add_column(column)
|
|
96
|
+
|
|
97
|
+
if not inspector.has_table(_FLOWS):
|
|
98
|
+
# Constraints are declared inline: SQLite cannot add them afterwards.
|
|
99
|
+
op.create_table(
|
|
100
|
+
_FLOWS,
|
|
101
|
+
sa.Column("state_hash", sa.String(length=64), primary_key=True),
|
|
102
|
+
sa.Column("provider_id", sa.String(length=36), nullable=False),
|
|
103
|
+
sa.Column("nonce_hash", sa.String(length=64), nullable=False),
|
|
104
|
+
sa.Column("code_verifier_encrypted", sa.LargeBinary(), nullable=False),
|
|
105
|
+
sa.Column("purpose", sa.String(length=16), nullable=False),
|
|
106
|
+
sa.Column("acting_user_id", sa.String(length=36), nullable=True),
|
|
107
|
+
sa.Column("redirect_uri", sa.String(length=512), nullable=False),
|
|
108
|
+
# The connection document this flow was started against, so a
|
|
109
|
+
# completed pre-flight can only stamp the configuration it proved.
|
|
110
|
+
sa.Column("config_fingerprint", sa.String(length=64), nullable=False),
|
|
111
|
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
112
|
+
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
|
113
|
+
sa.ForeignKeyConstraint(["provider_id"], [f"{_PROVIDERS}.id"], ondelete="CASCADE"),
|
|
114
|
+
sa.ForeignKeyConstraint(["acting_user_id"], ["dashboard_users.id"], ondelete="CASCADE"),
|
|
115
|
+
)
|
|
116
|
+
op.create_index(_FLOWS_INDEX, _FLOWS, ["expires_at"])
|
|
117
|
+
|
|
118
|
+
# Outside the guards so a re-run after a partial failure still seeds, and
|
|
119
|
+
# insert-ignore so an operator's configuration survives the upgrade.
|
|
120
|
+
if inspector.has_table(_PROVIDERS):
|
|
121
|
+
seed_default_auth_providers(bind, _seed_table())
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def downgrade() -> None:
|
|
125
|
+
bind = op.get_bind()
|
|
126
|
+
inspector = sa.inspect(bind)
|
|
127
|
+
if inspector.has_table(_FLOWS):
|
|
128
|
+
op.drop_table(_FLOWS)
|
|
129
|
+
present = _existing_columns(inspector, _PROVIDERS)
|
|
130
|
+
to_drop = [name for name in _PROOF_COLUMNS if name in present]
|
|
131
|
+
if to_drop:
|
|
132
|
+
with op.batch_alter_table(_PROVIDERS) as batch_op:
|
|
133
|
+
for name in to_drop:
|
|
134
|
+
batch_op.drop_column(name)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Add the SCIM bearer-token table and the identity's pushed user name.
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260914_000000_add_scim_tokens
|
|
4
|
+
Revises: 20260913_000000_add_oidc_provider_flow
|
|
5
|
+
Create Date: 2026-09-14
|
|
6
|
+
|
|
7
|
+
``dashboard_scim_tokens`` holds only the SHA-256 digest of each secret, in a
|
|
8
|
+
unique index, so verification is an index equality and no readable copy of the
|
|
9
|
+
credential survives the response that issued it. ``provider_key`` is the
|
|
10
|
+
identity namespace the token owns; it is read from the row on every request and
|
|
11
|
+
never from the caller.
|
|
12
|
+
|
|
13
|
+
``dashboard_identities.user_name`` is nullable and written only by the SCIM
|
|
14
|
+
path: our usernames are slugs without ``@``, so a ``userName eq`` filter on the
|
|
15
|
+
identity provider's own spelling can never match one, and an identity provider
|
|
16
|
+
reconciling the resources it created would otherwise find none of them.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import sqlalchemy as sa
|
|
22
|
+
from alembic import op
|
|
23
|
+
|
|
24
|
+
revision = "20260914_000000_add_scim_tokens"
|
|
25
|
+
down_revision = "20260913_000000_add_oidc_provider_flow"
|
|
26
|
+
branch_labels = None
|
|
27
|
+
depends_on = None
|
|
28
|
+
|
|
29
|
+
_TOKENS = "dashboard_scim_tokens"
|
|
30
|
+
_IDENTITIES = "dashboard_identities"
|
|
31
|
+
_USER_NAME_COLUMN = "user_name"
|
|
32
|
+
_CREATED_BY_FK = "fk_dashboard_scim_tokens_created_by_user_id"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _existing_columns(inspector: sa.Inspector, table: str) -> set[str]:
|
|
36
|
+
if not inspector.has_table(table):
|
|
37
|
+
return set()
|
|
38
|
+
return {column["name"] for column in inspector.get_columns(table)}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def upgrade() -> None:
|
|
42
|
+
bind = op.get_bind()
|
|
43
|
+
inspector = sa.inspect(bind)
|
|
44
|
+
|
|
45
|
+
if not inspector.has_table(_TOKENS):
|
|
46
|
+
# Constraints are declared inline: SQLite cannot add them afterwards.
|
|
47
|
+
op.create_table(
|
|
48
|
+
_TOKENS,
|
|
49
|
+
sa.Column("id", sa.String(length=36), primary_key=True),
|
|
50
|
+
sa.Column("label", sa.String(length=64), nullable=False),
|
|
51
|
+
sa.Column("token_hash", sa.String(length=64), nullable=False, unique=True),
|
|
52
|
+
sa.Column("token_prefix", sa.String(length=32), nullable=False),
|
|
53
|
+
sa.Column("provider_key", sa.String(length=128), nullable=False),
|
|
54
|
+
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
55
|
+
sa.Column("created_by_user_id", sa.String(length=36), nullable=True),
|
|
56
|
+
sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True),
|
|
57
|
+
sa.Column("rotated_at", sa.DateTime(timezone=True), nullable=True),
|
|
58
|
+
sa.ForeignKeyConstraint(
|
|
59
|
+
["created_by_user_id"],
|
|
60
|
+
["dashboard_users.id"],
|
|
61
|
+
ondelete="SET NULL",
|
|
62
|
+
name=_CREATED_BY_FK,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if _USER_NAME_COLUMN not in _existing_columns(inspector, _IDENTITIES):
|
|
67
|
+
with op.batch_alter_table(_IDENTITIES) as batch_op:
|
|
68
|
+
batch_op.add_column(sa.Column(_USER_NAME_COLUMN, sa.String(length=256), nullable=True))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def downgrade() -> None:
|
|
72
|
+
bind = op.get_bind()
|
|
73
|
+
inspector = sa.inspect(bind)
|
|
74
|
+
if _USER_NAME_COLUMN in _existing_columns(inspector, _IDENTITIES):
|
|
75
|
+
with op.batch_alter_table(_IDENTITIES) as batch_op:
|
|
76
|
+
batch_op.drop_column(_USER_NAME_COLUMN)
|
|
77
|
+
if inspector.has_table(_TOKENS):
|
|
78
|
+
op.drop_table(_TOKENS)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Drop the subscription-exhaustion overflow schema.
|
|
2
|
+
|
|
3
|
+
The feature was withdrawn (#2123): a 60-body capture sweep of real Codex
|
|
4
|
+
traffic found nothing the portability predicate would ever pass, and making it
|
|
5
|
+
pass would mean translating request bodies per provider. The code went in the
|
|
6
|
+
preceding revert; this revision takes the storage with it.
|
|
7
|
+
|
|
8
|
+
Production never wrote a row -- ``model_source_pins`` was empty, both
|
|
9
|
+
``dashboard_settings`` columns were NULL -- so the upgrade cannot lose data.
|
|
10
|
+
Every step is guarded so a database missing either object (a partial dump, a
|
|
11
|
+
fresh install created from current metadata) upgrades cleanly. The downgrade
|
|
12
|
+
restores exactly what ``20260908_000000_add_subscription_overflow`` and
|
|
13
|
+
``20260911_000000_model_source_pins_kind_expires_index`` built, so the pair
|
|
14
|
+
round-trips.
|
|
15
|
+
|
|
16
|
+
**This upgrade is not rolling-safe.** Every release below this one maps both
|
|
17
|
+
``dashboard_settings`` columns and loads the settings row as a whole entity, so
|
|
18
|
+
its settings reads fail the moment the columns are gone -- and the chart's
|
|
19
|
+
migration Job is a ``pre-upgrade`` hook, which runs before the new pods roll and
|
|
20
|
+
therefore before the old ones drain. Pre-withdrawal replicas must be stopped
|
|
21
|
+
before this runs; ``docs/deployment/kubernetes.md`` carries the procedure. It
|
|
22
|
+
refuses nothing, and unlike ``20260912_010000_drop_legacy_dashboard_credentials``
|
|
23
|
+
it says nothing either: that revision's pre-DDL drain warning in
|
|
24
|
+
``app/db/migrate.py`` is written around the credential columns it protects.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from collections.abc import Sequence
|
|
28
|
+
|
|
29
|
+
import sqlalchemy as sa
|
|
30
|
+
from alembic import op
|
|
31
|
+
from sqlalchemy.engine import Connection
|
|
32
|
+
|
|
33
|
+
revision = "20260914_000000_drop_subscription_overflow_schema"
|
|
34
|
+
down_revision = "20260913_000000_add_oidc_provider_flow"
|
|
35
|
+
branch_labels = None
|
|
36
|
+
depends_on = None
|
|
37
|
+
|
|
38
|
+
_SETTINGS_TABLE = "dashboard_settings"
|
|
39
|
+
_PINS_TABLE = "model_source_pins"
|
|
40
|
+
_PURGE_INDEX = "ix_model_source_pins_purge_at"
|
|
41
|
+
_KIND_EXPIRES_INDEX = "ix_model_source_pins_kind_expires_at"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _settings_columns() -> tuple[sa.Column, ...]:
|
|
45
|
+
# Fresh Column objects per call: a Column binds to the table it is added
|
|
46
|
+
# to, so module-level instances could not be reused across one process.
|
|
47
|
+
return (
|
|
48
|
+
sa.Column("subscription_overflow_source_id", sa.String(), nullable=True),
|
|
49
|
+
sa.Column("subscription_overflow_drain_until", sa.DateTime(), nullable=True),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _column_names(connection: Connection, table_name: str) -> set[str]:
|
|
54
|
+
inspector = sa.inspect(connection)
|
|
55
|
+
if not inspector.has_table(table_name):
|
|
56
|
+
return set()
|
|
57
|
+
return {str(column["name"]) for column in inspector.get_columns(table_name) if column.get("name") is not None}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _index_names(connection: Connection, table_name: str) -> set[str]:
|
|
61
|
+
# A fresh inspector per call, because the caller may have created the table
|
|
62
|
+
# after an earlier reflection filled another inspector's cache.
|
|
63
|
+
inspector = sa.inspect(connection)
|
|
64
|
+
if not inspector.has_table(table_name):
|
|
65
|
+
return set()
|
|
66
|
+
return {str(index["name"]) for index in inspector.get_indexes(table_name) if index.get("name") is not None}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _add_columns(names: Sequence[sa.Column]) -> None:
|
|
70
|
+
with op.batch_alter_table(_SETTINGS_TABLE) as batch_op:
|
|
71
|
+
for column in names:
|
|
72
|
+
batch_op.add_column(column)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _drop_columns(names: Sequence[str]) -> None:
|
|
76
|
+
with op.batch_alter_table(_SETTINGS_TABLE) as batch_op:
|
|
77
|
+
for column_name in names:
|
|
78
|
+
batch_op.drop_column(column_name)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def upgrade() -> None:
|
|
82
|
+
bind = op.get_bind()
|
|
83
|
+
|
|
84
|
+
# DROP TABLE takes both indexes with it, so they need no separate step --
|
|
85
|
+
# and dropping them first would only widen the window where the table is
|
|
86
|
+
# unindexed. The table is empty, so the exclusive lock is instant.
|
|
87
|
+
if sa.inspect(bind).has_table(_PINS_TABLE):
|
|
88
|
+
op.drop_table(_PINS_TABLE)
|
|
89
|
+
|
|
90
|
+
existing_columns = _column_names(bind, _SETTINGS_TABLE)
|
|
91
|
+
present = [column.name for column in _settings_columns() if column.name in existing_columns]
|
|
92
|
+
if present:
|
|
93
|
+
_drop_columns(present)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def downgrade() -> None:
|
|
97
|
+
bind = op.get_bind()
|
|
98
|
+
|
|
99
|
+
existing_columns = _column_names(bind, _SETTINGS_TABLE)
|
|
100
|
+
missing = [column for column in _settings_columns() if column.name not in existing_columns]
|
|
101
|
+
if existing_columns and missing:
|
|
102
|
+
_add_columns(missing)
|
|
103
|
+
|
|
104
|
+
if not sa.inspect(bind).has_table(_PINS_TABLE):
|
|
105
|
+
op.create_table(
|
|
106
|
+
_PINS_TABLE,
|
|
107
|
+
sa.Column("pin_key", sa.String(), nullable=False),
|
|
108
|
+
sa.Column("kind", sa.String(), nullable=False),
|
|
109
|
+
# No foreign key, as in the original: rows had to outlive a deleted
|
|
110
|
+
# source for the drain window instead of cascading away.
|
|
111
|
+
sa.Column("source_id", sa.String(), nullable=False),
|
|
112
|
+
sa.Column("api_key_id", sa.String(), nullable=True),
|
|
113
|
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
114
|
+
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False),
|
|
115
|
+
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
|
116
|
+
sa.Column("purge_at", sa.DateTime(timezone=True), nullable=False),
|
|
117
|
+
sa.PrimaryKeyConstraint("pin_key"),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Each index is reflected on its own rather than ridden along with CREATE
|
|
121
|
+
# TABLE, exactly as 20260908_000000_add_subscription_overflow does it: a
|
|
122
|
+
# downgrade interrupted between the table and its indexes -- or a partial
|
|
123
|
+
# restore that already carries the table -- would otherwise land stamped at
|
|
124
|
+
# the parent with neither index, and nothing later would build them.
|
|
125
|
+
# Both tables are empty here, so the plain in-transaction build is right on
|
|
126
|
+
# either dialect; CONCURRENTLY would buy nothing and cannot run in this
|
|
127
|
+
# transaction.
|
|
128
|
+
existing_indexes = _index_names(bind, _PINS_TABLE)
|
|
129
|
+
if _PURGE_INDEX not in existing_indexes:
|
|
130
|
+
op.create_index(_PURGE_INDEX, _PINS_TABLE, ["purge_at"], unique=False)
|
|
131
|
+
if _KIND_EXPIRES_INDEX not in existing_indexes:
|
|
132
|
+
op.create_index(_KIND_EXPIRES_INDEX, _PINS_TABLE, ["kind", "expires_at"], unique=False)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Merge the SCIM token and subscription-overflow migration histories."""
|
|
2
|
+
|
|
3
|
+
revision = "20260918_000000_merge_scim_and_overflow_heads"
|
|
4
|
+
down_revision = (
|
|
5
|
+
"20260914_000000_add_scim_tokens",
|
|
6
|
+
"20260914_000000_drop_subscription_overflow_schema",
|
|
7
|
+
)
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade() -> None:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def downgrade() -> None:
|
|
17
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|