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
@@ -0,0 +1,140 @@
1
+ """add subscription overflow designation and model source pins
2
+
3
+ Schema-only groundwork for subscription-exhaustion overflow to a designated
4
+ model source (#2123, WP-A): two nullable ``dashboard_settings`` columns (the
5
+ designated source id and the drain deadline armed when that designation is
6
+ cleared) and the ``model_source_pins`` table later stages use to keep
7
+ conversations sticky to the source they overflowed to. Nothing reads the new
8
+ columns or table yet; this revision is inert.
9
+
10
+ Revision ID: 20260908_000000_add_subscription_overflow
11
+ Revises: 20260830_000000_add_quota_warmup_claim_expiry
12
+ Create Date: 2026-09-08
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import sqlalchemy as sa
18
+ from alembic import op
19
+ from sqlalchemy.engine import Connection
20
+ from sqlalchemy.engine.interfaces import ReflectedIndex
21
+
22
+ revision = "20260908_000000_add_subscription_overflow"
23
+ down_revision = "20260830_000000_add_quota_warmup_claim_expiry"
24
+ branch_labels = None
25
+ depends_on = None
26
+
27
+ _SETTINGS_TABLE = "dashboard_settings"
28
+ _PINS_TABLE = "model_source_pins"
29
+ _PINS_INDEX = "ix_model_source_pins_purge_at"
30
+
31
+
32
+ def _settings_columns() -> tuple[sa.Column, ...]:
33
+ # Fresh Column objects per call: a Column binds to the table it is added to,
34
+ # so module-level instances could not be reused across upgrades in one process.
35
+ return (
36
+ # No foreign key on purpose: a dangling id means "off" (precedent:
37
+ # single_account_id).
38
+ sa.Column("subscription_overflow_source_id", sa.String(), nullable=True),
39
+ # Naive UTC like every other dashboard_settings timestamp; the app
40
+ # compares it against utcnow().
41
+ sa.Column("subscription_overflow_drain_until", sa.DateTime(), nullable=True),
42
+ )
43
+
44
+
45
+ def _column_names(connection: Connection, table_name: str) -> set[str]:
46
+ inspector = sa.inspect(connection)
47
+ if not inspector.has_table(table_name):
48
+ return set()
49
+ return {str(column["name"]) for column in inspector.get_columns(table_name) if column.get("name") is not None}
50
+
51
+
52
+ def _reflected_index(connection: Connection, table_name: str, index_name: str) -> ReflectedIndex | None:
53
+ inspector = sa.inspect(connection)
54
+ if not inspector.has_table(table_name):
55
+ return None
56
+ for index in inspector.get_indexes(table_name):
57
+ if index.get("name") == index_name:
58
+ return index
59
+ return None
60
+
61
+
62
+ def _index_targets_purge_at(index: ReflectedIndex) -> bool:
63
+ # A same-named index on other columns (or a unique one) is a decoy: it must
64
+ # be replaced, not accepted by name.
65
+ return tuple(index.get("column_names") or ()) == ("purge_at",) and not index.get("unique")
66
+
67
+
68
+ def _postgresql_index_is_invalid(connection: Connection, index_name: str) -> bool:
69
+ return bool(
70
+ connection.execute(
71
+ sa.text(
72
+ "SELECT 1 FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid "
73
+ "WHERE c.relname = :name AND NOT i.indisvalid"
74
+ ),
75
+ {"name": index_name},
76
+ ).scalar()
77
+ )
78
+
79
+
80
+ def upgrade() -> None:
81
+ bind = op.get_bind()
82
+
83
+ existing_columns = _column_names(bind, _SETTINGS_TABLE)
84
+ missing_columns = [column for column in _settings_columns() if column.name not in existing_columns]
85
+ if existing_columns and missing_columns:
86
+ with op.batch_alter_table(_SETTINGS_TABLE) as batch_op:
87
+ for column in missing_columns:
88
+ batch_op.add_column(column)
89
+
90
+ if not sa.inspect(bind).has_table(_PINS_TABLE):
91
+ op.create_table(
92
+ _PINS_TABLE,
93
+ sa.Column("pin_key", sa.String(), nullable=False),
94
+ # thread | anchor | bounce, a plain string: the routing stage owns the values.
95
+ sa.Column("kind", sa.String(), nullable=False),
96
+ # No foreign key on purpose: rows must outlive a deleted source for
97
+ # the drain window instead of cascading away.
98
+ sa.Column("source_id", sa.String(), nullable=False),
99
+ sa.Column("api_key_id", sa.String(), nullable=True),
100
+ # Timezone-aware like file_account_pins: the database clock is
101
+ # authoritative for pin expiry and tombstone comparisons.
102
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
103
+ sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False),
104
+ sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
105
+ sa.Column("purge_at", sa.DateTime(timezone=True), nullable=False),
106
+ sa.PrimaryKeyConstraint("pin_key"),
107
+ )
108
+
109
+ # Guarded independently of the table step: a database where the table
110
+ # already exists (partial dump, out-of-band creation) still needs the
111
+ # purge-at index. The table is created empty by this same revision, so a
112
+ # plain in-transaction index suffices; CONCURRENTLY would buy nothing.
113
+ # An existing same-named index is only accepted when it is the index this
114
+ # revision would create: one on exactly ``purge_at`` that is, on
115
+ # PostgreSQL, not left invalid by an interrupted out-of-band CONCURRENTLY
116
+ # build. Anything else is dropped and rebuilt.
117
+ existing_index = _reflected_index(bind, _PINS_TABLE, _PINS_INDEX)
118
+ if existing_index is not None:
119
+ if _index_targets_purge_at(existing_index) and (
120
+ bind.dialect.name != "postgresql" or not _postgresql_index_is_invalid(bind, _PINS_INDEX)
121
+ ):
122
+ return
123
+ op.drop_index(_PINS_INDEX, table_name=_PINS_TABLE)
124
+ op.create_index(_PINS_INDEX, _PINS_TABLE, ["purge_at"], unique=False)
125
+
126
+
127
+ def downgrade() -> None:
128
+ bind = op.get_bind()
129
+
130
+ if sa.inspect(bind).has_table(_PINS_TABLE):
131
+ if _reflected_index(bind, _PINS_TABLE, _PINS_INDEX) is not None:
132
+ op.drop_index(_PINS_INDEX, table_name=_PINS_TABLE)
133
+ op.drop_table(_PINS_TABLE)
134
+
135
+ existing_columns = _column_names(bind, _SETTINGS_TABLE)
136
+ present_columns = [column.name for column in _settings_columns() if column.name in existing_columns]
137
+ if present_columns:
138
+ with op.batch_alter_table(_SETTINGS_TABLE) as batch_op:
139
+ for column_name in present_columns:
140
+ batch_op.drop_column(column_name)
@@ -0,0 +1,50 @@
1
+ """Replace the upstream_stream_transport "default" sentinel with "auto".
2
+
3
+ Revision ID: 20260908_000000_replace_upstream_stream_transport_default_sentinel
4
+ Revises: 20260830_000000_add_quota_warmup_claim_expiry
5
+ Create Date: 2026-09-08
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+
13
+ revision = "20260908_000000_replace_upstream_stream_transport_default_sentinel"
14
+ down_revision = "20260830_000000_add_quota_warmup_claim_expiry"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ # "default" used to mean "defer to CODEX_LB_UPSTREAM_STREAM_TRANSPORT",
21
+ # whose own default was "auto". The env var is removed and the dashboard
22
+ # row is the only source, so the sentinel maps to the documented default.
23
+ # A migration cannot read the environment the removed variable was set in;
24
+ # an operator who pinned the env var to http/websocket must re-pin it in
25
+ # the dashboard (release notes).
26
+ op.execute(
27
+ sa.text(
28
+ "UPDATE dashboard_settings SET upstream_stream_transport = 'auto' "
29
+ "WHERE upstream_stream_transport = 'default'"
30
+ )
31
+ )
32
+ with op.batch_alter_table("dashboard_settings") as batch_op:
33
+ batch_op.alter_column(
34
+ "upstream_stream_transport",
35
+ existing_type=sa.String(),
36
+ existing_nullable=False,
37
+ server_default=sa.text("'auto'"),
38
+ )
39
+
40
+
41
+ def downgrade() -> None:
42
+ # "auto" is a valid value in the previous schema too, so rows are left as
43
+ # they are; only the server default is restored.
44
+ with op.batch_alter_table("dashboard_settings") as batch_op:
45
+ batch_op.alter_column(
46
+ "upstream_stream_transport",
47
+ existing_type=sa.String(),
48
+ existing_nullable=False,
49
+ server_default=sa.text("'default'"),
50
+ )
@@ -0,0 +1,27 @@
1
+ """Merge the subscription-overflow and transport-sentinel heads.
2
+
3
+ Revision ID: 20260908_020000_merge_overflow_transport_heads
4
+ Revises: 20260908_000000_add_subscription_overflow,
5
+ 20260908_000000_replace_upstream_stream_transport_default_sentinel
6
+ Create Date: 2026-09-08
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ revision = "20260908_020000_merge_overflow_transport_heads"
12
+ down_revision = (
13
+ "20260908_000000_add_subscription_overflow",
14
+ "20260908_000000_replace_upstream_stream_transport_default_sentinel",
15
+ )
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ """Join revision tracking after both parents have completed."""
22
+ pass
23
+
24
+
25
+ def downgrade() -> None:
26
+ """Restore both parent stamps without changing their schema or data."""
27
+ pass
@@ -0,0 +1,72 @@
1
+ """Add dashboard_roles and dashboard_role_grants with the five preset rows.
2
+
3
+ Revision ID: 20260909_000000_add_dashboard_roles
4
+ Revises: 20260908_000000_add_guest_session_generation
5
+ Create Date: 2026-09-09
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+
13
+ from app.modules.dashboard_roles.seed import seed_preset_dashboard_roles
14
+
15
+ revision = "20260909_000000_add_dashboard_roles"
16
+ down_revision = "20260908_000000_add_guest_session_generation"
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def _roles_table(metadata: sa.MetaData) -> sa.Table:
22
+ return sa.Table(
23
+ "dashboard_roles",
24
+ metadata,
25
+ sa.Column("id", sa.String(), primary_key=True),
26
+ sa.Column("slug", sa.String(length=32), nullable=False, unique=True),
27
+ sa.Column("name", sa.String(length=64), nullable=False),
28
+ sa.Column("description", sa.Text(), nullable=True),
29
+ sa.Column("kind", sa.String(length=16), nullable=False),
30
+ sa.Column("assignable_to_users", sa.Boolean(), server_default=sa.true(), nullable=False),
31
+ sa.Column(
32
+ "cloned_from_role_id",
33
+ sa.String(),
34
+ sa.ForeignKey("dashboard_roles.id", ondelete="SET NULL"),
35
+ nullable=True,
36
+ ),
37
+ sa.Column("permissions_version", sa.Integer(), server_default=sa.text("1"), nullable=False),
38
+ sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
39
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
40
+ )
41
+
42
+
43
+ def upgrade() -> None:
44
+ bind = op.get_bind()
45
+ inspector = sa.inspect(bind)
46
+ metadata = sa.MetaData()
47
+ roles = _roles_table(metadata)
48
+ if not inspector.has_table("dashboard_roles"):
49
+ roles.create(bind)
50
+ if not inspector.has_table("dashboard_role_grants"):
51
+ op.create_table(
52
+ "dashboard_role_grants",
53
+ sa.Column(
54
+ "role_id",
55
+ sa.String(),
56
+ sa.ForeignKey("dashboard_roles.id", ondelete="CASCADE"),
57
+ primary_key=True,
58
+ ),
59
+ sa.Column("permission", sa.String(length=64), primary_key=True),
60
+ sa.Column("scope", sa.String(length=8), nullable=False),
61
+ )
62
+ # Outside the table guard so a re-run after a partial failure still seeds.
63
+ seed_preset_dashboard_roles(bind, roles)
64
+
65
+
66
+ def downgrade() -> None:
67
+ bind = op.get_bind()
68
+ inspector = sa.inspect(bind)
69
+ if inspector.has_table("dashboard_role_grants"):
70
+ op.drop_table("dashboard_role_grants")
71
+ if inspector.has_table("dashboard_roles"):
72
+ op.drop_table("dashboard_roles")
@@ -0,0 +1,183 @@
1
+ """Add dashboard_users, dashboard_identities, API key ownership columns, and backfill the compat admin.
2
+
3
+ Revision ID: 20260909_010000_add_dashboard_users
4
+ Revises: 20260909_000000_add_dashboard_roles
5
+ Create Date: 2026-09-09
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+
13
+ revision = "20260909_010000_add_dashboard_users"
14
+ down_revision = "20260909_000000_add_dashboard_roles"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+ # Frozen copies of the runtime identifiers (see app.db.models and
19
+ # app.core.auth.dashboard_access). A migration must not import runtime modules,
20
+ # which may move or go; the unit test
21
+ # tests/unit/test_migration_identifier_literals.py asserts these stay in sync
22
+ # with the constants they were copied from.
23
+ COMPAT_ADMIN_USER_ID = "7a4fc02d-216e-5be7-b974-bc437f23df60"
24
+ COMPAT_ADMIN_USERNAME = "admin"
25
+ ADMIN_ROLE_ID = "3fe7dc57-aabd-5b16-9850-b6d464087f07"
26
+
27
+ _API_KEY_COLUMNS = ("owner_user_id", "created_by_user_id", "deactivated_reason")
28
+
29
+
30
+ def _columns(bind: sa.engine.Connection, table: str) -> set[str]:
31
+ inspector = sa.inspect(bind)
32
+ if not inspector.has_table(table):
33
+ return set()
34
+ return {column["name"] for column in inspector.get_columns(table)}
35
+
36
+
37
+ def upgrade() -> None:
38
+ bind = op.get_bind()
39
+ inspector = sa.inspect(bind)
40
+
41
+ if not inspector.has_table("dashboard_users"):
42
+ op.create_table(
43
+ "dashboard_users",
44
+ sa.Column("id", sa.String(), primary_key=True),
45
+ sa.Column("username", sa.String(length=64), nullable=False, unique=True),
46
+ sa.Column("display_name", sa.String(length=128), nullable=True),
47
+ sa.Column("email", sa.String(length=320), nullable=True, unique=True),
48
+ sa.Column("role_id", sa.String(), sa.ForeignKey("dashboard_roles.id", ondelete="RESTRICT"), nullable=False),
49
+ sa.Column("role_source", sa.String(length=16), server_default=sa.text("'manual'"), nullable=False),
50
+ sa.Column("status", sa.String(length=16), server_default=sa.text("'active'"), nullable=False),
51
+ sa.Column("password_hash", sa.Text(), nullable=True),
52
+ sa.Column("totp_secret_encrypted", sa.LargeBinary(), nullable=True),
53
+ sa.Column("totp_last_verified_step", sa.Integer(), nullable=True),
54
+ sa.Column("session_generation", sa.Integer(), server_default=sa.text("0"), nullable=False),
55
+ sa.Column("must_change_password", sa.Boolean(), server_default=sa.false(), nullable=False),
56
+ sa.Column("is_break_glass", sa.Boolean(), server_default=sa.false(), nullable=False),
57
+ sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
58
+ sa.Column(
59
+ "created_by_user_id",
60
+ sa.String(),
61
+ sa.ForeignKey("dashboard_users.id", ondelete="SET NULL"),
62
+ nullable=True,
63
+ ),
64
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
65
+ sa.Column("last_login_at", sa.DateTime(), nullable=True),
66
+ )
67
+ if not inspector.has_table("dashboard_identities"):
68
+ op.create_table(
69
+ "dashboard_identities",
70
+ sa.Column("id", sa.String(), primary_key=True),
71
+ sa.Column("user_id", sa.String(), sa.ForeignKey("dashboard_users.id", ondelete="CASCADE"), nullable=False),
72
+ sa.Column("provider", sa.String(length=32), nullable=False),
73
+ sa.Column("provider_key", sa.String(length=128), nullable=False),
74
+ sa.Column("subject", sa.String(length=512), nullable=False),
75
+ sa.Column("email", sa.String(length=320), nullable=True),
76
+ sa.Column("display_name", sa.String(length=128), nullable=True),
77
+ sa.Column("groups_json", sa.Text(), nullable=True),
78
+ sa.Column("last_seen_at", sa.DateTime(), nullable=True),
79
+ sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
80
+ sa.UniqueConstraint("provider", "provider_key", "subject", name="uq_dashboard_identities_subject"),
81
+ )
82
+ op.create_index("idx_dashboard_identities_user_id", "dashboard_identities", ["user_id"])
83
+
84
+ existing = _columns(bind, "api_keys")
85
+ missing = [name for name in _API_KEY_COLUMNS if name not in existing]
86
+ if missing:
87
+ with op.batch_alter_table("api_keys") as batch_op:
88
+ if "owner_user_id" in missing:
89
+ batch_op.add_column(sa.Column("owner_user_id", sa.String(), nullable=True))
90
+ batch_op.create_foreign_key(
91
+ "fk_api_keys_owner_user_id",
92
+ "dashboard_users",
93
+ ["owner_user_id"],
94
+ ["id"],
95
+ ondelete="SET NULL",
96
+ )
97
+ if "created_by_user_id" in missing:
98
+ batch_op.add_column(sa.Column("created_by_user_id", sa.String(), nullable=True))
99
+ batch_op.create_foreign_key(
100
+ "fk_api_keys_created_by_user_id",
101
+ "dashboard_users",
102
+ ["created_by_user_id"],
103
+ ["id"],
104
+ ondelete="SET NULL",
105
+ )
106
+ if "deactivated_reason" in missing:
107
+ batch_op.add_column(sa.Column("deactivated_reason", sa.String(length=32), nullable=True))
108
+
109
+ # Backfill: the legacy shared admin password becomes the `admin` user. Runs
110
+ # outside the table guards so a re-run after a partial failure still
111
+ # migrates the credential. Idempotent on the deterministic user id and on
112
+ # the unique username.
113
+ settings_row = bind.execute(
114
+ sa.text(
115
+ "SELECT password_hash, totp_secret_encrypted, totp_last_verified_step FROM dashboard_settings WHERE id = 1"
116
+ )
117
+ ).first()
118
+ if settings_row is not None and settings_row[0] is not None:
119
+ existing_admin = bind.execute(
120
+ sa.text("SELECT id FROM dashboard_users WHERE id = :id OR username = :username"),
121
+ {"id": COMPAT_ADMIN_USER_ID, "username": COMPAT_ADMIN_USERNAME},
122
+ ).first()
123
+ if existing_admin is None:
124
+ bind.execute(
125
+ sa.text(
126
+ "INSERT INTO dashboard_users "
127
+ "(id, username, display_name, role_id, role_source, status, password_hash, "
128
+ "totp_secret_encrypted, totp_last_verified_step, session_generation, "
129
+ "must_change_password, is_break_glass, created_at, updated_at) "
130
+ "VALUES (:id, :username, NULL, :role_id, 'manual', 'active', :password_hash, "
131
+ ":totp_secret, :totp_step, 0, :false_value, :true_value, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
132
+ ),
133
+ {
134
+ "id": COMPAT_ADMIN_USER_ID,
135
+ "username": COMPAT_ADMIN_USERNAME,
136
+ "role_id": ADMIN_ROLE_ID,
137
+ "password_hash": settings_row[0],
138
+ "totp_secret": settings_row[1],
139
+ "totp_step": settings_row[2],
140
+ "false_value": False,
141
+ "true_value": True,
142
+ },
143
+ )
144
+
145
+
146
+ def _user_fk_names(inspector: sa.Inspector, column: str) -> list[str]:
147
+ """Names of api_keys foreign keys from ``column`` to dashboard_users.
148
+
149
+ The constraint may carry this revision's explicit name or a dialect
150
+ default (a schema built by ``Base.metadata.create_all`` on PostgreSQL
151
+ names it ``api_keys_<column>_fkey``); unnamed SQLite constraints are
152
+ dropped together with the column by the batch table rebuild.
153
+ """
154
+
155
+ names: list[str] = []
156
+ for fk in inspector.get_foreign_keys("api_keys"):
157
+ name = fk.get("name")
158
+ if (
159
+ isinstance(name, str)
160
+ and fk.get("referred_table") == "dashboard_users"
161
+ and column in fk.get("constrained_columns", ())
162
+ ):
163
+ names.append(name)
164
+ return names
165
+
166
+
167
+ def downgrade() -> None:
168
+ bind = op.get_bind()
169
+ inspector = sa.inspect(bind)
170
+ existing = _columns(bind, "api_keys")
171
+ if existing & set(_API_KEY_COLUMNS):
172
+ with op.batch_alter_table("api_keys") as batch_op:
173
+ for column in ("owner_user_id", "created_by_user_id"):
174
+ if column in existing:
175
+ for fk_name in _user_fk_names(inspector, column):
176
+ batch_op.drop_constraint(fk_name, type_="foreignkey")
177
+ batch_op.drop_column(column)
178
+ if "deactivated_reason" in existing:
179
+ batch_op.drop_column("deactivated_reason")
180
+ if inspector.has_table("dashboard_identities"):
181
+ op.drop_table("dashboard_identities")
182
+ if inspector.has_table("dashboard_users"):
183
+ op.drop_table("dashboard_users")
@@ -0,0 +1,105 @@
1
+ """Re-project the legacy dashboard credential onto the compat ``admin`` user once more.
2
+
3
+ Revision ID: 20260909_020000_reproject_compat_admin_credentials
4
+ Revises: 20260909_010000_add_dashboard_users
5
+ Create Date: 2026-09-09
6
+
7
+ Through the previous release the legacy ``dashboard_settings`` columns were the
8
+ authoritative credential and the ``admin`` user row a mirror; a replica still
9
+ running that release during a rolling upgrade may have changed the password or
10
+ TOTP secret on the legacy row only. This data-only revision copies the legacy
11
+ credential onto the user row one last time before the user row becomes the
12
+ source of truth (and the legacy columns become the mirror). Downgrade is a
13
+ no-op: nothing structural changes.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import sqlalchemy as sa
19
+ from alembic import op
20
+
21
+ revision = "20260909_020000_reproject_compat_admin_credentials"
22
+ down_revision = "20260909_010000_add_dashboard_users"
23
+ branch_labels = None
24
+ depends_on = None
25
+
26
+ # Frozen copies of the runtime identifiers (see 20260909_010000): a migration
27
+ # must not import transient application modules. The preset role rows are
28
+ # guaranteed by 20260909_000000 earlier in this chain.
29
+ COMPAT_ADMIN_USER_ID = "7a4fc02d-216e-5be7-b974-bc437f23df60"
30
+ COMPAT_ADMIN_USERNAME = "admin"
31
+ ADMIN_ROLE_ID = "3fe7dc57-aabd-5b16-9850-b6d464087f07"
32
+
33
+
34
+ def upgrade() -> None:
35
+ bind = op.get_bind()
36
+ inspector = sa.inspect(bind)
37
+ if not inspector.has_table("dashboard_settings") or not inspector.has_table("dashboard_users"):
38
+ return
39
+
40
+ settings_row = bind.execute(
41
+ sa.text(
42
+ "SELECT password_hash, totp_secret_encrypted, totp_last_verified_step FROM dashboard_settings WHERE id = 1"
43
+ )
44
+ ).first()
45
+ if settings_row is None:
46
+ return
47
+ legacy_password_hash, legacy_totp_secret, legacy_totp_step = settings_row
48
+
49
+ existing_admin = bind.execute(
50
+ sa.text("SELECT id FROM dashboard_users WHERE id = :id OR username = :username"),
51
+ {"id": COMPAT_ADMIN_USER_ID, "username": COMPAT_ADMIN_USERNAME},
52
+ ).first()
53
+
54
+ if legacy_password_hash is None:
55
+ # The previous release removed the password: the compat row (if any)
56
+ # must be credential-less too. The row itself stays so the account
57
+ # keeps its identity and settings.
58
+ if existing_admin is not None:
59
+ bind.execute(
60
+ sa.text(
61
+ "UPDATE dashboard_users SET password_hash = NULL, totp_secret_encrypted = NULL, "
62
+ "totp_last_verified_step = NULL WHERE id = :id"
63
+ ),
64
+ {"id": existing_admin[0]},
65
+ )
66
+ return
67
+
68
+ credential = {
69
+ "password_hash": legacy_password_hash,
70
+ "totp_secret": legacy_totp_secret,
71
+ "totp_step": legacy_totp_step,
72
+ }
73
+ if existing_admin is not None:
74
+ bind.execute(
75
+ sa.text(
76
+ "UPDATE dashboard_users SET password_hash = :password_hash, totp_secret_encrypted = :totp_secret, "
77
+ "totp_last_verified_step = :totp_step WHERE id = :id"
78
+ ),
79
+ {**credential, "id": existing_admin[0]},
80
+ )
81
+ return
82
+
83
+ bind.execute(
84
+ sa.text(
85
+ "INSERT INTO dashboard_users "
86
+ "(id, username, display_name, role_id, role_source, status, password_hash, "
87
+ "totp_secret_encrypted, totp_last_verified_step, session_generation, "
88
+ "must_change_password, is_break_glass, created_at, updated_at) "
89
+ "VALUES (:id, :username, NULL, :role_id, 'manual', 'active', :password_hash, "
90
+ ":totp_secret, :totp_step, 0, :false_value, :true_value, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
91
+ ),
92
+ {
93
+ **credential,
94
+ "id": COMPAT_ADMIN_USER_ID,
95
+ "username": COMPAT_ADMIN_USERNAME,
96
+ "role_id": ADMIN_ROLE_ID,
97
+ "false_value": False,
98
+ "true_value": True,
99
+ },
100
+ )
101
+
102
+
103
+ def downgrade() -> None:
104
+ # Data-only re-projection; the previous revision's downgrade drops the tables.
105
+ return
@@ -0,0 +1,103 @@
1
+ """Add actor, target and severity columns to audit_logs.
2
+
3
+ Revision ID: 20260909_030000_add_audit_actor_columns
4
+ Revises: 20260909_020000_reproject_compat_admin_credentials
5
+ Create Date: 2026-09-09
6
+
7
+ The actor columns are a snapshot (no foreign key) so an audit row outlives the
8
+ account it names. Existing rows keep NULL actor/target columns and receive
9
+ ``severity='info'`` through the server default.
10
+
11
+ On SQLite the ``timestamp`` text of rows written through the database default
12
+ (``CURRENT_TIMESTAMP``, second granularity) is padded once to the
13
+ microsecond form the ORM writes and binds, so time-window predicates and
14
+ same-second ordering compare consistently. Representation only; the instant
15
+ does not change.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import sqlalchemy as sa
21
+ from alembic import op
22
+
23
+ revision = "20260909_030000_add_audit_actor_columns"
24
+ down_revision = "20260909_020000_reproject_compat_admin_credentials"
25
+ branch_labels = None
26
+ depends_on = None
27
+
28
+ _TABLE = "audit_logs"
29
+ _COLUMN_NAMES: tuple[str, ...] = (
30
+ "actor_user_id",
31
+ "actor_username",
32
+ "actor_role_slug",
33
+ "auth_method",
34
+ "target_type",
35
+ "target_id",
36
+ "severity",
37
+ )
38
+ _INDEXES: tuple[tuple[str, tuple[str, ...]], ...] = (
39
+ ("idx_audit_logs_actor_user_id", ("actor_user_id",)),
40
+ ("idx_audit_logs_target", ("target_type", "target_id")),
41
+ )
42
+
43
+
44
+ def _new_columns() -> tuple[sa.Column, ...]:
45
+ """Fresh Column objects per call: a Column may be bound to one table only."""
46
+
47
+ return (
48
+ sa.Column("actor_user_id", sa.String(length=36), nullable=True),
49
+ sa.Column("actor_username", sa.String(length=64), nullable=True),
50
+ sa.Column("actor_role_slug", sa.String(length=32), nullable=True),
51
+ sa.Column("auth_method", sa.String(length=32), nullable=True),
52
+ sa.Column("target_type", sa.String(length=32), nullable=True),
53
+ sa.Column("target_id", sa.String(length=128), nullable=True),
54
+ sa.Column("severity", sa.String(length=16), nullable=False, server_default=sa.text("'info'")),
55
+ )
56
+
57
+
58
+ def _columns(inspector: sa.Inspector) -> set[str]:
59
+ if not inspector.has_table(_TABLE):
60
+ return set()
61
+ return {column["name"] for column in inspector.get_columns(_TABLE)}
62
+
63
+
64
+ def _indexes(inspector: sa.Inspector) -> set[str]:
65
+ if not inspector.has_table(_TABLE):
66
+ return set()
67
+ return {name for index in inspector.get_indexes(_TABLE) if isinstance(name := index.get("name"), str)}
68
+
69
+
70
+ def upgrade() -> None:
71
+ bind = op.get_bind()
72
+ inspector = sa.inspect(bind)
73
+ if not inspector.has_table(_TABLE):
74
+ return
75
+ existing_columns = _columns(inspector)
76
+ missing = [column for column in _new_columns() if column.name not in existing_columns]
77
+ if missing:
78
+ with op.batch_alter_table(_TABLE) as batch_op:
79
+ for column in missing:
80
+ batch_op.add_column(column)
81
+ existing_indexes = _indexes(sa.inspect(bind))
82
+ for name, columns in _INDEXES:
83
+ if name not in existing_indexes:
84
+ op.create_index(name, _TABLE, list(columns))
85
+ if bind.dialect.name == "sqlite":
86
+ op.execute(sa.text("UPDATE audit_logs SET timestamp = timestamp || '.000000' WHERE timestamp NOT LIKE '%.%'"))
87
+
88
+
89
+ def downgrade() -> None:
90
+ bind = op.get_bind()
91
+ inspector = sa.inspect(bind)
92
+ if not inspector.has_table(_TABLE):
93
+ return
94
+ existing_indexes = _indexes(inspector)
95
+ for name, _ in _INDEXES:
96
+ if name in existing_indexes:
97
+ op.drop_index(name, table_name=_TABLE)
98
+ existing_columns = _columns(inspector)
99
+ present = [name for name in _COLUMN_NAMES if name in existing_columns]
100
+ if present:
101
+ with op.batch_alter_table(_TABLE) as batch_op:
102
+ for name in present:
103
+ batch_op.drop_column(name)