imbi-api 2.34.0__tar.gz → 2.35.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.34.0 → imbi_api-2.35.0}/PKG-INFO +2 -2
  2. {imbi_api-2.34.0 → imbi_api-2.35.0}/pyproject.toml +2 -2
  3. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_pushed_at_repair.py +2 -2
  4. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/component_facts.py +5 -1
  5. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/drift.py +10 -15
  6. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/__init__.py +6 -6
  7. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_events.py +3 -2
  8. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_history.py +4 -3
  9. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_reads.py +21 -7
  10. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/comments.py +13 -23
  11. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/dashboard.py +33 -2
  12. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/operations_log.py +25 -13
  13. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_configuration.py +2 -6
  14. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_deployments.py +153 -19
  15. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/projects.py +15 -28
  16. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/lifespans.py +1 -2
  17. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/log.py +9 -16
  18. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/operations.py +6 -19
  19. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/lifecycle_dispatch.py +17 -32
  20. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/prompts/release_notes_system.md +17 -5
  21. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/sbom.py +16 -7
  22. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/scoring/queue.py +5 -8
  23. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/test_client.py +0 -81
  24. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/test_init.py +0 -103
  25. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_integration.py +12 -12
  26. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_comments.py +18 -40
  27. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_dashboard.py +46 -1
  28. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_reads_sql.py +86 -1
  29. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_versions.py +5 -5
  30. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_documents.py +14 -14
  31. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_operations_log.py +63 -35
  32. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_configuration.py +6 -3
  33. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_deployments.py +235 -28
  34. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_deployments_autonomous.py +6 -1
  35. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_projects.py +37 -52
  36. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_releases.py +1 -1
  37. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/support.py +61 -0
  38. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_commit_pushed_at_repair.py +3 -3
  39. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_drift.py +10 -13
  40. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_lifecycle_dispatch.py +22 -31
  41. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_log.py +13 -14
  42. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_operations.py +9 -5
  43. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_sbom.py +2 -2
  44. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_scoring_queue.py +5 -15
  45. {imbi_api-2.34.0 → imbi_api-2.35.0}/.gitignore +0 -0
  46. {imbi_api-2.34.0 → imbi_api-2.35.0}/README.md +0 -0
  47. {imbi_api-2.34.0 → imbi_api-2.35.0}/moon.yml +0 -0
  48. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/README.md +0 -0
  49. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/__init__.py +0 -0
  50. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/app.py +0 -0
  51. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/__init__.py +0 -0
  52. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/authorization_codes.py +0 -0
  53. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/autonomous.py +0 -0
  54. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/internal_services.py +0 -0
  55. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/local_auth.py +0 -0
  56. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/login_providers.py +0 -0
  57. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/membership.py +0 -0
  58. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/models.py +0 -0
  59. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/oauth.py +0 -0
  60. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/oauth_clients.py +0 -0
  61. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/password.py +0 -0
  62. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/permissions.py +0 -0
  63. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/principals.py +0 -0
  64. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/seed.py +0 -0
  65. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/sessions.py +0 -0
  66. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/tokens.py +0 -0
  67. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/totp.py +0 -0
  68. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/backfill_embeddings.py +0 -0
  69. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/blueprint_attributes.py +0 -0
  70. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/blueprint_compliance.py +0 -0
  71. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_sync/__init__.py +0 -0
  72. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_sync/queue.py +0 -0
  73. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_sync/service.py +0 -0
  74. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_status_repair.py +0 -0
  75. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sweeper.py +0 -0
  76. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
  77. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sync/queue.py +0 -0
  78. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sync/service.py +0 -0
  79. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/documents/__init__.py +0 -0
  80. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/documents/read_sweeper.py +0 -0
  81. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/domain/__init__.py +0 -0
  82. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/domain/models.py +0 -0
  83. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/domain/scoring.py +0 -0
  84. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/client.py +0 -0
  85. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/dependencies.py +0 -0
  86. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/models.py +0 -0
  87. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/base.html +0 -0
  88. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/base.txt +0 -0
  89. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/password_reset.html +0 -0
  90. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
  91. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/welcome.html +0 -0
  92. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/welcome.txt +0 -0
  93. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates.py +0 -0
  94. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/__init__.py +0 -0
  95. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_credentials.py +0 -0
  96. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
  97. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_helpers.py +0 -0
  98. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
  99. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_pagination.py +0 -0
  100. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
  101. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_search_index.py +0 -0
  102. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/admin.py +0 -0
  103. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
  104. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/ai_models.py +0 -0
  105. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
  106. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/api_keys.py +0 -0
  107. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/auth.py +0 -0
  108. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
  109. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/blueprints.py +0 -0
  110. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
  111. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/components.py +0 -0
  112. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
  113. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_likes.py +0 -0
  114. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_presence.py +0 -0
  115. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_templates.py +0 -0
  116. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_versions.py +0 -0
  117. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/documents.py +0 -0
  118. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/environments.py +0 -0
  119. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/events.py +0 -0
  120. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/graph_query.py +0 -0
  121. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
  122. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/integrations.py +0 -0
  123. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
  124. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/local_auth.py +0 -0
  125. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/maintenance.py +0 -0
  126. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
  127. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/mfa.py +0 -0
  128. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
  129. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/organizations.py +0 -0
  130. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
  131. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
  132. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/plugins.py +0 -0
  133. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_analysis.py +0 -0
  134. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
  135. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
  136. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
  137. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
  138. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_logs.py +0 -0
  139. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
  140. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
  141. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_types.py +0 -0
  142. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/pull_requests.py +0 -0
  143. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/releases.py +0 -0
  144. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/roles.py +0 -0
  145. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
  146. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/scoring.py +0 -0
  147. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
  148. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/search.py +0 -0
  149. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
  150. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/status.py +0 -0
  151. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/tags.py +0 -0
  152. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/teams.py +0 -0
  153. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/uploads.py +0 -0
  154. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/user_activity.py +0 -0
  155. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/users.py +0 -0
  156. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/webhooks.py +0 -0
  157. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/entrypoint.py +0 -0
  158. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/graph_sql.py +0 -0
  159. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/__init__.py +0 -0
  160. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/attribution.py +0 -0
  161. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/endpoints.py +0 -0
  162. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/errors.py +0 -0
  163. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/flows.py +0 -0
  164. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/host_integration.py +0 -0
  165. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/models.py +0 -0
  166. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/repository.py +0 -0
  167. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/resolution.py +0 -0
  168. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/state.py +0 -0
  169. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/sweeper.py +0 -0
  170. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/llm/__init__.py +0 -0
  171. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/llm/dependencies.py +0 -0
  172. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/__init__.py +0 -0
  173. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/registry.py +0 -0
  174. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/state.py +0 -0
  175. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/worker.py +0 -0
  176. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/mcp_test.py +0 -0
  177. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/middleware/__init__.py +0 -0
  178. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/middleware/rate_limit.py +0 -0
  179. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/models.py +0 -0
  180. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/openapi.py +0 -0
  181. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/orphan_releases.py +0 -0
  182. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/__init__.py +0 -0
  183. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
  184. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/assignments.py +0 -0
  185. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/credentials.py +0 -0
  186. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/installer.py +0 -0
  187. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/lifecycle.py +0 -0
  188. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/reload.py +0 -0
  189. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/resolution.py +0 -0
  190. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/schemas.py +0 -0
  191. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/pr_sync/__init__.py +0 -0
  192. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/pr_sync/queue.py +0 -0
  193. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/pr_sync/service.py +0 -0
  194. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/prompts/__init__.py +0 -0
  195. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/py.typed +0 -0
  196. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/relationships.py +0 -0
  197. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/release_promote/__init__.py +0 -0
  198. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/release_promote/queue.py +0 -0
  199. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/release_promote/service.py +0 -0
  200. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/scoring/__init__.py +0 -0
  201. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/settings.py +0 -0
  202. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/__init__.py +0 -0
  203. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/client.py +0 -0
  204. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/dependencies.py +0 -0
  205. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/thumbnails.py +0 -0
  206. {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/validation.py +0 -0
  207. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/__init__.py +0 -0
  208. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/__init__.py +0 -0
  209. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_authentication.py +0 -0
  210. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_authorization.py +0 -0
  211. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_encryption.py +0 -0
  212. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_internal_services.py +0 -0
  213. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_login_providers.py +0 -0
  214. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_membership.py +0 -0
  215. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_oauth.py +0 -0
  216. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_oauth_server.py +0 -0
  217. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_permissions.py +0 -0
  218. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_seed.py +0 -0
  219. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_sessions.py +0 -0
  220. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_totp.py +0 -0
  221. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/__init__.py +0 -0
  222. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/test_privacy.py +0 -0
  223. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/__init__.py +0 -0
  224. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_client.py +0 -0
  225. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_init.py +0 -0
  226. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_models.py +0 -0
  227. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_templates.py +0 -0
  228. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/__init__.py +0 -0
  229. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_admin.py +0 -0
  230. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_admin_plugins.py +0 -0
  231. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_ai_models.py +0 -0
  232. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_ai_providers.py +0 -0
  233. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_api_keys.py +0 -0
  234. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_auth.py +0 -0
  235. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_auth_providers.py +0 -0
  236. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_blueprints.py +0 -0
  237. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_client_credentials.py +0 -0
  238. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_components.py +0 -0
  239. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_credentials.py +0 -0
  240. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_analytics.py +0 -0
  241. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_likes.py +0 -0
  242. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_presence.py +0 -0
  243. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_reads.py +0 -0
  244. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_templates.py +0 -0
  245. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_environments.py +0 -0
  246. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_events.py +0 -0
  247. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_graph_query.py +0 -0
  248. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_iggy_connectors.py +0 -0
  249. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_init.py +0 -0
  250. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_integrations.py +0 -0
  251. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_json_fields.py +0 -0
  252. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_link_definitions.py +0 -0
  253. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_local_auth.py +0 -0
  254. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_maintenance.py +0 -0
  255. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_mcp_servers.py +0 -0
  256. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_mfa.py +0 -0
  257. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_organizations.py +0 -0
  258. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_pagination.py +0 -0
  259. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
  260. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_plugins.py +0 -0
  261. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_analysis.py +0 -0
  262. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_commit_sync.py +0 -0
  263. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_incidents.py +0 -0
  264. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_integrations.py +0 -0
  265. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_lifecycle.py +0 -0
  266. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_logs.py +0 -0
  267. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_plugins.py +0 -0
  268. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
  269. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_types.py +0 -0
  270. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_projects_helpers.py +0 -0
  271. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_pull_requests.py +0 -0
  272. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_request_urls.py +0 -0
  273. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_roles.py +0 -0
  274. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_sa_api_keys.py +0 -0
  275. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_scoring.py +0 -0
  276. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_scoring_policies.py +0 -0
  277. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_search.py +0 -0
  278. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_search_index.py +0 -0
  279. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_service_accounts.py +0 -0
  280. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_service_writeback.py +0 -0
  281. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_status.py +0 -0
  282. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_tags.py +0 -0
  283. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_teams.py +0 -0
  284. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_uploads.py +0 -0
  285. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_user_activity.py +0 -0
  286. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_users.py +0 -0
  287. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_webhooks.py +0 -0
  288. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
  289. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
  290. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/fixtures/sbom/tiny.json +0 -0
  291. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/__init__.py +0 -0
  292. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_endpoints.py +0 -0
  293. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_errors.py +0 -0
  294. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_flows.py +0 -0
  295. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_host_integration.py +0 -0
  296. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_repository.py +0 -0
  297. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_resolution.py +0 -0
  298. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_state.py +0 -0
  299. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_sweeper.py +0 -0
  300. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/middleware/test_rate_limit.py +0 -0
  301. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/__init__.py +0 -0
  302. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_client.py +0 -0
  303. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_init.py +0 -0
  304. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_thumbnails.py +0 -0
  305. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_validation.py +0 -0
  306. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_app.py +0 -0
  307. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_assignment_writer.py +0 -0
  308. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_backfill_embeddings.py +0 -0
  309. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_blueprint_attributes.py +0 -0
  310. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_blueprint_compliance.py +0 -0
  311. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_blueprints.py +0 -0
  312. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_commit_sync_queue.py +0 -0
  313. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_commit_sync_service.py +0 -0
  314. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_component_facts.py +0 -0
  315. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_status_repair.py +0 -0
  316. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_sweeper.py +0 -0
  317. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_sync_queue.py +0 -0
  318. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_sync_service.py +0 -0
  319. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_entrypoint.py +0 -0
  320. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_graph_sql.py +0 -0
  321. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_identity_attribution.py +0 -0
  322. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_init.py +0 -0
  323. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_installer.py +0 -0
  324. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_lifespans.py +0 -0
  325. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_state.py +0 -0
  326. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_worker.py +0 -0
  327. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_models.py +0 -0
  328. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_openapi.py +0 -0
  329. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_orphan_releases.py +0 -0
  330. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugin_schemas.py +0 -0
  331. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugins.py +0 -0
  332. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugins_assignments.py +0 -0
  333. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugins_credentials.py +0 -0
  334. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_pr_sync_queue.py +0 -0
  335. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_pr_sync_service.py +0 -0
  336. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_projects_drift_ranges.py +0 -0
  337. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_pull_requests_pending_deploy.py +0 -0
  338. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_release_promote_service.py +0 -0
  339. {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_scoring_triggers.py +0 -0
  340. {imbi_api-2.34.0 → imbi_api-2.35.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.34.0
3
+ Version: 2.35.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.34.0
22
+ Requires-Dist: imbi-common[databases,llm,sentry,server]==2.35.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.34.0"
3
+ version = "2.35.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.34.0",
31
+ "imbi-common[databases,llm,sentry,server]==2.35.0",
32
32
  "jinja2",
33
33
  "jsonpatch>=1.33",
34
34
  "mcp>=1.26.0",
@@ -80,7 +80,7 @@ import typing
80
80
 
81
81
  import pydantic
82
82
 
83
- from imbi.common import clickhouse, models
83
+ from imbi.common import clickhouse, iggy, models
84
84
 
85
85
  LOGGER = logging.getLogger(__name__)
86
86
 
@@ -235,7 +235,7 @@ async def repair_project(
235
235
  largest = max(largest, shift)
236
236
  repaired.append(_repaired_record(row, delivered))
237
237
  if repaired and not dry_run:
238
- await clickhouse.insert('commits', repaired)
238
+ await iggy.publish('commits', 'maintenance', repaired)
239
239
  LOGGER.info(
240
240
  'restored pushed_at on %d commit(s) for project %s',
241
241
  len(repaired),
@@ -332,10 +332,14 @@ async def release_components(
332
332
  ``ComponentRelease`` node and were never copied here, so the
333
333
  listing endpoint fetches them from the graph by id -- a bounded
334
334
  ``IN`` over one release's versions, not a traversal.
335
+
336
+ ``DISTINCT`` because the stream delivers at least once: a
337
+ redelivered fact row lands inside the winning batch, where the
338
+ batch ranking cannot remove it.
335
339
  """
336
340
  rows = await clickhouse.query(
337
341
  _by_release(
338
- 'SELECT c.component_id AS component_id,'
342
+ 'SELECT DISTINCT c.component_id AS component_id,'
339
343
  ' c.component_release_id AS component_release_id,'
340
344
  ' c.purl_name AS purl_name,'
341
345
  ' c.ecosystem AS ecosystem,'
@@ -40,8 +40,7 @@ import typing
40
40
  import fastapi
41
41
 
42
42
  from imbi.api.auth import principals
43
- from imbi.common import clickhouse, graph
44
- from imbi.common.clickhouse import client as ch_client
43
+ from imbi.common import clickhouse, graph, iggy
45
44
  from imbi.common.plugins import errors as plugin_errors
46
45
 
47
46
  LOGGER = logging.getLogger(__name__)
@@ -54,14 +53,6 @@ NAMESPACE = 'imbi-drift'
54
53
  #: commit, which is what OR-ing a range needs.
55
54
  VERDICT_TABLE = 'commit_drift'
56
55
 
57
- _VERDICT_COLUMNS = [
58
- 'project_id',
59
- 'sha',
60
- 'drift_detected',
61
- 'paths',
62
- 'recorded_at',
63
- ]
64
-
65
56
  REQUESTED_BY = principals.DRIFT_SYNC
66
57
 
67
58
  #: How many unanswered Releases one sweep asks the remote about. Every
@@ -172,7 +163,7 @@ def parse_note(body: str | None) -> bool | None:
172
163
  async def record_verdicts(
173
164
  project_id: str, verdicts: dict[str, NoteVerdict]
174
165
  ) -> int | None:
175
- """Persist per-commit verdicts, one insert for the whole batch.
166
+ """Persist per-commit verdicts, one publish for the whole batch.
176
167
 
177
168
  Keyed by the annotated commit's full SHA to join ``imbi.commits``,
178
169
  so only callers holding full SHAs write here -- the notes tree is
@@ -191,16 +182,20 @@ async def record_verdicts(
191
182
  """
192
183
  now = datetime.datetime.now(datetime.UTC)
193
184
  rows = [
194
- [project_id, sha.lower(), verdict.drift_detected, verdict.paths, now]
185
+ {
186
+ 'project_id': project_id,
187
+ 'sha': sha.lower(),
188
+ 'drift_detected': verdict.drift_detected,
189
+ 'paths': verdict.paths,
190
+ 'recorded_at': now,
191
+ }
195
192
  for sha, verdict in verdicts.items()
196
193
  if verdict.drift_detected is not None
197
194
  ]
198
195
  if not rows:
199
196
  return 0
200
197
  try:
201
- await ch_client.Clickhouse.get_instance().insert(
202
- VERDICT_TABLE, rows, _VERDICT_COLUMNS
203
- )
198
+ await iggy.publish_rows(VERDICT_TABLE, 'drift', rows)
204
199
  except Exception:
205
200
  LOGGER.exception(
206
201
  'could not record %d drift verdicts for project %s',
@@ -13,7 +13,7 @@ rendered using Jinja2 templates with both HTML and plain text versions.
13
13
  import logging
14
14
  from urllib import parse
15
15
 
16
- from imbi.common import clickhouse, graph
16
+ from imbi.common import graph, iggy
17
17
 
18
18
  from .client import EmailClient
19
19
  from .dependencies import InjectEmailClient, InjectTemplateManager
@@ -154,18 +154,18 @@ async def send_password_reset(
154
154
 
155
155
 
156
156
  async def _save_audit(audit: models.EmailAudit) -> None:
157
- """Save email audit record to ClickHouse.
157
+ """Publish the email audit record to the ``email_audit`` stream.
158
158
 
159
159
  Args:
160
160
  audit: Email audit record to save
161
161
 
162
162
  """
163
163
  try:
164
- await clickhouse.insert('email_audit', [audit])
165
- LOGGER.debug('Email audit saved to ClickHouse: %s', audit.to_email)
166
- except clickhouse.client.DatabaseError as err:
164
+ await iggy.publish('email_audit', 'email', [audit])
165
+ LOGGER.debug('Email audit published: %s', audit.to_email)
166
+ except iggy.PublishError as err:
167
167
  # Log error but don't fail the email send
168
168
  LOGGER.warning(
169
- 'Failed to save email audit to ClickHouse: %s',
169
+ 'Failed to publish email audit: %s',
170
170
  err,
171
171
  )
@@ -10,7 +10,7 @@ than surfaced.
10
10
  import datetime
11
11
  import logging
12
12
 
13
- from imbi.common import clickhouse, models
13
+ from imbi.common import iggy, models
14
14
 
15
15
  LOGGER = logging.getLogger(__name__)
16
16
 
@@ -34,8 +34,9 @@ async def emit_like_event(
34
34
  activity feed, which keys on ``attributed_to`` rather than project.
35
35
  """
36
36
  try:
37
- await clickhouse.insert(
37
+ await iggy.publish(
38
38
  'events',
39
+ 'documents',
39
40
  [
40
41
  models.Event(
41
42
  project_id=project_id,
@@ -18,7 +18,7 @@ import typing
18
18
 
19
19
  import pydantic
20
20
 
21
- from imbi.common import clickhouse
21
+ from imbi.common import clickhouse, iggy
22
22
 
23
23
  LOGGER = logging.getLogger(__name__)
24
24
 
@@ -79,8 +79,9 @@ async def record_created(
79
79
  ) -> None:
80
80
  """Record version 1 for a newly created document. Best-effort."""
81
81
  try:
82
- await clickhouse.insert(
82
+ await iggy.publish(
83
83
  'document_versions',
84
+ 'documents',
84
85
  [
85
86
  DocumentVersionRow(
86
87
  document_id=document_id,
@@ -162,7 +163,7 @@ async def record_updated(
162
163
  recorded_at=now,
163
164
  )
164
165
  )
165
- await clickhouse.insert('document_versions', rows)
166
+ await iggy.publish('document_versions', 'documents', rows)
166
167
  except Exception:
167
168
  LOGGER.exception(
168
169
  'failed to record version %s for document %s',
@@ -27,7 +27,7 @@ import pydantic
27
27
  from valkey import asyncio as valkey_asyncio
28
28
 
29
29
  from imbi.api.endpoints import _document_scope
30
- from imbi.common import clickhouse, graph
30
+ from imbi.common import clickhouse, graph, iggy
31
31
 
32
32
  LOGGER = logging.getLogger(__name__)
33
33
 
@@ -238,8 +238,9 @@ async def record_events(rows: list[DocumentReadEventRow]) -> None:
238
238
  if not rows:
239
239
  return
240
240
  try:
241
- await clickhouse.insert(
241
+ await iggy.publish(
242
242
  'document_read_events',
243
+ 'documents',
243
244
  typing.cast('list[pydantic.BaseModel]', rows),
244
245
  )
245
246
  except Exception:
@@ -352,7 +353,7 @@ async def finalize_sessions(session_ids: list[str]) -> int:
352
353
  if not rows:
353
354
  return 0
354
355
  try:
355
- await clickhouse.insert('document_read_sessions', rows)
356
+ await iggy.publish('document_read_sessions', 'documents', rows)
356
357
  except Exception:
357
358
  LOGGER.exception('failed to finalize %d session(s)', len(rows))
358
359
  return 0
@@ -366,8 +367,20 @@ async def finalize_sessions(session_ids: list[str]) -> int:
366
367
  #: never having been finalized.
367
368
  SWEEP_LOOKBACK_HOURS = 24
368
369
 
369
- # Sessions whose last heartbeat has aged past the idle timeout and that
370
- # have no finalized row yet.
370
+ # Sessions whose last heartbeat has aged past the idle timeout and whose
371
+ # finalized row does not yet cover that heartbeat.
372
+ #
373
+ # The condition is "not finalized *through* the last beat", not "not
374
+ # finalized at all". Heartbeats reach ClickHouse through Iggy, so a
375
+ # session finalized inline by its own final flush aggregates only the
376
+ # beats the sink had already drained -- the last one or two are
377
+ # routinely missing, and `engaged_ms` and `max_scroll_pct` are short by
378
+ # that much. Skipping every session that has any row would make that
379
+ # shortfall permanent. Comparing `ended_at` against the last beat makes
380
+ # this sweep a repair pass instead: it re-finalizes the session once the
381
+ # sink has caught up, `finalize_sessions` is idempotent, and the
382
+ # rewritten row then covers the last beat, so the session stops
383
+ # matching.
371
384
  #
372
385
  # Both sides are pinned to the same recent window so neither scans more
373
386
  # than the other needs: without the `recorded_at` bound the outer scan
@@ -386,11 +399,12 @@ FROM (
386
399
  HAVING last_beat < now() - INTERVAL {idle_seconds:UInt32} SECOND
387
400
  ) AS e
388
401
  LEFT JOIN (
389
- SELECT DISTINCT session_id
402
+ SELECT session_id, max(ended_at) AS finalized_through
390
403
  FROM imbi.document_read_sessions
391
404
  WHERE started_at > now() - INTERVAL {lookback_hours:UInt32} HOUR
405
+ GROUP BY session_id
392
406
  ) AS s USING (session_id)
393
- WHERE s.session_id = ''
407
+ WHERE s.session_id = '' OR s.finalized_through < e.last_beat
394
408
  LIMIT {batch:UInt32}
395
409
  """
396
410
 
@@ -25,9 +25,8 @@ import pydantic
25
25
 
26
26
  from imbi.api.auth import permissions
27
27
  from imbi.api.endpoints import _search_index
28
- from imbi.common import graph, models
28
+ from imbi.common import graph, iggy, models
29
29
  from imbi.common import patch as json_patch
30
- from imbi.common.clickhouse import client as ch_client
31
30
 
32
31
  LOGGER = logging.getLogger(__name__)
33
32
 
@@ -55,18 +54,19 @@ async def _emit_comment_event(
55
54
  failed analytics write must not fail the comment request.
56
55
  """
57
56
  try:
58
- await ch_client.Clickhouse.get_instance().insert(
57
+ await iggy.publish_rows(
59
58
  'events',
59
+ 'comments',
60
60
  [
61
- [
62
- nanoid.generate(),
63
- project_id or '',
64
- datetime.datetime.now(datetime.UTC),
65
- _COMMENT_EVENT_TYPE,
66
- 'internal',
67
- principal,
68
- {},
69
- {
61
+ {
62
+ 'id': nanoid.generate(),
63
+ 'project_id': project_id or '',
64
+ 'recorded_at': datetime.datetime.now(datetime.UTC),
65
+ 'type': _COMMENT_EVENT_TYPE,
66
+ 'integration': 'internal',
67
+ 'attributed_to': principal,
68
+ 'metadata': {},
69
+ 'payload': {
70
70
  'document_id': document_id,
71
71
  'thread_id': thread_id,
72
72
  'comment_id': comment_id,
@@ -74,17 +74,7 @@ async def _emit_comment_event(
74
74
  'action': action,
75
75
  'excerpt': body[:140],
76
76
  },
77
- ]
78
- ],
79
- [
80
- 'id',
81
- 'project_id',
82
- 'recorded_at',
83
- 'type',
84
- 'integration',
85
- 'attributed_to',
86
- 'metadata',
87
- 'payload',
77
+ }
88
78
  ],
89
79
  )
90
80
  except Exception:
@@ -1,6 +1,7 @@
1
1
  """Admin dashboard system-health status endpoint.
2
2
 
3
- Probes the datastores in-process (PostgreSQL/AGE, ClickHouse, Valkey)
3
+ Probes the datastores in-process (PostgreSQL/AGE, ClickHouse, Valkey,
4
+ Iggy)
4
5
  and the sibling HTTP services via their ``/status`` endpoints. Every
5
6
  check runs concurrently with a short timeout so the endpoint never
6
7
  blocks on a degraded dependency.
@@ -18,7 +19,7 @@ import pydantic
18
19
 
19
20
  from imbi.api import settings, version
20
21
  from imbi.api.auth import permissions
21
- from imbi.common import clickhouse, graph, valkey
22
+ from imbi.common import clickhouse, graph, iggy, valkey
22
23
 
23
24
  LOGGER = logging.getLogger(__name__)
24
25
 
@@ -207,6 +208,35 @@ async def _check_valkey() -> DatastoreStatus:
207
208
  )
208
209
 
209
210
 
211
+ async def _check_iggy() -> DatastoreStatus:
212
+ """Ping Iggy and total the bytes held by the topics Imbi publishes."""
213
+ start = time.perf_counter()
214
+ try:
215
+ await asyncio.wait_for(iggy.ping(), _CHECK_TIMEOUT)
216
+ except Exception as err: # noqa: BLE001
217
+ LOGGER.warning('Iggy health check failed: %s', err)
218
+ return DatastoreStatus(
219
+ name='Iggy',
220
+ role='Event streaming',
221
+ status='error',
222
+ detail=str(err),
223
+ )
224
+ latency = _ms(start)
225
+ # Size is best-effort; it must not fail the (already-ok) check.
226
+ try:
227
+ size = await asyncio.wait_for(iggy.stored_bytes(), _CHECK_TIMEOUT)
228
+ except Exception as err: # noqa: BLE001
229
+ LOGGER.warning('Iggy size probe failed: %s', err)
230
+ size = None
231
+ return DatastoreStatus(
232
+ name='Iggy',
233
+ role='Event streaming',
234
+ status='ok',
235
+ latency_ms=latency,
236
+ size_bytes=size,
237
+ )
238
+
239
+
210
240
  async def _check_service(
211
241
  client: httpx.AsyncClient, name: str, base_url: str
212
242
  ) -> ServiceStatus:
@@ -251,6 +281,7 @@ async def get_dashboard_status(
251
281
  _check_postgres(db),
252
282
  _check_clickhouse(),
253
283
  _check_valkey(),
284
+ _check_iggy(),
254
285
  ),
255
286
  asyncio.gather(
256
287
  _check_service(
@@ -12,6 +12,7 @@ are hidden.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import asyncio
15
16
  import datetime
16
17
  import logging
17
18
  import typing
@@ -29,7 +30,7 @@ from imbi.api.endpoints._pagination import (
29
30
  encode_cursor,
30
31
  parse_iso,
31
32
  )
32
- from imbi.common import clickhouse, models
33
+ from imbi.common import clickhouse, iggy, models
33
34
  from imbi.common import patch as json_patch
34
35
  from imbi.common.plugins import OpsLogTemplate
35
36
  from imbi.common.plugins.registry import list_plugins
@@ -147,18 +148,19 @@ def _model_to_row(entry: models.OperationLog) -> dict[str, typing.Any]:
147
148
  return dumped
148
149
 
149
150
 
150
- async def _insert_row(row: dict[str, typing.Any]) -> None:
151
- """Insert a single row into operations_log.
151
+ #: One poll interval of the ClickHouse sink. A row published moments
152
+ #: ago is not readable until the sink has drained it, so a read that
153
+ #: expects it waits this long once before concluding it is absent.
154
+ SINK_POLL_INTERVAL_SECONDS: typing.Final[float] = 0.25
152
155
 
153
- Calls the class method directly with explicit column names/values
154
- because the module-level ``clickhouse.insert`` wrapper only accepts
155
- pydantic models and loses the alias during serialization.
156
+
157
+ async def _insert_row(row: dict[str, typing.Any], topic: str = 'api') -> None:
158
+ """Publish a single ``operations_log`` row to the Iggy stream.
159
+
160
+ The row is a column-name mapping, so it goes through
161
+ ``publish_rows``; the ``_row_version`` alias is already rendered.
156
162
  """
157
- await clickhouse.client.Clickhouse.get_instance().insert(
158
- 'operations_log',
159
- [list(row.values())],
160
- list(row.keys()),
161
- )
163
+ await iggy.publish_rows('operations_log', topic, [row])
162
164
 
163
165
 
164
166
  _BY_EXTERNAL_RUN_SQL: typing.Final[str] = (
@@ -176,14 +178,24 @@ async def complete_opslog_entry(
176
178
  """Mark the opslog entry for ``external_run_id`` as completed.
177
179
 
178
180
  Looks up the latest in-progress row by ``external_run_id``, then
179
- re-inserts it with ``completed_at`` set and a bumped ``_row_version``
181
+ re-publishes it with ``completed_at`` set and a bumped ``_row_version``
180
182
  so ReplacingMergeTree converges to the completed state. Returns
181
183
  ``True`` when an entry was found and written, ``False`` otherwise.
182
184
  Silently no-ops when the entry is already completed or missing.
185
+
186
+ The open row reaches ClickHouse through the sink, so when the lookup
187
+ finds nothing it is retried once after one poll interval before the
188
+ entry is treated as missing. The start and the completion are
189
+ normally seconds to minutes apart, so the retry is a safety net.
183
190
  """
184
191
  rows = await clickhouse.client.Clickhouse.get_instance().query(
185
192
  _BY_EXTERNAL_RUN_SQL, {'run_id': external_run_id}
186
193
  )
194
+ if not rows:
195
+ await asyncio.sleep(SINK_POLL_INTERVAL_SECONDS)
196
+ rows = await clickhouse.client.Clickhouse.get_instance().query(
197
+ _BY_EXTERNAL_RUN_SQL, {'run_id': external_run_id}
198
+ )
187
199
  if not rows:
188
200
  LOGGER.debug(
189
201
  'complete_opslog_entry: no open entry for external_run_id=%s',
@@ -193,7 +205,7 @@ async def complete_opslog_entry(
193
205
  row = dict(rows[0])
194
206
  row['completed_at'] = completed_at
195
207
  row['_row_version'] = next_row_version(int(row['_row_version']))
196
- await _insert_row(row)
208
+ await _insert_row(row, 'deployments')
197
209
  return True
198
210
 
199
211
 
@@ -18,7 +18,7 @@ from imbi.api.endpoints._helpers import (
18
18
  from imbi.api.identity.host_integration import call_with_identity_retry
19
19
  from imbi.api.plugins import call_with_timeout
20
20
  from imbi.api.plugins.resolution import ResolvedCapability, resolve_capability
21
- from imbi.common import clickhouse, graph, valkey
21
+ from imbi.common import graph, iggy, valkey
22
22
  from imbi.common import models as common_models
23
23
  from imbi.common.plugins import decrypt_integration_credentials
24
24
  from imbi.common.plugins.base import (
@@ -412,8 +412,4 @@ async def _write_audit(
412
412
  )
413
413
  row = entry.model_dump(by_alias=True, mode='python')
414
414
  row['is_deleted'] = 1 if entry.is_deleted else 0
415
- await clickhouse.client.Clickhouse.get_instance().insert(
416
- 'operations_log',
417
- [list(row.values())],
418
- list(row.keys()),
419
- )
415
+ await iggy.publish_rows('operations_log', 'configuration', [row])