imbi-api 2.32.3__tar.gz → 2.34.0__tar.gz

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 (340) hide show
  1. {imbi_api-2.32.3 → imbi_api-2.34.0}/PKG-INFO +2 -2
  2. {imbi_api-2.32.3 → imbi_api-2.34.0}/pyproject.toml +2 -2
  3. imbi_api-2.34.0/src/imbi/api/commit_pushed_at_repair.py +249 -0
  4. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/dashboard.py +26 -2
  5. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/projects.py +4 -2
  6. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/pull_requests.py +223 -20
  7. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/operations.py +79 -0
  8. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/registry.py +35 -0
  9. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_dashboard.py +19 -0
  10. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_projects_helpers.py +2 -0
  11. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_pull_requests.py +93 -0
  12. imbi_api-2.34.0/tests/test_commit_pushed_at_repair.py +292 -0
  13. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_operations.py +97 -0
  14. imbi_api-2.34.0/tests/test_pull_requests_pending_deploy.py +165 -0
  15. {imbi_api-2.32.3 → imbi_api-2.34.0}/.gitignore +0 -0
  16. {imbi_api-2.32.3 → imbi_api-2.34.0}/README.md +0 -0
  17. {imbi_api-2.32.3 → imbi_api-2.34.0}/moon.yml +0 -0
  18. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/README.md +0 -0
  19. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/__init__.py +0 -0
  20. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/app.py +0 -0
  21. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/__init__.py +0 -0
  22. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/authorization_codes.py +0 -0
  23. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/autonomous.py +0 -0
  24. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/internal_services.py +0 -0
  25. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/local_auth.py +0 -0
  26. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/login_providers.py +0 -0
  27. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/membership.py +0 -0
  28. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/models.py +0 -0
  29. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/oauth.py +0 -0
  30. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/oauth_clients.py +0 -0
  31. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/password.py +0 -0
  32. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/permissions.py +0 -0
  33. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/principals.py +0 -0
  34. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/seed.py +0 -0
  35. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/sessions.py +0 -0
  36. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/tokens.py +0 -0
  37. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/totp.py +0 -0
  38. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/backfill_embeddings.py +0 -0
  39. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/blueprint_attributes.py +0 -0
  40. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/blueprint_compliance.py +0 -0
  41. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/commit_sync/__init__.py +0 -0
  42. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/commit_sync/queue.py +0 -0
  43. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/commit_sync/service.py +0 -0
  44. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/component_facts.py +0 -0
  45. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_status_repair.py +0 -0
  46. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sweeper.py +0 -0
  47. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
  48. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sync/queue.py +0 -0
  49. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sync/service.py +0 -0
  50. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/documents/__init__.py +0 -0
  51. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/documents/read_sweeper.py +0 -0
  52. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/domain/__init__.py +0 -0
  53. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/domain/models.py +0 -0
  54. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/domain/scoring.py +0 -0
  55. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/drift.py +0 -0
  56. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/__init__.py +0 -0
  57. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/client.py +0 -0
  58. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/dependencies.py +0 -0
  59. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/models.py +0 -0
  60. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/base.html +0 -0
  61. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/base.txt +0 -0
  62. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/password_reset.html +0 -0
  63. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
  64. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/welcome.html +0 -0
  65. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/welcome.txt +0 -0
  66. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates.py +0 -0
  67. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/__init__.py +0 -0
  68. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_credentials.py +0 -0
  69. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_events.py +0 -0
  70. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_history.py +0 -0
  71. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_reads.py +0 -0
  72. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
  73. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_helpers.py +0 -0
  74. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
  75. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_pagination.py +0 -0
  76. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
  77. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_search_index.py +0 -0
  78. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/admin.py +0 -0
  79. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
  80. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/ai_models.py +0 -0
  81. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
  82. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/api_keys.py +0 -0
  83. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/auth.py +0 -0
  84. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
  85. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/blueprints.py +0 -0
  86. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
  87. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/comments.py +0 -0
  88. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/components.py +0 -0
  89. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
  90. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_likes.py +0 -0
  91. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_presence.py +0 -0
  92. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_templates.py +0 -0
  93. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_versions.py +0 -0
  94. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/documents.py +0 -0
  95. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/environments.py +0 -0
  96. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/events.py +0 -0
  97. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/graph_query.py +0 -0
  98. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
  99. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/integrations.py +0 -0
  100. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
  101. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/local_auth.py +0 -0
  102. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/maintenance.py +0 -0
  103. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
  104. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/mfa.py +0 -0
  105. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
  106. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/operations_log.py +0 -0
  107. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/organizations.py +0 -0
  108. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
  109. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
  110. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/plugins.py +0 -0
  111. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_analysis.py +0 -0
  112. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
  113. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_configuration.py +0 -0
  114. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_deployments.py +0 -0
  115. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
  116. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
  117. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
  118. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_logs.py +0 -0
  119. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
  120. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
  121. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_types.py +0 -0
  122. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/releases.py +0 -0
  123. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/roles.py +0 -0
  124. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
  125. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/scoring.py +0 -0
  126. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
  127. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/search.py +0 -0
  128. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
  129. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/status.py +0 -0
  130. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/tags.py +0 -0
  131. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/teams.py +0 -0
  132. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/uploads.py +0 -0
  133. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/user_activity.py +0 -0
  134. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/users.py +0 -0
  135. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/webhooks.py +0 -0
  136. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/entrypoint.py +0 -0
  137. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/graph_sql.py +0 -0
  138. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/__init__.py +0 -0
  139. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/attribution.py +0 -0
  140. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/endpoints.py +0 -0
  141. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/errors.py +0 -0
  142. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/flows.py +0 -0
  143. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/host_integration.py +0 -0
  144. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/models.py +0 -0
  145. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/repository.py +0 -0
  146. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/resolution.py +0 -0
  147. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/state.py +0 -0
  148. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/sweeper.py +0 -0
  149. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/lifespans.py +0 -0
  150. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/llm/__init__.py +0 -0
  151. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/llm/dependencies.py +0 -0
  152. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/__init__.py +0 -0
  153. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/log.py +0 -0
  154. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/state.py +0 -0
  155. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/worker.py +0 -0
  156. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/mcp_test.py +0 -0
  157. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/middleware/__init__.py +0 -0
  158. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/middleware/rate_limit.py +0 -0
  159. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/models.py +0 -0
  160. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/openapi.py +0 -0
  161. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/orphan_releases.py +0 -0
  162. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/__init__.py +0 -0
  163. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
  164. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/assignments.py +0 -0
  165. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/credentials.py +0 -0
  166. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/installer.py +0 -0
  167. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/lifecycle.py +0 -0
  168. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/lifecycle_dispatch.py +0 -0
  169. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/reload.py +0 -0
  170. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/resolution.py +0 -0
  171. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/schemas.py +0 -0
  172. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/pr_sync/__init__.py +0 -0
  173. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/pr_sync/queue.py +0 -0
  174. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/pr_sync/service.py +0 -0
  175. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/prompts/__init__.py +0 -0
  176. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/prompts/release_notes_system.md +0 -0
  177. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/py.typed +0 -0
  178. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/relationships.py +0 -0
  179. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/release_promote/__init__.py +0 -0
  180. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/release_promote/queue.py +0 -0
  181. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/release_promote/service.py +0 -0
  182. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/sbom.py +0 -0
  183. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/scoring/__init__.py +0 -0
  184. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/scoring/queue.py +0 -0
  185. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/settings.py +0 -0
  186. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/__init__.py +0 -0
  187. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/client.py +0 -0
  188. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/dependencies.py +0 -0
  189. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/thumbnails.py +0 -0
  190. {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/validation.py +0 -0
  191. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/__init__.py +0 -0
  192. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/__init__.py +0 -0
  193. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_authentication.py +0 -0
  194. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_authorization.py +0 -0
  195. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_encryption.py +0 -0
  196. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_internal_services.py +0 -0
  197. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_login_providers.py +0 -0
  198. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_membership.py +0 -0
  199. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_oauth.py +0 -0
  200. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_oauth_server.py +0 -0
  201. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_permissions.py +0 -0
  202. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_seed.py +0 -0
  203. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_sessions.py +0 -0
  204. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_totp.py +0 -0
  205. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/__init__.py +0 -0
  206. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/test_client.py +0 -0
  207. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/test_init.py +0 -0
  208. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/test_privacy.py +0 -0
  209. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/__init__.py +0 -0
  210. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_client.py +0 -0
  211. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_init.py +0 -0
  212. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_integration.py +0 -0
  213. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_models.py +0 -0
  214. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_templates.py +0 -0
  215. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/__init__.py +0 -0
  216. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_admin.py +0 -0
  217. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_admin_plugins.py +0 -0
  218. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_ai_models.py +0 -0
  219. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_ai_providers.py +0 -0
  220. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_api_keys.py +0 -0
  221. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_auth.py +0 -0
  222. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_auth_providers.py +0 -0
  223. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_blueprints.py +0 -0
  224. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_client_credentials.py +0 -0
  225. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_comments.py +0 -0
  226. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_components.py +0 -0
  227. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_credentials.py +0 -0
  228. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_analytics.py +0 -0
  229. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_likes.py +0 -0
  230. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_presence.py +0 -0
  231. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_reads.py +0 -0
  232. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_reads_sql.py +0 -0
  233. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_templates.py +0 -0
  234. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_versions.py +0 -0
  235. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_documents.py +0 -0
  236. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_environments.py +0 -0
  237. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_events.py +0 -0
  238. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_graph_query.py +0 -0
  239. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_iggy_connectors.py +0 -0
  240. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_init.py +0 -0
  241. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_integrations.py +0 -0
  242. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_json_fields.py +0 -0
  243. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_link_definitions.py +0 -0
  244. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_local_auth.py +0 -0
  245. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_maintenance.py +0 -0
  246. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_mcp_servers.py +0 -0
  247. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_mfa.py +0 -0
  248. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_operations_log.py +0 -0
  249. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_organizations.py +0 -0
  250. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_pagination.py +0 -0
  251. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
  252. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_plugins.py +0 -0
  253. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_analysis.py +0 -0
  254. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_commit_sync.py +0 -0
  255. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_configuration.py +0 -0
  256. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_deployments.py +0 -0
  257. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_deployments_autonomous.py +0 -0
  258. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_incidents.py +0 -0
  259. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_integrations.py +0 -0
  260. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_lifecycle.py +0 -0
  261. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_logs.py +0 -0
  262. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_plugins.py +0 -0
  263. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
  264. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_types.py +0 -0
  265. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_projects.py +0 -0
  266. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_releases.py +0 -0
  267. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_request_urls.py +0 -0
  268. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_roles.py +0 -0
  269. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_sa_api_keys.py +0 -0
  270. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_scoring.py +0 -0
  271. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_scoring_policies.py +0 -0
  272. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_search.py +0 -0
  273. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_search_index.py +0 -0
  274. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_service_accounts.py +0 -0
  275. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_service_writeback.py +0 -0
  276. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_status.py +0 -0
  277. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_tags.py +0 -0
  278. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_teams.py +0 -0
  279. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_uploads.py +0 -0
  280. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_user_activity.py +0 -0
  281. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_users.py +0 -0
  282. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_webhooks.py +0 -0
  283. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
  284. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
  285. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/fixtures/sbom/tiny.json +0 -0
  286. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/__init__.py +0 -0
  287. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_endpoints.py +0 -0
  288. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_errors.py +0 -0
  289. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_flows.py +0 -0
  290. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_host_integration.py +0 -0
  291. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_repository.py +0 -0
  292. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_resolution.py +0 -0
  293. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_state.py +0 -0
  294. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_sweeper.py +0 -0
  295. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/middleware/test_rate_limit.py +0 -0
  296. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/__init__.py +0 -0
  297. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_client.py +0 -0
  298. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_init.py +0 -0
  299. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_thumbnails.py +0 -0
  300. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_validation.py +0 -0
  301. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/support.py +0 -0
  302. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_app.py +0 -0
  303. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_assignment_writer.py +0 -0
  304. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_backfill_embeddings.py +0 -0
  305. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_blueprint_attributes.py +0 -0
  306. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_blueprint_compliance.py +0 -0
  307. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_blueprints.py +0 -0
  308. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_commit_sync_queue.py +0 -0
  309. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_commit_sync_service.py +0 -0
  310. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_component_facts.py +0 -0
  311. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_status_repair.py +0 -0
  312. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_sweeper.py +0 -0
  313. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_sync_queue.py +0 -0
  314. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_sync_service.py +0 -0
  315. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_drift.py +0 -0
  316. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_entrypoint.py +0 -0
  317. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_graph_sql.py +0 -0
  318. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_identity_attribution.py +0 -0
  319. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_init.py +0 -0
  320. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_installer.py +0 -0
  321. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_lifecycle_dispatch.py +0 -0
  322. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_lifespans.py +0 -0
  323. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_log.py +0 -0
  324. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_state.py +0 -0
  325. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_worker.py +0 -0
  326. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_models.py +0 -0
  327. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_openapi.py +0 -0
  328. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_orphan_releases.py +0 -0
  329. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugin_schemas.py +0 -0
  330. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugins.py +0 -0
  331. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugins_assignments.py +0 -0
  332. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugins_credentials.py +0 -0
  333. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_pr_sync_queue.py +0 -0
  334. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_pr_sync_service.py +0 -0
  335. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_projects_drift_ranges.py +0 -0
  336. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_release_promote_service.py +0 -0
  337. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_sbom.py +0 -0
  338. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_scoring_queue.py +0 -0
  339. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_scoring_triggers.py +0 -0
  340. {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: imbi-api
3
- Version: 2.32.3
3
+ Version: 2.34.0
4
4
  Summary: Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications.
5
5
  Author-email: Alex Campbell <alexc@aweber.com>, Dave Shawley <daves@aweber.com>, "Gavin M. Roy" <gavinr@aweber.com>
6
6
  License-Expression: BSD-3-Clause
@@ -19,7 +19,7 @@ Requires-Dist: cyclonedx-python-lib<12,>=11.7
19
19
  Requires-Dist: fastapi
20
20
  Requires-Dist: filetype
21
21
  Requires-Dist: httpx<1,>=0.28.1
22
- Requires-Dist: imbi-common[databases,llm,sentry,server]==2.32.3
22
+ Requires-Dist: imbi-common[databases,llm,sentry,server]==2.34.0
23
23
  Requires-Dist: jinja2
24
24
  Requires-Dist: jsonpatch>=1.33
25
25
  Requires-Dist: mcp>=1.26.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "imbi-api"
3
- version = "2.32.3"
3
+ version = "2.34.0"
4
4
  description = "Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -28,7 +28,7 @@ dependencies = [
28
28
  "fastapi",
29
29
  "filetype",
30
30
  "httpx>=0.28.1,<1",
31
- "imbi-common[databases,llm,sentry,server]==2.32.3",
31
+ "imbi-common[databases,llm,sentry,server]==2.34.0",
32
32
  "jinja2",
33
33
  "jsonpatch>=1.33",
34
34
  "mcp>=1.26.0",
@@ -0,0 +1,249 @@
1
+ """Put back the push time commit re-syncs overwrote.
2
+
3
+ Every commit-sync path used to stamp ``commits.pushed_at`` with the
4
+ clock at ingest, and the table is a ``ReplacingMergeTree`` versioned on
5
+ ``recorded_at`` -- so any re-sync of a commit already stored replaced
6
+ its row with a fresh ``pushed_at``. Two producers did that at scale:
7
+
8
+ * the full backfill (``sync_all_history``, the ``commit-sync``
9
+ maintenance operation), which stamps every commit a project has with
10
+ the single instant the run started. Three runs in August 2026 left
11
+ roughly 19,000 commits across 600 projects sharing a handful of
12
+ timestamps, their push order collapsed onto author order;
13
+ * the ``workflow_run`` webhook rule, which re-syncs a run's head commit
14
+ when the run completes. A release workflow runs against the commit
15
+ a release was *cut from*, so that commit was re-stamped minutes after
16
+ the release commit itself and sorted above it (#308).
17
+
18
+ ``pushed_at`` orders the recent-commits feed, picks the commit-sync
19
+ gap-healing base, and picks the release-drift head, so the wrong values
20
+ mis-slice release ranges. The sync no longer re-stamps a stored commit,
21
+ but nothing that reads GitHub can put the old values back: the commits
22
+ API carries author and committer dates, never a push time.
23
+
24
+ The ``events`` table can. It holds every webhook delivery, and a
25
+ ``push`` delivery names the commits it carried (``payload.commits[]``,
26
+ plus ``payload.after`` for the head) and the branch it was for
27
+ (``payload.ref``). Its ``recorded_at`` is within about a second of the
28
+ ``pushed_at`` the sync originally wrote from it. So for each stored
29
+ commit, the earliest push delivery to the commit's own branch that
30
+ lists it is what its ``pushed_at`` was before anything overwrote it.
31
+
32
+ Deliberately conservative:
33
+
34
+ * only pushes to the branch the commit is stored under count. A pull
35
+ request's commits are also pushed to their feature branch, earlier,
36
+ and the sync's branch-gated rule never recorded those -- matching
37
+ them would order the commits by when their branch was pushed rather
38
+ than by when they reached the default branch, which is a different
39
+ wrong order.
40
+ * a row is rewritten only when its ``pushed_at`` is *later* than the
41
+ delivery by more than :data:`TOLERANCE`. Within tolerance it is the
42
+ original stamp; earlier means an even earlier sync recorded it and
43
+ that value is closer to the truth.
44
+ * a commit with no push delivery on record is left alone. Deliveries
45
+ only go back to June 2026, GitHub lists at most twenty commits per
46
+ push payload, and a compare-healed gap has no delivery of its own.
47
+ There is nothing to restore, and inventing a value would be worse
48
+ than the collapsed one.
49
+
50
+ The rewrite is a re-insert of the full row with the corrected
51
+ ``pushed_at`` and a fresh ``recorded_at`` -- the same read-modify-insert
52
+ ``opslog-backfill`` uses -- so ``FINAL`` readers see it at once and the
53
+ background merge collapses the superseded version. It composes with
54
+ the sync's carry-forward, which reads the *earliest* ``pushed_at``
55
+ across row versions: once the repaired row is on disk, every later
56
+ re-sync of that commit inherits the restored value.
57
+
58
+ There is no lock between this and a webhook re-sync, and the two are not
59
+ serialized: a re-sync that read the stored value *before* the repaired
60
+ row landed and inserted *after* it writes the stale value back with a
61
+ newer ``recorded_at``, and that version wins. The window is the few
62
+ milliseconds between the sync's lookup and its insert, it has to
63
+ coincide with a delivery for that exact commit, and the outcome is one
64
+ row back where it started rather than anything new being wrong. The
65
+ repair is idempotent, so the remedy is to run it again; the
66
+ ``commit-pushed-at-check`` dry run after a repair is what shows whether
67
+ anything did slip through. A shared lock would have to span the API
68
+ worker and the gateway's webhook handler for a race this narrow and
69
+ this cheaply undone, which is not worth the coupling.
70
+
71
+ Runs as the ``commit-pushed-at-repair`` maintenance operation, with
72
+ ``commit-pushed-at-check`` as its dry run.
73
+ """
74
+
75
+ from __future__ import annotations
76
+
77
+ import datetime
78
+ import logging
79
+ import typing
80
+
81
+ import pydantic
82
+
83
+ from imbi.common import clickhouse, models
84
+
85
+ LOGGER = logging.getLogger(__name__)
86
+
87
+ #: How far a stored ``pushed_at`` may trail its push delivery before it
88
+ #: counts as re-stamped. The webhook sync stamps ``now()`` while
89
+ #: handling the delivery, so the original value lands within a second
90
+ #: or two of ``events.recorded_at``; the smallest re-stamp -- a CI run
91
+ #: completing -- is minutes later.
92
+ TOLERANCE = datetime.timedelta(seconds=60)
93
+
94
+ _BRANCH_REF_PREFIX = 'refs/heads/'
95
+
96
+ #: Every column ``CommitRecord`` carries except ``recorded_at``, which
97
+ #: the re-insert regenerates. Listed rather than ``SELECT *`` so a
98
+ #: column added to the table later is a loud failure here, not a row
99
+ #: silently rewritten with a default.
100
+ _COMMIT_COLUMNS: typing.Final = (
101
+ 'project_id',
102
+ 'sha',
103
+ 'short_sha',
104
+ 'ref',
105
+ 'message',
106
+ 'author_name',
107
+ 'author_email',
108
+ 'author_login',
109
+ 'author_user',
110
+ 'committer_name',
111
+ 'ci_status',
112
+ 'authored_at',
113
+ 'committed_at',
114
+ 'url',
115
+ 'pushed_at',
116
+ )
117
+
118
+ # ``payload`` is a JSON column; ``payload.commits[]`` is its array
119
+ # subcolumn and ``c.id`` the per-element path. ``metadata.event_type``
120
+ # is the gateway's per-source label ('push'), distinct from the
121
+ # top-level ``type`` category ('webhook').
122
+ _DELIVERIES_SQL: typing.Final = (
123
+ 'SELECT recorded_at, '
124
+ 'toString(payload.ref) AS ref, '
125
+ 'toString(payload.after) AS after, '
126
+ 'arrayMap(c -> toString(c.id), payload.commits[]) AS commit_ids '
127
+ 'FROM events '
128
+ 'WHERE project_id = {project_id:String} '
129
+ "AND type = 'webhook' "
130
+ "AND toString(metadata.event_type) = 'push' "
131
+ "AND toString(payload.ref) LIKE 'refs/heads/%'"
132
+ )
133
+
134
+ # Column list is a module constant; values are bound parameters.
135
+ _COMMITS_SQL: typing.Final = (
136
+ 'SELECT ' + ', '.join(_COMMIT_COLUMNS) + ' FROM commits FINAL ' # noqa: S608
137
+ 'WHERE project_id = {project_id:String} '
138
+ 'AND sha IN {shas:Array(String)}'
139
+ )
140
+
141
+
142
+ class RepairSummary(typing.NamedTuple):
143
+ """What one project's repair did (or, dry run, would do)."""
144
+
145
+ #: Push deliveries to a branch found for the project.
146
+ deliveries: int = 0
147
+ #: Stored commits a delivery to their own branch accounts for.
148
+ examined: int = 0
149
+ #: Rows rewritten -- or, on a dry run, rows that would be.
150
+ repaired: int = 0
151
+ #: Largest correction applied, in seconds. Tells an operator
152
+ #: whether a project's damage was the backfill (days) or a
153
+ #: ``workflow_run`` re-stamp (minutes).
154
+ largest_shift_seconds: int = 0
155
+
156
+
157
+ async def _push_times(
158
+ project_id: str,
159
+ ) -> tuple[int, dict[tuple[str, str], datetime.datetime]]:
160
+ """Push deliveries read, and ``(branch, sha) -> earliest delivery``.
161
+
162
+ A commit pushed to several branches (a feature branch, then the
163
+ default branch on merge) gets one entry per branch, so the caller
164
+ can pick the delivery for the branch the commit is stored under.
165
+ """
166
+ rows = await clickhouse.query(_DELIVERIES_SQL, {'project_id': project_id})
167
+ earliest: dict[tuple[str, str], datetime.datetime] = {}
168
+ for row in rows:
169
+ recorded_at = clickhouse.as_utc_or_none(row.get('recorded_at'))
170
+ ref = str(row.get('ref') or '')
171
+ if recorded_at is None or not ref.startswith(_BRANCH_REF_PREFIX):
172
+ continue
173
+ branch = ref[len(_BRANCH_REF_PREFIX) :]
174
+ listed = typing.cast('list[object]', row.get('commit_ids') or [])
175
+ shas = {str(sha) for sha in listed if sha}
176
+ # ``after`` is the head the delivery attests to even when the
177
+ # payload's commit list is capped (GitHub sends at most twenty)
178
+ # or empty (a branch created from an existing commit).
179
+ after = str(row.get('after') or '')
180
+ if after and after != '0' * 40:
181
+ shas.add(after)
182
+ for sha in shas:
183
+ key = (branch, sha.lower())
184
+ if key not in earliest or recorded_at < earliest[key]:
185
+ earliest[key] = recorded_at
186
+ return len(rows), earliest
187
+
188
+
189
+ def _repaired_record(
190
+ row: dict[str, typing.Any], pushed_at: datetime.datetime
191
+ ) -> models.CommitRecord:
192
+ """The row as a ``CommitRecord`` carrying the restored push time.
193
+
194
+ ``recorded_at`` is left to the model's default so the re-insert
195
+ ranks newest under ``ReplacingMergeTree(recorded_at)``.
196
+ """
197
+ fields = {name: row.get(name) for name in _COMMIT_COLUMNS}
198
+ fields['authored_at'] = clickhouse.as_utc_or_none(row.get('authored_at'))
199
+ fields['committed_at'] = clickhouse.as_utc_or_none(row.get('committed_at'))
200
+ fields['pushed_at'] = pushed_at
201
+ return models.CommitRecord.model_validate(fields)
202
+
203
+
204
+ async def repair_project(
205
+ project_id: str, *, dry_run: bool = False
206
+ ) -> RepairSummary:
207
+ """Restore one project's re-stamped commit push times.
208
+
209
+ Idempotent: a repaired row sits within :data:`TOLERANCE` of its
210
+ delivery, so a second run finds nothing to rewrite.
211
+ """
212
+ deliveries, push_times = await _push_times(project_id)
213
+ if not push_times:
214
+ return RepairSummary(deliveries=deliveries)
215
+ shas = sorted({sha for _, sha in push_times})
216
+ rows = await clickhouse.query(
217
+ _COMMITS_SQL, {'project_id': project_id, 'shas': shas}
218
+ )
219
+ examined = 0
220
+ largest = datetime.timedelta(0)
221
+ repaired: list[pydantic.BaseModel] = []
222
+ for row in rows:
223
+ sha = str(row.get('sha') or '').lower()
224
+ branch = str(row.get('ref') or '')
225
+ delivered = push_times.get((branch, sha))
226
+ if delivered is None:
227
+ continue # only pushed to some other branch; see module doc
228
+ examined += 1
229
+ stored = clickhouse.as_utc_or_none(row.get('pushed_at'))
230
+ if stored is None:
231
+ continue
232
+ shift = stored - delivered
233
+ if shift <= TOLERANCE:
234
+ continue
235
+ largest = max(largest, shift)
236
+ repaired.append(_repaired_record(row, delivered))
237
+ if repaired and not dry_run:
238
+ await clickhouse.insert('commits', repaired)
239
+ LOGGER.info(
240
+ 'restored pushed_at on %d commit(s) for project %s',
241
+ len(repaired),
242
+ project_id,
243
+ )
244
+ return RepairSummary(
245
+ deliveries=deliveries,
246
+ examined=examined,
247
+ repaired=len(repaired),
248
+ largest_shift_seconds=int(largest.total_seconds()),
249
+ )
@@ -289,8 +289,24 @@ def _build_series(
289
289
  return MetricSeries(total=sum(daily), daily=daily)
290
290
 
291
291
 
292
+ async def _active_project_ids(db: graph.Graph) -> list[str]:
293
+ """IDs of every project that is not archived."""
294
+ query: typing.LiteralString = """
295
+ MATCH (p:Project)
296
+ WHERE coalesce(p.archived, false) = false
297
+ RETURN p.id AS id
298
+ """
299
+ records = await db.execute(query, {}, ['id'])
300
+ return [
301
+ graph.parse_agtype(r['id'])
302
+ for r in records
303
+ if graph.parse_agtype(r['id'])
304
+ ]
305
+
306
+
292
307
  @dashboard_router.get('/metrics', response_model=DashboardMetrics)
293
308
  async def get_dashboard_metrics(
309
+ db: graph.Pool,
294
310
  auth: typing.Annotated[
295
311
  permissions.AuthContext,
296
312
  fastapi.Depends(
@@ -298,7 +314,10 @@ async def get_dashboard_metrics(
298
314
  ),
299
315
  ],
300
316
  ) -> DashboardMetrics:
301
- """Return 7-day activity metrics with per-day counts for the tiles."""
317
+ """Return 7-day activity metrics with per-day counts for the tiles.
318
+
319
+ The pull request series skips drafts and archived projects.
320
+ """
302
321
  today = datetime.datetime.now(datetime.UTC).date()
303
322
  start_date = today - datetime.timedelta(days=_METRICS_WINDOW_DAYS - 1)
304
323
  since = datetime.datetime.combine(
@@ -308,7 +327,10 @@ async def get_dashboard_metrics(
308
327
  (start_date + datetime.timedelta(days=offset)).isoformat()
309
328
  for offset in range(_METRICS_WINDOW_DAYS)
310
329
  ]
311
- params: dict[str, typing.Any] = {'since': since}
330
+ params: dict[str, typing.Any] = {
331
+ 'since': since,
332
+ 'project_ids': await _active_project_ids(db),
333
+ }
312
334
  # One scan of the (FINAL, merge-on-read) deploy rows yields both the
313
335
  # per-day series and the per-environment rollup.
314
336
  deploys, events, ops, prs = await asyncio.gather(
@@ -339,6 +361,8 @@ async def get_dashboard_metrics(
339
361
  'SELECT toDate(created_at) AS day, count() AS c'
340
362
  ' FROM pull_requests FINAL'
341
363
  ' WHERE created_at >= {since:DateTime64(3)}'
364
+ ' AND NOT draft'
365
+ ' AND project_id IN {project_ids:Array(String)}'
342
366
  ' GROUP BY day',
343
367
  params,
344
368
  ),
@@ -553,8 +553,8 @@ async def _fetch_pr_counts(
553
553
  ) -> dict[str, tuple[int, int, int, int]]:
554
554
  """Return {project_id: (open, closed, viewer_open, viewer_closed)}.
555
555
 
556
- Errors are swallowed — PR counts are best-effort and should not
557
- fail the project list endpoint.
556
+ Draft PRs are not counted. Errors are swallowed — PR counts are
557
+ best-effort and should not fail the project list endpoint.
558
558
  """
559
559
  if not project_ids:
560
560
  return {}
@@ -570,6 +570,7 @@ async def _fetch_pr_counts(
570
570
  ' AS viewer_closed_count'
571
571
  ' FROM pull_requests FINAL'
572
572
  ' WHERE project_id IN {project_ids:Array(String)}'
573
+ ' AND NOT draft'
573
574
  ' GROUP BY project_id'
574
575
  )
575
576
  params['viewer'] = viewer
@@ -582,6 +583,7 @@ async def _fetch_pr_counts(
582
583
  ' 0 AS viewer_closed_count'
583
584
  ' FROM pull_requests FINAL'
584
585
  ' WHERE project_id IN {project_ids:Array(String)}'
586
+ ' AND NOT draft'
585
587
  ' GROUP BY project_id'
586
588
  )
587
589
  try: