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.
Files changed (827) hide show
  1. app/__init__.py +10 -0
  2. app/admin_cli.py +494 -0
  3. app/cli.py +339 -0
  4. app/codex_sessions_retag.py +487 -0
  5. app/core/__init__.py +0 -0
  6. app/core/audit/__init__.py +0 -0
  7. app/core/audit/service.py +216 -0
  8. app/core/audit/types.py +94 -0
  9. app/core/auth/__init__.py +236 -0
  10. app/core/auth/api_key_cache.py +65 -0
  11. app/core/auth/dashboard_access.py +429 -0
  12. app/core/auth/dashboard_mode.py +171 -0
  13. app/core/auth/dashboard_session_ttl.py +74 -0
  14. app/core/auth/dashboard_users_cache.py +120 -0
  15. app/core/auth/dependencies.py +710 -0
  16. app/core/auth/external_identity.py +78 -0
  17. app/core/auth/guardian.py +374 -0
  18. app/core/auth/models.py +49 -0
  19. app/core/auth/providers/__init__.py +129 -0
  20. app/core/auth/providers/oidc.py +838 -0
  21. app/core/auth/providers/registry.py +118 -0
  22. app/core/auth/refresh.py +394 -0
  23. app/core/auth/step_up.py +117 -0
  24. app/core/auth/totp.py +81 -0
  25. app/core/balancer/__init__.py +71 -0
  26. app/core/balancer/logic.py +1612 -0
  27. app/core/balancer/rendezvous_hash.py +21 -0
  28. app/core/balancer/types.py +21 -0
  29. app/core/bootstrap.py +172 -0
  30. app/core/cache/invalidation.py +426 -0
  31. app/core/clients/__init__.py +0 -0
  32. app/core/clients/codex.py +996 -0
  33. app/core/clients/codex_version.py +187 -0
  34. app/core/clients/codex_version_snapshot.py +2 -0
  35. app/core/clients/files.py +534 -0
  36. app/core/clients/headers.py +29 -0
  37. app/core/clients/http.py +511 -0
  38. app/core/clients/model_fetcher.py +276 -0
  39. app/core/clients/native_egress.py +1304 -0
  40. app/core/clients/oauth.py +477 -0
  41. app/core/clients/proxy.py +6089 -0
  42. app/core/clients/proxy_websocket.py +1411 -0
  43. app/core/clients/rate_limit_reset_credits.py +412 -0
  44. app/core/clients/stream_errors.py +12 -0
  45. app/core/clients/thread_cache_identity.py +347 -0
  46. app/core/clients/usage.py +467 -0
  47. app/core/clock.py +170 -0
  48. app/core/config/__init__.py +0 -0
  49. app/core/config/background_jobs.py +73 -0
  50. app/core/config/context_window_overrides.py +166 -0
  51. app/core/config/dashboard_overrides.py +145 -0
  52. app/core/config/inheritable.py +47 -0
  53. app/core/config/key_fingerprint.py +115 -0
  54. app/core/config/settings.py +872 -0
  55. app/core/config/settings_cache.py +100 -0
  56. app/core/config/spool_retention.py +217 -0
  57. app/core/config/tiers.py +174 -0
  58. app/core/conversation_archive.py +568 -0
  59. app/core/crypto.py +37 -0
  60. app/core/draining.py +11 -0
  61. app/core/errors.py +365 -0
  62. app/core/exceptions.py +155 -0
  63. app/core/handlers/__init__.py +3 -0
  64. app/core/handlers/exceptions.py +478 -0
  65. app/core/http_protocol.py +176 -0
  66. app/core/http_protocol_httptools.py +204 -0
  67. app/core/ingress_limits.py +17 -0
  68. app/core/metadata_store.py +18 -0
  69. app/core/metrics/__init__.py +0 -0
  70. app/core/metrics/middleware.py +74 -0
  71. app/core/metrics/prometheus.py +604 -0
  72. app/core/middleware/__init__.py +25 -0
  73. app/core/middleware/api_firewall.py +111 -0
  74. app/core/middleware/app_version.py +37 -0
  75. app/core/middleware/dashboard_auth_proxy.py +55 -0
  76. app/core/middleware/dashboard_csrf.py +177 -0
  77. app/core/middleware/dashboard_gzip.py +41 -0
  78. app/core/middleware/dashboard_overrides.py +59 -0
  79. app/core/middleware/firewall_cache.py +91 -0
  80. app/core/middleware/inflight.py +126 -0
  81. app/core/middleware/multipart_content_encoding.py +129 -0
  82. app/core/middleware/path_rewrite.py +148 -0
  83. app/core/middleware/request_body_limit.py +196 -0
  84. app/core/middleware/request_decompression.py +209 -0
  85. app/core/middleware/request_id.py +72 -0
  86. app/core/middleware/required_capability_http.py +116 -0
  87. app/core/middleware/trusted_proxy_headers.py +36 -0
  88. app/core/multipart.py +237 -0
  89. app/core/multipart_fields.py +91 -0
  90. app/core/openai/__init__.py +0 -0
  91. app/core/openai/chat_requests.py +493 -0
  92. app/core/openai/chat_responses.py +931 -0
  93. app/core/openai/contracts.py +46 -0
  94. app/core/openai/exceptions.py +24 -0
  95. app/core/openai/host_models.py +14 -0
  96. app/core/openai/images.py +413 -0
  97. app/core/openai/message_coercion.py +376 -0
  98. app/core/openai/model_refresh_scheduler.py +423 -0
  99. app/core/openai/model_registry.py +997 -0
  100. app/core/openai/model_registry_store.py +548 -0
  101. app/core/openai/models.py +172 -0
  102. app/core/openai/parsing.py +97 -0
  103. app/core/openai/requests.py +1980 -0
  104. app/core/openai/strict_schema.py +199 -0
  105. app/core/openai/tool_call_safety.py +99 -0
  106. app/core/openai/v1_requests.py +187 -0
  107. app/core/plan_types.py +90 -0
  108. app/core/prestop.py +202 -0
  109. app/core/rate_limiter/__init__.py +0 -0
  110. app/core/rate_limiter/db_rate_limiter.py +215 -0
  111. app/core/request_locality.py +473 -0
  112. app/core/resilience/__init__.py +0 -0
  113. app/core/resilience/backpressure.py +57 -0
  114. app/core/resilience/bulkhead.py +202 -0
  115. app/core/resilience/circuit_breaker.py +150 -0
  116. app/core/resilience/degradation.py +55 -0
  117. app/core/resilience/loop_lag_monitor.py +60 -0
  118. app/core/resilience/memory_monitor.py +137 -0
  119. app/core/resilience/network_recovery.py +256 -0
  120. app/core/resilience/overload.py +98 -0
  121. app/core/resilience/toggles.py +114 -0
  122. app/core/retention/__init__.py +0 -0
  123. app/core/retention/job.py +240 -0
  124. app/core/retention/scheduler.py +69 -0
  125. app/core/runtime_logging.py +601 -0
  126. app/core/scheduling/__init__.py +0 -0
  127. app/core/scheduling/leader_election.py +993 -0
  128. app/core/scheduling/leader_election_handle.py +25 -0
  129. app/core/server.py +140 -0
  130. app/core/shutdown.py +326 -0
  131. app/core/socket_peer.py +22 -0
  132. app/core/startup.py +41 -0
  133. app/core/timeout_invariants.py +347 -0
  134. app/core/tracing/__init__.py +0 -0
  135. app/core/tracing/otel.py +207 -0
  136. app/core/types.py +6 -0
  137. app/core/upstream_proxy/__init__.py +13 -0
  138. app/core/upstream_proxy/cache.py +130 -0
  139. app/core/upstream_proxy/resolver.py +189 -0
  140. app/core/upstream_proxy/types.py +104 -0
  141. app/core/usage/__init__.py +407 -0
  142. app/core/usage/depletion.py +109 -0
  143. app/core/usage/live_hub.py +41 -0
  144. app/core/usage/live_snapshots.py +174 -0
  145. app/core/usage/logs.py +212 -0
  146. app/core/usage/metadata_scheduler.py +118 -0
  147. app/core/usage/models.py +54 -0
  148. app/core/usage/pricing.py +576 -0
  149. app/core/usage/pricing_catalog.py +241 -0
  150. app/core/usage/pricing_snapshot.json +821 -0
  151. app/core/usage/quota.py +133 -0
  152. app/core/usage/refresh_policy.py +21 -0
  153. app/core/usage/refresh_scheduler.py +713 -0
  154. app/core/usage/reset_credits_refresh_scheduler.py +435 -0
  155. app/core/usage/types.py +168 -0
  156. app/core/utils/__init__.py +0 -0
  157. app/core/utils/json_guards.py +18 -0
  158. app/core/utils/locks.py +33 -0
  159. app/core/utils/masking.py +20 -0
  160. app/core/utils/proxy_env.py +104 -0
  161. app/core/utils/request_id.py +65 -0
  162. app/core/utils/retry.py +77 -0
  163. app/core/utils/shared_future.py +172 -0
  164. app/core/utils/sse.py +286 -0
  165. app/core/utils/time.py +24 -0
  166. app/db/__init__.py +0 -0
  167. app/db/account_identity_lock.py +56 -0
  168. app/db/alembic/__init__.py +1 -0
  169. app/db/alembic/env.py +70 -0
  170. app/db/alembic/revision_ids.py +43 -0
  171. app/db/alembic/versions/20260213_000000_base_schema.py +248 -0
  172. app/db/alembic/versions/20260213_000100_normalize_account_plan_types.py +45 -0
  173. app/db/alembic/versions/20260213_000200_add_request_logs_reasoning_effort.py +45 -0
  174. app/db/alembic/versions/20260213_000300_add_accounts_reset_at.py +45 -0
  175. app/db/alembic/versions/20260213_000400_add_accounts_chatgpt_account_id.py +45 -0
  176. app/db/alembic/versions/20260213_000500_add_dashboard_settings.py +70 -0
  177. app/db/alembic/versions/20260213_000600_add_dashboard_settings_totp.py +64 -0
  178. app/db/alembic/versions/20260213_000700_add_dashboard_settings_password.py +59 -0
  179. app/db/alembic/versions/20260213_000800_add_api_keys.py +87 -0
  180. app/db/alembic/versions/20260214_000000_add_api_key_limits.py +143 -0
  181. app/db/alembic/versions/20260215_000000_add_idx_logs_requested_at.py +38 -0
  182. app/db/alembic/versions/20260218_000000_add_api_key_usage_reservations.py +117 -0
  183. app/db/alembic/versions/20260218_000100_add_import_without_overwrite_and_drop_accounts_email_unique.py +198 -0
  184. app/db/alembic/versions/20260225_000000_add_dashboard_settings_routing_strategy.py +53 -0
  185. app/db/alembic/versions/20260228_020000_align_api_key_limit_enum_types.py +104 -0
  186. app/db/alembic/versions/20260228_030000_add_api_firewall_allowlist.py +42 -0
  187. app/db/alembic/versions/20260306_000000_add_request_logs_service_tier.py +45 -0
  188. app/db/alembic/versions/20260307_000000_add_api_key_enforcement_fields.py +56 -0
  189. app/db/alembic/versions/20260308_000000_add_sqlite_performance_indexes.py +41 -0
  190. app/db/alembic/versions/20260309_000000_add_additional_usage_history.py +71 -0
  191. app/db/alembic/versions/20260309_000000_request_logs_nullable_account_id.py +28 -0
  192. app/db/alembic/versions/20260310_000000_add_request_logs_transport.py +45 -0
  193. app/db/alembic/versions/20260310_000000_fix_postgresql_enum_value_casing.py +79 -0
  194. app/db/alembic/versions/20260310_120000_add_sticky_session_kinds_and_affinity_ttl.py +110 -0
  195. app/db/alembic/versions/20260312_000000_add_additional_usage_quota_key.py +193 -0
  196. app/db/alembic/versions/20260312_000000_split_sticky_sessions_primary_key_by_kind.py +96 -0
  197. app/db/alembic/versions/20260312_010000_merge_additional_usage_and_sticky_session_heads.py +25 -0
  198. app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_feature_heads.py +25 -0
  199. app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_sticky_heads.py +25 -0
  200. app/db/alembic/versions/20260312_120000_add_dashboard_upstream_stream_transport.py +62 -0
  201. app/db/alembic/versions/20260313_024500_merge_request_log_transport_heads.py +27 -0
  202. app/db/alembic/versions/20260319_100937_increase_prompt_cache_ttl_to_1800s.py +73 -0
  203. app/db/alembic/versions/20260319_111500_merge_prompt_cache_ttl_and_upstream_transport_heads.py +25 -0
  204. app/db/alembic/versions/20260319_183000_normalize_sqlite_account_status_casing.py +48 -0
  205. app/db/alembic/versions/20260320_000000_add_request_log_requested_actual_tiers.py +51 -0
  206. app/db/alembic/versions/20260321_190000_tighten_dashboard_db_indexes.py +64 -0
  207. app/db/alembic/versions/20260321_210000_merge_request_log_tiers_and_dashboard_index_heads.py +25 -0
  208. app/db/alembic/versions/20260325_000000_add_request_log_cost.py +193 -0
  209. app/db/alembic/versions/20260328_000000_add_rate_limit_attempts.py +48 -0
  210. app/db/alembic/versions/20260328_120000_add_request_log_ttft.py +37 -0
  211. app/db/alembic/versions/20260328_130000_add_audit_logs_table.py +45 -0
  212. app/db/alembic/versions/20260328_140000_add_scheduler_leader_table.py +39 -0
  213. app/db/alembic/versions/20260330_000000_add_cache_locality_settings.py +69 -0
  214. app/db/alembic/versions/20260330_010000_merge_scheduler_leader_and_cache_locality_heads.py +25 -0
  215. app/db/alembic/versions/20260330_020000_add_bridge_ring_members.py +46 -0
  216. app/db/alembic/versions/20260401_000000_add_api_key_enforced_service_tier.py +52 -0
  217. app/db/alembic/versions/20260401_000000_add_cache_invalidation.py +41 -0
  218. app/db/alembic/versions/20260401_020000_merge_cache_invalidation_and_api_key_service_tier_heads.py +22 -0
  219. app/db/alembic/versions/20260402_000000_switch_dashboard_routing_default_to_capacity_weighted.py +66 -0
  220. app/db/alembic/versions/20260403_000000_add_credit_api_key_limit_values.py +44 -0
  221. app/db/alembic/versions/20260406_000000_add_api_key_account_assignments.py +57 -0
  222. app/db/alembic/versions/20260406_010000_add_api_key_assignment_scope_flag.py +75 -0
  223. app/db/alembic/versions/20260407_000000_add_bridge_gateway_safe_mode.py +51 -0
  224. app/db/alembic/versions/20260407_010000_merge_api_key_assignment_and_bridge_gateway_heads.py +25 -0
  225. app/db/alembic/versions/20260408_000000_switch_import_without_overwrite_default_to_true.py +73 -0
  226. app/db/alembic/versions/20260408_010000_merge_import_without_overwrite_and_assignment_heads.py +25 -0
  227. app/db/alembic/versions/20260409_000000_add_http_bridge_sessions.py +130 -0
  228. app/db/alembic/versions/20260409_000000_switch_sticky_threads_and_prefer_earlier_reset_defaults_to_true.py +89 -0
  229. app/db/alembic/versions/20260409_010000_add_dashboard_settings_bootstrap_token.py +57 -0
  230. app/db/alembic/versions/20260409_010000_merge_http_bridge_and_import_without_overwrite_heads.py +25 -0
  231. app/db/alembic/versions/20260409_020000_fix_http_bridge_last_seen_index.py +46 -0
  232. app/db/alembic/versions/20260410_000000_backfill_pristine_dashboard_settings_defaults.py +77 -0
  233. app/db/alembic/versions/20260410_030000_restore_import_without_overwrite_default_true.py +73 -0
  234. app/db/alembic/versions/20260410_040000_merge_dashboard_defaults_and_import_default_heads.py +25 -0
  235. app/db/alembic/versions/20260413_000000_add_accounts_blocked_at.py +44 -0
  236. app/db/alembic/versions/20260415_160000_add_automations_tables.py +189 -0
  237. app/db/alembic/versions/20260415_160000_add_request_logs_response_lookup_index.py +67 -0
  238. app/db/alembic/versions/20260417_000000_add_request_log_plan_type.py +44 -0
  239. app/db/alembic/versions/20260418_190000_add_automation_runs_cycle_key.py +87 -0
  240. app/db/alembic/versions/20260419_000000_add_automation_run_cycle_metadata.py +56 -0
  241. app/db/alembic/versions/20260419_020000_add_automation_run_cycles_snapshot_tables.py +342 -0
  242. app/db/alembic/versions/20260421_000000_add_dashboard_session_ttl_setting.py +51 -0
  243. app/db/alembic/versions/20260421_120000_merge_request_log_lookup_and_plan_type_heads.py +25 -0
  244. app/db/alembic/versions/20260421_130000_merge_automation_and_request_log_heads.py +25 -0
  245. app/db/alembic/versions/20260422_103000_normalize_legacy_automation_run_cycle_keys.py +648 -0
  246. app/db/alembic/versions/20260423_120000_add_api_key_limit_reset_at_index.py +43 -0
  247. app/db/alembic/versions/20260424_000000_merge_dashboard_session_ttl_and_request_log_heads.py +25 -0
  248. app/db/alembic/versions/20260426_000000_add_dashboard_relative_availability_settings.py +68 -0
  249. app/db/alembic/versions/20260427_130000_add_warmup_model_and_request_kind.py +75 -0
  250. app/db/alembic/versions/20260506_000000_add_account_routing_policy.py +37 -0
  251. app/db/alembic/versions/20260509_000000_add_api_key_traffic_class.py +37 -0
  252. app/db/alembic/versions/20260509_010000_add_additional_quota_routing_policies.py +37 -0
  253. app/db/alembic/versions/20260513_000000_add_accounts_alias.py +44 -0
  254. app/db/alembic/versions/20260513_000000_add_api_key_apply_to_codex_model.py +59 -0
  255. app/db/alembic/versions/20260514_000000_add_request_logs_api_key_time_index.py +43 -0
  256. app/db/alembic/versions/20260515_000000_add_prefer_earlier_reset_window.py +47 -0
  257. app/db/alembic/versions/20260515_000000_soft_delete_request_logs_on_account_delete.py +83 -0
  258. app/db/alembic/versions/20260515_020000_add_split_sticky_budget_thresholds.py +62 -0
  259. app/db/alembic/versions/20260516_000000_add_sqlite_hot_path_indexes.py +75 -0
  260. app/db/alembic/versions/20260517_000000_merge_sqlite_hot_path_and_request_log_delete_heads.py +24 -0
  261. app/db/alembic/versions/20260518_000000_add_http_bridge_durable_input_prefix.py +44 -0
  262. app/db/alembic/versions/20260518_010000_merge_http_bridge_and_sqlite_recovery_heads.py +25 -0
  263. app/db/alembic/versions/20260518_120000_add_dashboard_guest_access.py +57 -0
  264. app/db/alembic/versions/20260520_000000_merge_api_key_and_http_bridge_heads.py +26 -0
  265. app/db/alembic/versions/20260520_010000_add_request_logs_api_key_account_index.py +45 -0
  266. app/db/alembic/versions/20260520_010000_merge_automation_and_api_key_heads.py +26 -0
  267. app/db/alembic/versions/20260520_030000_add_quota_planner.py +194 -0
  268. app/db/alembic/versions/20260520_040000_merge_quota_planner_and_request_log_index_heads.py +24 -0
  269. app/db/alembic/versions/20260521_000000_add_account_security_work_authorized.py +46 -0
  270. app/db/alembic/versions/20260521_000000_merge_dashboard_guest_and_api_key_heads.py +26 -0
  271. app/db/alembic/versions/20260521_000000_merge_prefer_reset_window_head.py +26 -0
  272. app/db/alembic/versions/20260521_000000_merge_warmup_and_request_log_index_heads.py +25 -0
  273. app/db/alembic/versions/20260522_000000_add_limit_warmup_trigger.py +195 -0
  274. app/db/alembic/versions/20260524_000000_merge_accounts_alias_and_reset_window_heads.py +24 -0
  275. app/db/alembic/versions/20260525_000000_add_usage_raw_window_latest_index.py +31 -0
  276. app/db/alembic/versions/20260526_000000_add_request_log_failure_metadata.py +100 -0
  277. app/db/alembic/versions/20260531_000000_add_account_workspace_identity.py +39 -0
  278. app/db/alembic/versions/20260531_000000_add_single_account_routing.py +42 -0
  279. app/db/alembic/versions/20260601_000000_merge_relative_availability_and_usage_raw_heads.py +24 -0
  280. app/db/alembic/versions/20260601_010000_add_api_key_usage_sections.py +58 -0
  281. app/db/alembic/versions/20260601_010000_add_routing_policy_persistence.py +67 -0
  282. app/db/alembic/versions/20260601_010000_merge_security_work_and_relative_availability_heads.py +25 -0
  283. app/db/alembic/versions/20260601_010000_merge_split_sticky_and_relative_usage_heads.py +25 -0
  284. app/db/alembic/versions/20260601_010000_merge_warmup_and_relative_availability_heads.py +25 -0
  285. app/db/alembic/versions/20260601_020000_merge_additional_quota_routing_and_relative_availability_heads.py +25 -0
  286. app/db/alembic/versions/20260601_020000_merge_quota_planner_and_relative_availability_heads.py +25 -0
  287. app/db/alembic/versions/20260601_020000_merge_reset_window_and_relative_availability_heads.py +25 -0
  288. app/db/alembic/versions/20260601_020000_merge_routing_policy_and_split_threshold_heads.py +24 -0
  289. app/db/alembic/versions/20260601_020000_merge_warmup_and_request_log_failure_heads.py +25 -0
  290. app/db/alembic/versions/20260601_030000_merge_single_account_and_routing_quota_heads.py +29 -0
  291. app/db/alembic/versions/20260601_040000_merge_security_work_and_routing_consolidation_heads.py +25 -0
  292. app/db/alembic/versions/20260601_050000_merge_quota_planner_and_routing_consolidation_heads.py +25 -0
  293. app/db/alembic/versions/20260602_050000_add_upstream_proxy_routing.py +164 -0
  294. app/db/alembic/versions/20260602_060000_merge_account_workspace_and_failure_heads.py +25 -0
  295. app/db/alembic/versions/20260602_070000_add_request_log_useragent_fields.py +86 -0
  296. app/db/alembic/versions/20260602_070000_merge_quota_planner_and_account_workspace_heads.py +25 -0
  297. app/db/alembic/versions/20260602_080000_merge_upstream_proxy_and_quota_planner_heads.py +25 -0
  298. app/db/alembic/versions/20260602_080000_merge_useragent_and_upstream_proxy_heads.py +24 -0
  299. app/db/alembic/versions/20260603_000000_add_weekly_pace_working_days.py +52 -0
  300. app/db/alembic/versions/20260603_000000_free_account_monthly_window.py +60 -0
  301. app/db/alembic/versions/20260604_000000_add_reauth_required_account_status.py +159 -0
  302. app/db/alembic/versions/20260604_010000_merge_automations_and_reauth_status_heads.py +25 -0
  303. app/db/alembic/versions/20260604_010000_merge_dashboard_guest_and_reauth_status_heads.py +25 -0
  304. app/db/alembic/versions/20260604_010000_merge_useragent_and_reauth_heads.py +24 -0
  305. app/db/alembic/versions/20260607_000000_merge_weekly_monthly_useragent_heads.py +28 -0
  306. app/db/alembic/versions/20260608_000000_add_hide_upstream_quota_from_api_keys.py +50 -0
  307. app/db/alembic/versions/20260611_000000_add_request_log_archive_request_id.py +47 -0
  308. app/db/alembic/versions/20260611_000000_merge_automations_and_weekly_monthly_useragent_heads.py +26 -0
  309. app/db/alembic/versions/20260611_000000_merge_dashboard_guest_and_weekly_useragent_heads.py +26 -0
  310. app/db/alembic/versions/20260611_010000_add_request_log_client_ip.py +57 -0
  311. app/db/alembic/versions/20260611_020000_merge_request_log_archive_and_client_ip_heads.py +26 -0
  312. app/db/alembic/versions/20260611_030000_merge_dashboard_guest_and_request_log_client_ip_heads.py +26 -0
  313. app/db/alembic/versions/20260613_000000_add_accounts_codex_installation_id.py +57 -0
  314. app/db/alembic/versions/20260613_010000_merge_installation_id_and_dashboard_weekly_heads.py +26 -0
  315. app/db/alembic/versions/20260615_000000_merge_automations_and_dashboard_guest_heads.py +26 -0
  316. app/db/alembic/versions/20260623_000000_add_limit_warmup_exhausted_threshold.py +51 -0
  317. app/db/alembic/versions/20260626_000000_add_http_downstream_transport_policy.py +46 -0
  318. app/db/alembic/versions/20260626_010000_add_request_logs_upstream_transport.py +31 -0
  319. app/db/alembic/versions/20260629_000000_add_dashboard_query_hot_path_indexes.py +53 -0
  320. app/db/alembic/versions/20260630_000000_add_limit_warmup_staggered_idle.py +47 -0
  321. app/db/alembic/versions/20260630_000000_merge_request_log_client_ip_and_dashboard_hot_path_heads.py +26 -0
  322. app/db/alembic/versions/20260630_010000_merge_warmup_and_request_log_dashboard_heads.py +26 -0
  323. app/db/alembic/versions/20260630_020000_merge_automations_and_main_heads.py +26 -0
  324. app/db/alembic/versions/20260630_020000_merge_warmup_threshold_and_main_heads.py +26 -0
  325. app/db/alembic/versions/20260630_030000_add_automation_run_model_snapshot.py +59 -0
  326. app/db/alembic/versions/20260630_040000_merge_automation_snapshot_and_warmup_threshold_heads.py +26 -0
  327. app/db/alembic/versions/20260630_050000_add_automation_run_prompt_snapshot.py +52 -0
  328. app/db/alembic/versions/20260701_000000_add_weekly_pace_smoothing_minutes.py +51 -0
  329. app/db/alembic/versions/20260702_000000_add_model_sources.py +176 -0
  330. app/db/alembic/versions/20260705_000000_add_model_source_audio_transcriptions.py +50 -0
  331. app/db/alembic/versions/20260705_000000_harden_dashboard_session_ttl.py +62 -0
  332. app/db/alembic/versions/20260706_000000_add_model_source_audio_per_minute.py +43 -0
  333. app/db/alembic/versions/20260707_000000_merge_model_sources_and_session_ttl.py +24 -0
  334. app/db/alembic/versions/20260707_010000_merge_automations_and_model_sources_heads.py +24 -0
  335. app/db/alembic/versions/20260707_020000_add_automation_job_account_scope.py +125 -0
  336. app/db/alembic/versions/20260709_000000_add_ttft_phase_observability.py +66 -0
  337. app/db/alembic/versions/20260711_000000_add_dashboard_prohibit_fast_mode.py +42 -0
  338. app/db/alembic/versions/20260711_010000_add_account_chatgpt_user_id.py +40 -0
  339. app/db/alembic/versions/20260711_020000_add_dashboard_account_concurrency_caps.py +71 -0
  340. app/db/alembic/versions/20260711_030000_add_limit_warmup_idle_threshold.py +51 -0
  341. app/db/alembic/versions/20260712_010000_add_account_usage_rollups.py +56 -0
  342. app/db/alembic/versions/20260712_020000_add_api_key_usage_rollups.py +53 -0
  343. app/db/alembic/versions/20260713_020000_add_model_registry_snapshot.py +42 -0
  344. app/db/alembic/versions/20260713_040000_add_account_refresh_claims.py +42 -0
  345. app/db/alembic/versions/20260713_040000_add_replica_guardrails.py +64 -0
  346. app/db/alembic/versions/20260713_070000_add_reset_credit_redeem_tables.py +58 -0
  347. app/db/alembic/versions/20260714_000000_add_oauth_flow_states.py +63 -0
  348. app/db/alembic/versions/20260715_000000_add_request_log_queue_latency.py +44 -0
  349. app/db/alembic/versions/20260716_000000_add_oauth_device_flow_slots.py +41 -0
  350. app/db/alembic/versions/20260716_010000_add_dashboard_retention_settings.py +48 -0
  351. app/db/alembic/versions/20260716_010000_add_reset_credit_display_settings.py +73 -0
  352. app/db/alembic/versions/20260717_000000_add_http_bridge_retry_circuits.py +47 -0
  353. app/db/alembic/versions/20260717_000000_merge_retention_and_reset_credit_display_heads.py +26 -0
  354. app/db/alembic/versions/20260717_000000_optimize_dashboard_hot_path_indexes.py +162 -0
  355. app/db/alembic/versions/20260717_000001_merge_retry_circuits_and_dashboard_indexes.py +22 -0
  356. app/db/alembic/versions/20260720_000000_add_request_log_conversation_id.py +57 -0
  357. app/db/alembic/versions/20260722_000000_backfill_request_log_useragent_families.py +40 -0
  358. app/db/alembic/versions/20260724_000000_add_request_usage_time_rollups.py +176 -0
  359. app/db/alembic/versions/20260724_000000_merge_request_log_schema_heads.py +33 -0
  360. app/db/alembic/versions/20260725_000000_add_http_bridge_pending_tool_calls.py +43 -0
  361. app/db/alembic/versions/20260726_000000_add_account_plan_downgrade_observations.py +44 -0
  362. app/db/alembic/versions/20260726_000000_repair_request_usage_rollups_after_merge.py +37 -0
  363. app/db/alembic/versions/20260727_000000_add_sticky_session_continuity_abandoned_at.py +45 -0
  364. app/db/alembic/versions/20260728_000000_merge_pending_tool_calls_and_rollup_repair_heads.py +31 -0
  365. app/db/alembic/versions/20260730_000000_add_api_key_fair_share_threshold.py +44 -0
  366. app/db/alembic/versions/20260730_000000_add_http_bridge_recovery_attempts.py +60 -0
  367. app/db/alembic/versions/20260731_000000_add_capability_lineage_markers.py +38 -0
  368. app/db/alembic/versions/20260803_000000_merge_http_bridge_recovery_and_capability_lineage_heads.py +30 -0
  369. app/db/alembic/versions/20260804_000000_add_http_bridge_operations.py +63 -0
  370. app/db/alembic/versions/20260804_000001_add_global_http_bridge_operation_fingerprint.py +53 -0
  371. app/db/alembic/versions/20260804_230000_add_request_log_connection_request_kind.py +43 -0
  372. app/db/alembic/versions/20260805_000000_add_http_bridge_operation_spool.py +75 -0
  373. app/db/alembic/versions/20260805_000001_finalize_http_bridge_operation_spool.py +65 -0
  374. app/db/alembic/versions/20260806_000000_add_additional_usage_alias_probe_indexes.py +84 -0
  375. app/db/alembic/versions/20260806_000000_add_anonymous_telemetry.py +49 -0
  376. app/db/alembic/versions/20260806_010000_add_conversation_presence_rollup.py +72 -0
  377. app/db/alembic/versions/20260806_020000_add_usage_history_bulk_covering_indexes.py +99 -0
  378. app/db/alembic/versions/20260806_030000_add_api_key_allowed_reasoning_efforts.py +65 -0
  379. app/db/alembic/versions/20260806_120000_add_http_bridge_owner_process_epoch.py +57 -0
  380. app/db/alembic/versions/20260807_000000_merge_http_bridge_operation_spool_and_latest_main.py +24 -0
  381. app/db/alembic/versions/20260808_000000_tune_usage_history_autovacuum.py +54 -0
  382. app/db/alembic/versions/20260810_000000_add_http_bridge_recovery_dispatch_count.py +44 -0
  383. app/db/alembic/versions/20260811_000000_add_hourly_rollup_cancelled_count.py +92 -0
  384. app/db/alembic/versions/20260812_000000_merge_recovery_dispatch_and_hourly_cancelled_heads.py +25 -0
  385. app/db/alembic/versions/20260812_120000_add_sticky_abandonment_scope.py +43 -0
  386. app/db/alembic/versions/20260813_000000_add_file_account_pins.py +40 -0
  387. app/db/alembic/versions/20260816_000000_add_account_pending_deletion.py +81 -0
  388. app/db/alembic/versions/20260816_000000_add_model_source_embeddings.py +50 -0
  389. app/db/alembic/versions/20260821_000000_add_retry_circuit_admission_generation.py +49 -0
  390. app/db/alembic/versions/20260826_000000_add_http_bridge_event_chunks.py +107 -0
  391. app/db/alembic/versions/20260828_000000_add_accounts_chatgpt_identity_index.py +55 -0
  392. app/db/alembic/versions/20260830_000000_add_quota_warmup_claim_expiry.py +83 -0
  393. app/db/alembic/versions/20260908_000000_add_guest_session_generation.py +37 -0
  394. app/db/alembic/versions/20260908_000000_add_subscription_overflow.py +140 -0
  395. app/db/alembic/versions/20260908_000000_replace_upstream_stream_transport_default_sentinel.py +50 -0
  396. app/db/alembic/versions/20260908_020000_merge_overflow_transport_heads.py +27 -0
  397. app/db/alembic/versions/20260909_000000_add_dashboard_roles.py +72 -0
  398. app/db/alembic/versions/20260909_010000_add_dashboard_users.py +183 -0
  399. app/db/alembic/versions/20260909_020000_reproject_compat_admin_credentials.py +105 -0
  400. app/db/alembic/versions/20260909_030000_add_audit_actor_columns.py +103 -0
  401. app/db/alembic/versions/20260909_030000_dashboard_resilience_toggle_settings.py +63 -0
  402. app/db/alembic/versions/20260909_040000_add_dashboard_user_invites.py +54 -0
  403. app/db/alembic/versions/20260909_040000_dashboard_timeout_settings.py +68 -0
  404. app/db/alembic/versions/20260909_050000_dashboard_routing_overload_settings.py +65 -0
  405. app/db/alembic/versions/20260909_060000_add_report_rollup.py +74 -0
  406. app/db/alembic/versions/20260909_070000_automation_run_claim_budget.py +50 -0
  407. app/db/alembic/versions/20260909_080000_dashboard_stream_bridge_budgets.py +62 -0
  408. app/db/alembic/versions/20260909_090000_dashboard_background_job_toggles.py +63 -0
  409. app/db/alembic/versions/20260909_100000_dashboard_codex_prewarm.py +49 -0
  410. app/db/alembic/versions/20260909_110000_model_context_window_overrides.py +45 -0
  411. app/db/alembic/versions/20260909_120000_dashboard_conversation_archive.py +48 -0
  412. app/db/alembic/versions/20260909_130000_add_request_logs_live_facet_indexes.py +96 -0
  413. app/db/alembic/versions/20260910_000000_add_totp_required_for_admin_role.py +44 -0
  414. app/db/alembic/versions/20260910_000000_request_logs_missing_cost_index.py +52 -0
  415. app/db/alembic/versions/20260910_010000_add_dashboard_auth_providers.py +134 -0
  416. app/db/alembic/versions/20260910_010000_dashboard_spool_retention.py +52 -0
  417. app/db/alembic/versions/20260910_020000_add_dashboard_role_mappings.py +74 -0
  418. app/db/alembic/versions/20260910_143000_request_log_affinity.py +28 -0
  419. app/db/alembic/versions/20260910_180000_merge_affinity_guest_heads.py +17 -0
  420. app/db/alembic/versions/20260910_200000_merge_affinity_identity_heads.py +17 -0
  421. app/db/alembic/versions/20260910_220000_merge_affinity_invite_heads.py +17 -0
  422. app/db/alembic/versions/20260911_000000_model_source_pins_kind_expires_index.py +43 -0
  423. app/db/alembic/versions/20260911_010000_merge_pin_index_and_affinity_heads.py +17 -0
  424. app/db/alembic/versions/20260911_020000_add_http_bridge_terminal_append_phase.py +54 -0
  425. app/db/alembic/versions/20260911_030000_add_local_login_policy.py +48 -0
  426. app/db/alembic/versions/20260911_040000_add_thread_cache_identity_mode.py +48 -0
  427. app/db/alembic/versions/20260911_060000_add_bridge_session_continuity_abandonment.py +40 -0
  428. app/db/alembic/versions/20260912_000000_merge_thread_cache_and_bridge_retirement_heads.py +22 -0
  429. app/db/alembic/versions/20260912_010000_drop_legacy_dashboard_credentials.py +140 -0
  430. app/db/alembic/versions/20260913_000000_add_oidc_provider_flow.py +134 -0
  431. app/db/alembic/versions/20260914_000000_add_scim_tokens.py +78 -0
  432. app/db/alembic/versions/20260914_000000_drop_subscription_overflow_schema.py +132 -0
  433. app/db/alembic/versions/20260918_000000_merge_scim_and_overflow_heads.py +17 -0
  434. app/db/alembic/versions/__init__.py +1 -0
  435. app/db/backup.py +64 -0
  436. app/db/migrate.py +1249 -0
  437. app/db/migration_lock.py +160 -0
  438. app/db/migration_url.py +18 -0
  439. app/db/models.py +3100 -0
  440. app/db/recover.py +244 -0
  441. app/db/session.py +1295 -0
  442. app/db/snapshot.py +46 -0
  443. app/db/sqlite_lock_retry.py +190 -0
  444. app/db/sqlite_utils.py +549 -0
  445. app/dependencies.py +462 -0
  446. app/main.py +1240 -0
  447. app/modules/__init__.py +0 -0
  448. app/modules/accounts/__init__.py +0 -0
  449. app/modules/accounts/api.py +426 -0
  450. app/modules/accounts/auth_manager.py +1439 -0
  451. app/modules/accounts/background_repository.py +160 -0
  452. app/modules/accounts/deletion.py +540 -0
  453. app/modules/accounts/mappers.py +654 -0
  454. app/modules/accounts/refresh_claims.py +352 -0
  455. app/modules/accounts/repository.py +1679 -0
  456. app/modules/accounts/schemas.py +308 -0
  457. app/modules/accounts/service.py +809 -0
  458. app/modules/accounts/usage_rollup.py +486 -0
  459. app/modules/accounts/usage_rollup_scheduler.py +75 -0
  460. app/modules/accounts/usage_time_rollup.py +1037 -0
  461. app/modules/accounts/usage_time_rollup_read.py +522 -0
  462. app/modules/api_keys/__init__.py +1 -0
  463. app/modules/api_keys/api.py +344 -0
  464. app/modules/api_keys/last_used_coalescer.py +216 -0
  465. app/modules/api_keys/limit_windows.py +41 -0
  466. app/modules/api_keys/repository.py +1200 -0
  467. app/modules/api_keys/reset_scheduler.py +85 -0
  468. app/modules/api_keys/schemas.py +135 -0
  469. app/modules/api_keys/service.py +2123 -0
  470. app/modules/audit/__init__.py +0 -0
  471. app/modules/audit/api.py +88 -0
  472. app/modules/audit/repository.py +74 -0
  473. app/modules/audit/schemas.py +33 -0
  474. app/modules/audit/service.py +71 -0
  475. app/modules/auth_providers/__init__.py +0 -0
  476. app/modules/auth_providers/api.py +127 -0
  477. app/modules/auth_providers/config.py +343 -0
  478. app/modules/auth_providers/repository.py +101 -0
  479. app/modules/auth_providers/schemas.py +88 -0
  480. app/modules/auth_providers/seed.py +105 -0
  481. app/modules/auth_providers/service.py +289 -0
  482. app/modules/automations/__init__.py +1 -0
  483. app/modules/automations/api.py +369 -0
  484. app/modules/automations/repository.py +1896 -0
  485. app/modules/automations/scheduler.py +107 -0
  486. app/modules/automations/schemas.py +158 -0
  487. app/modules/automations/service.py +2548 -0
  488. app/modules/cache_isolation_probe/__init__.py +0 -0
  489. app/modules/cache_isolation_probe/api.py +188 -0
  490. app/modules/cache_isolation_probe/prefix.py +144 -0
  491. app/modules/cache_isolation_probe/schemas.py +94 -0
  492. app/modules/cache_isolation_probe/sender.py +229 -0
  493. app/modules/cache_isolation_probe/service.py +525 -0
  494. app/modules/conversation_archive/__init__.py +1 -0
  495. app/modules/conversation_archive/api.py +101 -0
  496. app/modules/conversation_archive/schemas.py +28 -0
  497. app/modules/conversation_archive/service.py +165 -0
  498. app/modules/dashboard/__init__.py +1 -0
  499. app/modules/dashboard/api.py +106 -0
  500. app/modules/dashboard/builders.py +107 -0
  501. app/modules/dashboard/repository.py +304 -0
  502. app/modules/dashboard/schemas.py +148 -0
  503. app/modules/dashboard/service.py +534 -0
  504. app/modules/dashboard/timeframes.py +19 -0
  505. app/modules/dashboard/weekly_pace.py +617 -0
  506. app/modules/dashboard_auth/__init__.py +1 -0
  507. app/modules/dashboard_auth/api.py +1344 -0
  508. app/modules/dashboard_auth/oidc_api.py +753 -0
  509. app/modules/dashboard_auth/oidc_flows.py +330 -0
  510. app/modules/dashboard_auth/repository.py +452 -0
  511. app/modules/dashboard_auth/schemas.py +232 -0
  512. app/modules/dashboard_auth/service.py +1384 -0
  513. app/modules/dashboard_roles/__init__.py +0 -0
  514. app/modules/dashboard_roles/api.py +64 -0
  515. app/modules/dashboard_roles/repository.py +54 -0
  516. app/modules/dashboard_roles/schemas.py +29 -0
  517. app/modules/dashboard_roles/seed.py +80 -0
  518. app/modules/dashboard_roles/service.py +142 -0
  519. app/modules/dashboard_users/__init__.py +0 -0
  520. app/modules/dashboard_users/api.py +278 -0
  521. app/modules/dashboard_users/break_glass.py +169 -0
  522. app/modules/dashboard_users/credentials.py +18 -0
  523. app/modules/dashboard_users/identity_resolver.py +665 -0
  524. app/modules/dashboard_users/repository.py +725 -0
  525. app/modules/dashboard_users/schemas.py +117 -0
  526. app/modules/dashboard_users/service.py +1054 -0
  527. app/modules/firewall/__init__.py +1 -0
  528. app/modules/firewall/api.py +78 -0
  529. app/modules/firewall/repository.py +51 -0
  530. app/modules/firewall/schemas.py +28 -0
  531. app/modules/firewall/service.py +93 -0
  532. app/modules/fleet/__init__.py +1 -0
  533. app/modules/fleet/api.py +177 -0
  534. app/modules/fleet/mappers.py +56 -0
  535. app/modules/fleet/observability.py +372 -0
  536. app/modules/fleet/schemas.py +119 -0
  537. app/modules/health/__init__.py +0 -0
  538. app/modules/health/api.py +246 -0
  539. app/modules/health/schemas.py +27 -0
  540. app/modules/limit_warmup/__init__.py +1 -0
  541. app/modules/limit_warmup/repository.py +159 -0
  542. app/modules/limit_warmup/service.py +1001 -0
  543. app/modules/model_sources/__init__.py +1 -0
  544. app/modules/model_sources/api.py +99 -0
  545. app/modules/model_sources/catalog.py +307 -0
  546. app/modules/model_sources/forwarding.py +1435 -0
  547. app/modules/model_sources/projection.py +55 -0
  548. app/modules/model_sources/repository.py +201 -0
  549. app/modules/model_sources/schemas.py +79 -0
  550. app/modules/model_sources/selection.py +166 -0
  551. app/modules/model_sources/service.py +236 -0
  552. app/modules/oauth/__init__.py +0 -0
  553. app/modules/oauth/api.py +101 -0
  554. app/modules/oauth/repository.py +238 -0
  555. app/modules/oauth/schemas.py +49 -0
  556. app/modules/oauth/service.py +1196 -0
  557. app/modules/oauth/templates/oauth_success.html +122 -0
  558. app/modules/proxy/__init__.py +0 -0
  559. app/modules/proxy/_load_balancer/__init__.py +1 -0
  560. app/modules/proxy/_load_balancer/error_rate.py +116 -0
  561. app/modules/proxy/_load_balancer/latency_cohort.py +154 -0
  562. app/modules/proxy/_load_balancer/model_eligibility.py +250 -0
  563. app/modules/proxy/_load_balancer/opportunistic_admission.py +350 -0
  564. app/modules/proxy/_load_balancer/overload_backoff.py +394 -0
  565. app/modules/proxy/_load_balancer/sticky_selection.py +2417 -0
  566. app/modules/proxy/_load_balancer/throughput_cohort.py +233 -0
  567. app/modules/proxy/_load_balancer/ttft_cohort.py +214 -0
  568. app/modules/proxy/_load_balancer/tunables.py +97 -0
  569. app/modules/proxy/_load_balancer/types.py +96 -0
  570. app/modules/proxy/_load_balancer/unbound_selection.py +534 -0
  571. app/modules/proxy/_service/__init__.py +6 -0
  572. app/modules/proxy/_service/api_key_usage.py +745 -0
  573. app/modules/proxy/_service/clock_budget.py +38 -0
  574. app/modules/proxy/_service/codex_control.py +611 -0
  575. app/modules/proxy/_service/compact.py +2211 -0
  576. app/modules/proxy/_service/file_ops.py +749 -0
  577. app/modules/proxy/_service/http_bridge/__init__.py +3 -0
  578. app/modules/proxy/_service/http_bridge/accepted_replay.py +331 -0
  579. app/modules/proxy/_service/http_bridge/account_sessions.py +57 -0
  580. app/modules/proxy/_service/http_bridge/activity.py +178 -0
  581. app/modules/proxy/_service/http_bridge/helpers.py +3959 -0
  582. app/modules/proxy/_service/http_bridge/mixin.py +2435 -0
  583. app/modules/proxy/_service/http_bridge/owner_forwarding.py +609 -0
  584. app/modules/proxy/_service/http_bridge/protocol.py +167 -0
  585. app/modules/proxy/_service/http_bridge/proxy_failover.py +58 -0
  586. app/modules/proxy/_service/http_bridge/quarantine.py +443 -0
  587. app/modules/proxy/_service/http_bridge/request_submit.py +4582 -0
  588. app/modules/proxy/_service/http_bridge/retry_circuit.py +2442 -0
  589. app/modules/proxy/_service/http_bridge/service_stubs.py +492 -0
  590. app/modules/proxy/_service/http_bridge/session_registry.py +778 -0
  591. app/modules/proxy/_service/http_bridge/streaming.py +5328 -0
  592. app/modules/proxy/_service/http_bridge/upstream_events.py +4538 -0
  593. app/modules/proxy/_service/observability.py +410 -0
  594. app/modules/proxy/_service/rate_limit.py +361 -0
  595. app/modules/proxy/_service/realtime_live.py +655 -0
  596. app/modules/proxy/_service/refresh.py +154 -0
  597. app/modules/proxy/_service/request_log.py +585 -0
  598. app/modules/proxy/_service/response_create.py +930 -0
  599. app/modules/proxy/_service/streaming/__init__.py +3 -0
  600. app/modules/proxy/_service/streaming/helpers.py +1317 -0
  601. app/modules/proxy/_service/streaming/mixin.py +1100 -0
  602. app/modules/proxy/_service/streaming/protocol.py +32 -0
  603. app/modules/proxy/_service/streaming/retry.py +3762 -0
  604. app/modules/proxy/_service/support.py +2122 -0
  605. app/modules/proxy/_service/transcribe.py +434 -0
  606. app/modules/proxy/_service/warmup.py +483 -0
  607. app/modules/proxy/_service/websocket/__init__.py +3 -0
  608. app/modules/proxy/_service/websocket/helpers.py +2360 -0
  609. app/modules/proxy/_service/websocket/mixin.py +7285 -0
  610. app/modules/proxy/_service/websocket/protocol.py +74 -0
  611. app/modules/proxy/account_cache.py +229 -0
  612. app/modules/proxy/account_eligibility.py +87 -0
  613. app/modules/proxy/additional_model_limits.py +40 -0
  614. app/modules/proxy/affinity.py +830 -0
  615. app/modules/proxy/affinity_observation.py +101 -0
  616. app/modules/proxy/api.py +10254 -0
  617. app/modules/proxy/api_key_usage.py +113 -0
  618. app/modules/proxy/cap_partitioning.py +280 -0
  619. app/modules/proxy/capability_lineage.py +65 -0
  620. app/modules/proxy/capability_lineage_repository.py +82 -0
  621. app/modules/proxy/capability_routing.py +253 -0
  622. app/modules/proxy/continuity.py +112 -0
  623. app/modules/proxy/downstream_delivery.py +277 -0
  624. app/modules/proxy/durable_bridge_coordinator.py +1217 -0
  625. app/modules/proxy/durable_bridge_repository.py +4516 -0
  626. app/modules/proxy/durable_bridge_runtime.py +9 -0
  627. app/modules/proxy/durable_bridge_transcript_codec.py +122 -0
  628. app/modules/proxy/fair_share.py +131 -0
  629. app/modules/proxy/file_pin_repository.py +210 -0
  630. app/modules/proxy/helpers.py +439 -0
  631. app/modules/proxy/http_bridge_event_batcher.py +613 -0
  632. app/modules/proxy/http_bridge_forwarding.py +797 -0
  633. app/modules/proxy/http_continuation.py +76 -0
  634. app/modules/proxy/images_observability.py +62 -0
  635. app/modules/proxy/images_service.py +1054 -0
  636. app/modules/proxy/load_balancer.py +3016 -0
  637. app/modules/proxy/rate_limit_cache.py +46 -0
  638. app/modules/proxy/replay_safety.py +1063 -0
  639. app/modules/proxy/repo_bundle.py +31 -0
  640. app/modules/proxy/request_policy.py +1054 -0
  641. app/modules/proxy/ring_membership.py +251 -0
  642. app/modules/proxy/schemas.py +345 -0
  643. app/modules/proxy/selection_errors.py +41 -0
  644. app/modules/proxy/service.py +2600 -0
  645. app/modules/proxy/source_admission.py +125 -0
  646. app/modules/proxy/source_dispatch.py +999 -0
  647. app/modules/proxy/sticky_repository.py +831 -0
  648. app/modules/proxy/thread_anchors.py +491 -0
  649. app/modules/proxy/tool_call_dedupe.py +802 -0
  650. app/modules/proxy/types.py +54 -0
  651. app/modules/proxy/work_admission.py +121 -0
  652. app/modules/quota_planner/__init__.py +1 -0
  653. app/modules/quota_planner/api.py +276 -0
  654. app/modules/quota_planner/logic.py +743 -0
  655. app/modules/quota_planner/repository.py +722 -0
  656. app/modules/quota_planner/scheduler.py +212 -0
  657. app/modules/quota_planner/schemas.py +102 -0
  658. app/modules/quota_planner/warmup.py +693 -0
  659. app/modules/rate_limit_reset_credits/__init__.py +0 -0
  660. app/modules/rate_limit_reset_credits/api.py +601 -0
  661. app/modules/rate_limit_reset_credits/redeem_coordination.py +274 -0
  662. app/modules/rate_limit_reset_credits/store.py +114 -0
  663. app/modules/reports/__init__.py +1 -0
  664. app/modules/reports/api.py +134 -0
  665. app/modules/reports/cache.py +74 -0
  666. app/modules/reports/filters.py +27 -0
  667. app/modules/reports/repository.py +525 -0
  668. app/modules/reports/rollup.py +150 -0
  669. app/modules/reports/rollup_read.py +148 -0
  670. app/modules/reports/schemas.py +123 -0
  671. app/modules/reports/service.py +287 -0
  672. app/modules/reports/thread_identity.py +256 -0
  673. app/modules/request_logs/__init__.py +0 -0
  674. app/modules/request_logs/api.py +224 -0
  675. app/modules/request_logs/cost_backfill.py +154 -0
  676. app/modules/request_logs/mappers.py +97 -0
  677. app/modules/request_logs/repository.py +1765 -0
  678. app/modules/request_logs/schemas.py +145 -0
  679. app/modules/request_logs/service.py +348 -0
  680. app/modules/role_mappings/__init__.py +0 -0
  681. app/modules/role_mappings/api.py +206 -0
  682. app/modules/role_mappings/matching.py +55 -0
  683. app/modules/role_mappings/repository.py +106 -0
  684. app/modules/role_mappings/schemas.py +67 -0
  685. app/modules/role_mappings/service.py +334 -0
  686. app/modules/runtime/__init__.py +1 -0
  687. app/modules/runtime/api.py +18 -0
  688. app/modules/runtime/schemas.py +14 -0
  689. app/modules/runtime/service.py +218 -0
  690. app/modules/scim/__init__.py +1 -0
  691. app/modules/scim/api.py +194 -0
  692. app/modules/scim/dependencies.py +112 -0
  693. app/modules/scim/errors.py +77 -0
  694. app/modules/scim/management_api.py +156 -0
  695. app/modules/scim/repository.py +212 -0
  696. app/modules/scim/schemas.py +145 -0
  697. app/modules/scim/service.py +786 -0
  698. app/modules/scim/tokens.py +43 -0
  699. app/modules/settings/__init__.py +1 -0
  700. app/modules/settings/api.py +1677 -0
  701. app/modules/settings/repository.py +606 -0
  702. app/modules/settings/schemas.py +496 -0
  703. app/modules/settings/service.py +769 -0
  704. app/modules/shared/__init__.py +0 -0
  705. app/modules/shared/schemas.py +22 -0
  706. app/modules/sticky_sessions/__init__.py +1 -0
  707. app/modules/sticky_sessions/api.py +118 -0
  708. app/modules/sticky_sessions/cleanup_scheduler.py +487 -0
  709. app/modules/sticky_sessions/schemas.py +78 -0
  710. app/modules/sticky_sessions/service.py +196 -0
  711. app/modules/telemetry/__init__.py +5 -0
  712. app/modules/telemetry/api.py +115 -0
  713. app/modules/telemetry/clients.py +62 -0
  714. app/modules/telemetry/consent.py +115 -0
  715. app/modules/telemetry/scheduler.py +85 -0
  716. app/modules/telemetry/schemas.py +163 -0
  717. app/modules/telemetry/sender.py +221 -0
  718. app/modules/telemetry/snapshot.py +490 -0
  719. app/modules/usage/__init__.py +0 -0
  720. app/modules/usage/additional_quota_keys.py +379 -0
  721. app/modules/usage/api.py +45 -0
  722. app/modules/usage/background_repository.py +147 -0
  723. app/modules/usage/builders.py +559 -0
  724. app/modules/usage/depletion_service.py +277 -0
  725. app/modules/usage/live_ingest.py +356 -0
  726. app/modules/usage/mappers.py +29 -0
  727. app/modules/usage/plan_downgrade_observations.py +488 -0
  728. app/modules/usage/repository.py +1890 -0
  729. app/modules/usage/schemas.py +68 -0
  730. app/modules/usage/service.py +102 -0
  731. app/modules/usage/updater.py +1488 -0
  732. app/static/assets/Trans-Cj-J9mVA.js +1 -0
  733. app/static/assets/access-page-DHzZiXZi.js +1 -0
  734. app/static/assets/account-cost-donut-DGhfA4U8.js +1 -0
  735. app/static/assets/account-identifiers-CxbdLshZ.js +1 -0
  736. app/static/assets/account-status-DIsU5sUS.js +1 -0
  737. app/static/assets/account-trend-chart-BptlfwwQ.js +1 -0
  738. app/static/assets/accounts-page-Bbai8ift.js +3 -0
  739. app/static/assets/api-Dwa7XxPe.js +1 -0
  740. app/static/assets/api-client-dugX1Pti.js +55 -0
  741. app/static/assets/api-key-create-dialog-DnRm0oYN.js +1 -0
  742. app/static/assets/api-key-created-dialog-B6jkDG9f.js +1 -0
  743. app/static/assets/api-key-edit-dialog-Ck3Bnc3V.js +1 -0
  744. app/static/assets/api-trend-chart-BchCw7Hj.js +1 -0
  745. app/static/assets/apis-page-BvXz9c6i.js +2 -0
  746. app/static/assets/arrow-up-Dqh_KzJo.js +1 -0
  747. app/static/assets/arrow-up-down-LBRgVDGY.js +1 -0
  748. app/static/assets/automations-page-DQgNAJOR.js +3 -0
  749. app/static/assets/badge-DqMb9es0.js +1 -0
  750. app/static/assets/blur-email-HEvKagG2.js +1 -0
  751. app/static/assets/button-BjVmmiGW.js +1 -0
  752. app/static/assets/checkbox-apsl9vzk.js +1 -0
  753. app/static/assets/chevron-left-1d_pHVa5.js +1 -0
  754. app/static/assets/circle-alert-BS0xd_o1.js +1 -0
  755. app/static/assets/clsx-DB0hHKMi.js +1 -0
  756. app/static/assets/colors-BBy8GEtr.js +1 -0
  757. app/static/assets/copy-button-D7nAvOKR.js +1 -0
  758. app/static/assets/cost-per-day-chart-YdqlkZVM.js +1 -0
  759. app/static/assets/createLucideIcon-DBLzLNh2.js +1 -0
  760. app/static/assets/dashboard-page-CSdhBy0f.js +2 -0
  761. app/static/assets/distribution-metric-format-yZTy1ZEu.js +1 -0
  762. app/static/assets/donut-chart-Bxc69cra.js +1 -0
  763. app/static/assets/download-kQteIcsB.js +1 -0
  764. app/static/assets/ellipsis-B_5OgIre.js +1 -0
  765. app/static/assets/empty-state-JcA7tc8m.js +1 -0
  766. app/static/assets/es2015-bMH8YKZT.js +41 -0
  767. app/static/assets/es6-B264ni6W.js +69 -0
  768. app/static/assets/external-link-UVAOgMQ0.js +1 -0
  769. app/static/assets/i18n-BWqZ9Ua2.js +1 -0
  770. app/static/assets/inbox-B_RM2s1J.js +1 -0
  771. app/static/assets/index-DIpd264Z.js +23 -0
  772. app/static/assets/index-DMeLKRJx.css +2 -0
  773. app/static/assets/inherit-badge-QhTYVaU6.js +1 -0
  774. app/static/assets/invite-dialog-0CYh0GiQ.js +1 -0
  775. app/static/assets/iso-Bpe5Ihbb.js +1 -0
  776. app/static/assets/label-CGaLmLNf.js +1 -0
  777. app/static/assets/lazy-recharts-Pv8p9ArM.js +2 -0
  778. app/static/assets/loading-overlay-CzKQfHLI.js +1 -0
  779. app/static/assets/mini-quota-bar-cGT_t8Ae.js +1 -0
  780. app/static/assets/model-distribution-donut-BKjAfT48.js +1 -0
  781. app/static/assets/model-source-multi-select-D2d06Vc7.js +1 -0
  782. app/static/assets/multi-select-filter-KIA5qU6Z.js +1 -0
  783. app/static/assets/oauth-dialog-XeaGGDjS.js +1 -0
  784. app/static/assets/pagination-controls-D1Ypd0RU.js +1 -0
  785. app/static/assets/payload-CIYnGZz9.js +1 -0
  786. app/static/assets/queue-wait-chart-VoV7Yg9L.js +1 -0
  787. app/static/assets/report-chart-card-BXDliwcc.js +1 -0
  788. app/static/assets/reports-page-CGCJeMHG.js +3 -0
  789. app/static/assets/reset-credit-confirm-dialog-QOifTnVB.js +1 -0
  790. app/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  791. app/static/assets/search-HfhqwERt.js +1 -0
  792. app/static/assets/select-Cl33bz-g.js +1 -0
  793. app/static/assets/settings-page-mZj01HYl.js +2 -0
  794. app/static/assets/shim-CZwyM4Q7.js +1 -0
  795. app/static/assets/skeleton-C_EUc4Rp.js +1 -0
  796. app/static/assets/sparkline-chart-DdNNsddM.js +1 -0
  797. app/static/assets/status-badge-CHHA5X6c.js +1 -0
  798. app/static/assets/switch-CzxgokcM.js +1 -0
  799. app/static/assets/table-DXaNSQ0G.js +1 -0
  800. app/static/assets/time-to-first-token-chart--68L0u_v.js +1 -0
  801. app/static/assets/tokens-per-day-chart-Cs82BFuE.js +1 -0
  802. app/static/assets/tokens-per-second-chart-AD2dfOn6.js +1 -0
  803. app/static/assets/totp-settings-CRHtxQ7j.js +1 -0
  804. app/static/assets/trash-D3KyWr1h.js +1 -0
  805. app/static/assets/use-account-quota-display-BPmrTBTC.js +1 -0
  806. app/static/assets/use-accounts-PWlVGw3z.js +1 -0
  807. app/static/assets/use-auth-CuDlW3iC.js +1 -0
  808. app/static/assets/use-chart-colors-DIu6tClI.js +1 -0
  809. app/static/assets/use-model-sources-Bul24JBu.js +1 -0
  810. app/static/assets/use-models-DUCPUuZg.js +1 -0
  811. app/static/assets/use-reduced-motion-hAm5r6_d.js +1 -0
  812. app/static/assets/useTranslation--56nHhlH.js +1 -0
  813. app/static/assets/useragent-distribution-donut-Dup6sf_n.js +1 -0
  814. app/static/assets/users-uIC3TIlE.js +1 -0
  815. app/static/assets/vendor-query-_bHZuxEx.js +1 -0
  816. app/static/assets/vendor-react-BVL0YZPW.js +9 -0
  817. app/static/favicon.svg +10 -0
  818. app/static/fonts/GeistSans-Variable.woff2 +0 -0
  819. app/static/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  820. app/static/fonts/jetbrains-mono-latin.woff2 +0 -0
  821. app/static/index.html +29 -0
  822. codexlb_up-1.25.0b10.dist-info/METADATA +124 -0
  823. codexlb_up-1.25.0b10.dist-info/RECORD +827 -0
  824. codexlb_up-1.25.0b10.dist-info/WHEEL +4 -0
  825. codexlb_up-1.25.0b10.dist-info/entry_points.txt +3 -0
  826. codexlb_up-1.25.0b10.dist-info/licenses/LICENSE +21 -0
  827. 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
+ )