imbi-api 2.9.2__tar.gz → 2.10.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 (296) hide show
  1. {imbi_api-2.9.2 → imbi_api-2.10.0}/PKG-INFO +2 -2
  2. {imbi_api-2.9.2 → imbi_api-2.10.0}/pyproject.toml +2 -2
  3. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/commit_sync/service.py +118 -24
  4. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/domain/models.py +13 -1
  5. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/domain/scoring.py +8 -1
  6. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/dashboard.py +18 -7
  7. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_commit_sync.py +8 -1
  8. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/projects.py +177 -41
  9. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/releases.py +43 -0
  10. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/scoring_policies.py +2 -0
  11. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/users.py +28 -0
  12. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/models.py +2 -1
  13. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/scoring/queue.py +12 -6
  14. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_projects.py +12 -4
  15. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_projects_helpers.py +5 -5
  16. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_releases.py +29 -0
  17. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_scoring_policies.py +46 -0
  18. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_users.py +33 -12
  19. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_commit_sync_service.py +131 -2
  20. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_scoring_triggers.py +23 -0
  21. imbi_api-2.10.0/uv.lock +3209 -0
  22. imbi_api-2.9.2/uv.lock +0 -3209
  23. {imbi_api-2.9.2 → imbi_api-2.10.0}/.github/workflows/deploy.yaml +0 -0
  24. {imbi_api-2.9.2 → imbi_api-2.10.0}/.github/workflows/docs.yaml +0 -0
  25. {imbi_api-2.9.2 → imbi_api-2.10.0}/.github/workflows/testing.yaml +0 -0
  26. {imbi_api-2.9.2 → imbi_api-2.10.0}/.gitignore +0 -0
  27. {imbi_api-2.9.2 → imbi_api-2.10.0}/.pre-commit-config.yaml +0 -0
  28. {imbi_api-2.9.2 → imbi_api-2.10.0}/.python-version +0 -0
  29. {imbi_api-2.9.2 → imbi_api-2.10.0}/CLAUDE.md +0 -0
  30. {imbi_api-2.9.2 → imbi_api-2.10.0}/CODE_REVIEW_PUNCHLIST.md +0 -0
  31. {imbi_api-2.9.2 → imbi_api-2.10.0}/LICENSE +0 -0
  32. {imbi_api-2.9.2 → imbi_api-2.10.0}/README.md +0 -0
  33. {imbi_api-2.9.2 → imbi_api-2.10.0}/coderabbit.yaml +0 -0
  34. {imbi_api-2.9.2 → imbi_api-2.10.0}/compose.yaml +0 -0
  35. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0001-record-architecture-decisions.md +0 -0
  36. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0002-authentication-and-authorization-architecture.md +0 -0
  37. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0003-email-sending-architecture.md +0 -0
  38. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0004-phase-5-authentication-enhancements.md +0 -0
  39. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0005-file-upload-storage-architecture.md +0 -0
  40. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0006-project-identity-and-multi-type.md +0 -0
  41. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0007-relationship-blueprints.md +0 -0
  42. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0008-plugin-system-architecture.md +0 -0
  43. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0009-database-driven-oauth-providers.md +0 -0
  44. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0010-identity-plugin-architecture.md +0 -0
  45. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0011-graph-based-project-scoring.md +0 -0
  46. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0012-plugin-manifest-service-template.md +0 -0
  47. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0013-deployment-plugin-type.md +0 -0
  48. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0014-generic-plugin-entity-abstraction.md +0 -0
  49. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0015-cyclonedx-1.7-sbom-standard.md +0 -0
  50. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr.md +0 -0
  51. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/configuration.md +0 -0
  52. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/index.md +0 -0
  53. {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/restore-backup.md +0 -0
  54. {imbi_api-2.9.2 → imbi_api-2.10.0}/justfile +0 -0
  55. {imbi_api-2.9.2 → imbi_api-2.10.0}/mkdocs.yml +0 -0
  56. {imbi_api-2.9.2 → imbi_api-2.10.0}/mypy.ini +0 -0
  57. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/__init__.py +0 -0
  58. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/app.py +0 -0
  59. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/__init__.py +0 -0
  60. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/authorization_codes.py +0 -0
  61. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/local_auth.py +0 -0
  62. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/login_providers.py +0 -0
  63. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/membership.py +0 -0
  64. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/models.py +0 -0
  65. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/oauth.py +0 -0
  66. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/oauth_clients.py +0 -0
  67. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/password.py +0 -0
  68. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/permissions.py +0 -0
  69. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/seed.py +0 -0
  70. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/sessions.py +0 -0
  71. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/tokens.py +0 -0
  72. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/totp.py +0 -0
  73. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/backfill_embeddings.py +0 -0
  74. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/blueprint_attributes.py +0 -0
  75. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/commit_sync/__init__.py +0 -0
  76. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/commit_sync/queue.py +0 -0
  77. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/domain/__init__.py +0 -0
  78. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/__init__.py +0 -0
  79. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/client.py +0 -0
  80. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/dependencies.py +0 -0
  81. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/models.py +0 -0
  82. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/base.html +0 -0
  83. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/base.txt +0 -0
  84. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/password_reset.html +0 -0
  85. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/password_reset.txt +0 -0
  86. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/welcome.html +0 -0
  87. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/welcome.txt +0 -0
  88. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates.py +0 -0
  89. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/__init__.py +0 -0
  90. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_credentials.py +0 -0
  91. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_helpers.py +0 -0
  92. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_json_fields.py +0 -0
  93. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_pagination.py +0 -0
  94. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_request_urls.py +0 -0
  95. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/admin.py +0 -0
  96. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/admin_plugins.py +0 -0
  97. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/api_keys.py +0 -0
  98. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/auth.py +0 -0
  99. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/auth_providers.py +0 -0
  100. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/blueprints.py +0 -0
  101. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/client_credentials.py +0 -0
  102. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/comments.py +0 -0
  103. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/document_templates.py +0 -0
  104. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/documents.py +0 -0
  105. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/environments.py +0 -0
  106. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/events.py +0 -0
  107. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/graph_query.py +0 -0
  108. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/identity_plugins.py +0 -0
  109. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/link_definitions.py +0 -0
  110. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/local_auth.py +0 -0
  111. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/mcp_servers.py +0 -0
  112. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/mfa.py +0 -0
  113. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/oauth_metadata.py +0 -0
  114. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/operations_log.py +0 -0
  115. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/organizations.py +0 -0
  116. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/plugin_edges.py +0 -0
  117. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/plugin_entities.py +0 -0
  118. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/plugins.py +0 -0
  119. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_analysis.py +0 -0
  120. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_configuration.py +0 -0
  121. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_deployments.py +0 -0
  122. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_logs.py +0 -0
  123. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_plugins.py +0 -0
  124. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_type_plugins.py +0 -0
  125. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_types.py +0 -0
  126. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/pull_requests.py +0 -0
  127. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/roles.py +0 -0
  128. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/sa_api_keys.py +0 -0
  129. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/scoring.py +0 -0
  130. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/search.py +0 -0
  131. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/service_accounts.py +0 -0
  132. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/service_plugins.py +0 -0
  133. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/status.py +0 -0
  134. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/tags.py +0 -0
  135. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/teams.py +0 -0
  136. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/third_party_services.py +0 -0
  137. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/uploads.py +0 -0
  138. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/user_activity.py +0 -0
  139. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/webhooks.py +0 -0
  140. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/entrypoint.py +0 -0
  141. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/graph_sql.py +0 -0
  142. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/__init__.py +0 -0
  143. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/endpoints.py +0 -0
  144. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/errors.py +0 -0
  145. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/flows.py +0 -0
  146. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/host_integration.py +0 -0
  147. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/models.py +0 -0
  148. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/repository.py +0 -0
  149. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/resolution.py +0 -0
  150. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/state.py +0 -0
  151. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/sweeper.py +0 -0
  152. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/lifespans.py +0 -0
  153. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/llm/__init__.py +0 -0
  154. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/llm/dependencies.py +0 -0
  155. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/mcp_test.py +0 -0
  156. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/middleware/__init__.py +0 -0
  157. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/middleware/rate_limit.py +0 -0
  158. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/openapi.py +0 -0
  159. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/patch.py +0 -0
  160. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/__init__.py +0 -0
  161. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/assignment_writer.py +0 -0
  162. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/assignments.py +0 -0
  163. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/credentials.py +0 -0
  164. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/installer.py +0 -0
  165. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/lifecycle.py +0 -0
  166. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/lifecycle_dispatch.py +0 -0
  167. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/reload.py +0 -0
  168. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/resolution.py +0 -0
  169. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/schemas.py +0 -0
  170. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/prompts/__init__.py +0 -0
  171. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/prompts/release_notes_system.md +0 -0
  172. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/py.typed +0 -0
  173. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/relationships.py +0 -0
  174. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/sbom.py +0 -0
  175. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/scoring/__init__.py +0 -0
  176. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/settings.py +0 -0
  177. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/__init__.py +0 -0
  178. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/client.py +0 -0
  179. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/dependencies.py +0 -0
  180. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/thumbnails.py +0 -0
  181. {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/validation.py +0 -0
  182. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/__init__.py +0 -0
  183. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/__init__.py +0 -0
  184. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_api_key_auth.py +0 -0
  185. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_authentication.py +0 -0
  186. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_authorization.py +0 -0
  187. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_encryption.py +0 -0
  188. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_login_providers.py +0 -0
  189. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_membership.py +0 -0
  190. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_oauth.py +0 -0
  191. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_oauth_server.py +0 -0
  192. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_permissions.py +0 -0
  193. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_seed.py +0 -0
  194. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_sessions.py +0 -0
  195. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_totp.py +0 -0
  196. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/__init__.py +0 -0
  197. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/test_client.py +0 -0
  198. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/test_init.py +0 -0
  199. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/test_privacy.py +0 -0
  200. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/__init__.py +0 -0
  201. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_client.py +0 -0
  202. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_init.py +0 -0
  203. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_integration.py +0 -0
  204. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_models.py +0 -0
  205. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_templates.py +0 -0
  206. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/__init__.py +0 -0
  207. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_admin.py +0 -0
  208. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_admin_plugins.py +0 -0
  209. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_api_keys.py +0 -0
  210. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_auth.py +0 -0
  211. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_auth_providers.py +0 -0
  212. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_blueprints.py +0 -0
  213. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_client_credentials.py +0 -0
  214. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_comments.py +0 -0
  215. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_credentials.py +0 -0
  216. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_dashboard.py +0 -0
  217. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_document_templates.py +0 -0
  218. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_documents.py +0 -0
  219. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_environments.py +0 -0
  220. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_events.py +0 -0
  221. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_graph_query.py +0 -0
  222. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_init.py +0 -0
  223. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_json_fields.py +0 -0
  224. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_link_definitions.py +0 -0
  225. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_local_auth.py +0 -0
  226. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_mcp_servers.py +0 -0
  227. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_mfa.py +0 -0
  228. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_operations_log.py +0 -0
  229. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_organizations.py +0 -0
  230. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_pagination.py +0 -0
  231. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
  232. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_plugins.py +0 -0
  233. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_analysis.py +0 -0
  234. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_commit_sync.py +0 -0
  235. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_configuration.py +0 -0
  236. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_deployments.py +0 -0
  237. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_logs.py +0 -0
  238. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_plugins.py +0 -0
  239. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
  240. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_type_plugins.py +0 -0
  241. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_types.py +0 -0
  242. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_pull_requests.py +0 -0
  243. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_request_urls.py +0 -0
  244. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_roles.py +0 -0
  245. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_sa_api_keys.py +0 -0
  246. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_scoring.py +0 -0
  247. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_search.py +0 -0
  248. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_service_accounts.py +0 -0
  249. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_service_plugins.py +0 -0
  250. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_service_writeback.py +0 -0
  251. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_status.py +0 -0
  252. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_tags.py +0 -0
  253. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_teams.py +0 -0
  254. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_third_party_services.py +0 -0
  255. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_uploads.py +0 -0
  256. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_user_activity.py +0 -0
  257. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_webhooks.py +0 -0
  258. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
  259. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
  260. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/fixtures/sbom/tiny.json +0 -0
  261. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/__init__.py +0 -0
  262. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_endpoints.py +0 -0
  263. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_errors.py +0 -0
  264. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_flows.py +0 -0
  265. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_host_integration.py +0 -0
  266. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_repository.py +0 -0
  267. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_resolution.py +0 -0
  268. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_state.py +0 -0
  269. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_sweeper.py +0 -0
  270. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/middleware/test_rate_limit.py +0 -0
  271. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/__init__.py +0 -0
  272. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_client.py +0 -0
  273. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_init.py +0 -0
  274. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_thumbnails.py +0 -0
  275. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_validation.py +0 -0
  276. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/support.py +0 -0
  277. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_app.py +0 -0
  278. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_assignment_writer.py +0 -0
  279. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_backfill_embeddings.py +0 -0
  280. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_blueprint_attributes.py +0 -0
  281. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_blueprints.py +0 -0
  282. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_commit_sync_queue.py +0 -0
  283. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_entrypoint.py +0 -0
  284. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_graph_sql.py +0 -0
  285. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_init.py +0 -0
  286. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_installer.py +0 -0
  287. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_lifecycle_dispatch.py +0 -0
  288. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_lifespans.py +0 -0
  289. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_models.py +0 -0
  290. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_openapi.py +0 -0
  291. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_patch.py +0 -0
  292. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_plugin_schemas.py +0 -0
  293. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_plugins.py +0 -0
  294. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_sbom.py +0 -0
  295. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_scoring_queue.py +0 -0
  296. {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: imbi-api
3
- Version: 2.9.2
3
+ Version: 2.10.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: BSD-3-Clause
@@ -20,7 +20,7 @@ Requires-Dist: cyclonedx-python-lib<12,>=11.7
20
20
  Requires-Dist: fastapi
21
21
  Requires-Dist: filetype
22
22
  Requires-Dist: httpx<1,>=0.28.1
23
- Requires-Dist: imbi-common[databases,llm,sentry,server]==2.9.2
23
+ Requires-Dist: imbi-common[databases,llm,sentry,server]==2.10.0
24
24
  Requires-Dist: jinja2
25
25
  Requires-Dist: jsonpatch>=1.33
26
26
  Requires-Dist: mcp>=1.26.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "imbi-api"
3
- version = "2.9.2"
3
+ version = "2.10.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.9.2",
31
+ "imbi-common[databases,llm,sentry,server]==2.10.0",
32
32
  "jinja2",
33
33
  "jsonpatch>=1.33",
34
34
  "mcp>=1.26.0",
@@ -13,6 +13,7 @@ node so the UI can poll it without a dedicated status store.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
+ import asyncio
16
17
  import datetime
17
18
  import logging
18
19
  import typing
@@ -24,6 +25,8 @@ from imbi_common.plugins.base import PluginContext, ServicePlugin
24
25
  from imbi_common.plugins.errors import PluginNotFoundError
25
26
  from imbi_common.plugins.registry import RegistryEntry, get_plugin
26
27
 
28
+ from imbi_api import models
29
+ from imbi_api.identity import repository as identity_repository
27
30
  from imbi_api.plugins import parse_options
28
31
  from imbi_api.plugins.credentials import get_plugin_credentials
29
32
 
@@ -33,6 +36,12 @@ _COMMIT_SYNC_SLUG = 'github-commit-sync'
33
36
  # Persisted error strings are truncated so a noisy upstream message can't
34
37
  # bloat the Project node.
35
38
  _MAX_ERROR_LEN = 500
39
+ # Apache AGE aborts a SET with "Entity failed to be updated: <n>"
40
+ # (heap_update -> TM_Updated) when another transaction updates the same
41
+ # Project vertex concurrently. The worker's authoritative status writes
42
+ # retry the transient conflict so they still land under contention.
43
+ _STATUS_WRITE_RETRIES = 3
44
+ _STATUS_RETRY_BACKOFF = 0.05
36
45
 
37
46
  SyncState = typing.Literal['idle', 'queued', 'running', 'success', 'failed']
38
47
 
@@ -130,6 +139,61 @@ async def _resolve_plugin(
130
139
  )
131
140
 
132
141
 
142
+ def _identity_plugin_slugs(service_plugins: list[ServicePlugin]) -> list[str]:
143
+ """Return connected plugin slugs registered as identity plugins."""
144
+ slugs: list[str] = []
145
+ for plugin in service_plugins:
146
+ try:
147
+ entry = get_plugin(plugin.slug)
148
+ except PluginNotFoundError:
149
+ continue
150
+ if entry.manifest.plugin_type == 'identity':
151
+ slugs.append(plugin.slug)
152
+ return slugs
153
+
154
+
155
+ def _make_user_resolver(
156
+ db: graph.Graph, service_plugins: list[ServicePlugin]
157
+ ) -> abc.Callable[[str], abc.Awaitable[str | None]] | None:
158
+ """Build the ``PluginContext.resolve_user_by_identity`` resolver.
159
+
160
+ Maps an external identity *subject* (a GitHub numeric user id) to the
161
+ matching Imbi user's email by querying the graph directly -- no HTTP,
162
+ since this runs inside imbi-api on a background backfill. Connected
163
+ plugins are filtered to identity plugins; ``None`` is returned when
164
+ none qualify so the action skips the lookups entirely. A subject
165
+ resolving to two different users across plugins is treated as
166
+ unresolved (logged), never mis-attributed.
167
+ """
168
+ identity_slugs = _identity_plugin_slugs(service_plugins)
169
+ if not identity_slugs:
170
+ return None
171
+
172
+ async def _resolve(subject: str) -> str | None:
173
+ emails: set[str] = set()
174
+ for slug in identity_slugs:
175
+ user_id = await identity_repository.find_user_by_subject(
176
+ db, slug, subject
177
+ )
178
+ if user_id is None:
179
+ continue
180
+ users = await db.match(models.User, {'id': user_id})
181
+ if users:
182
+ emails.add(users[0].email)
183
+ if len(emails) > 1:
184
+ LOGGER.error(
185
+ 'Identity subject %r resolved to multiple Imbi users via '
186
+ 'plugins %r: %r — leaving unattributed',
187
+ subject,
188
+ identity_slugs,
189
+ sorted(emails),
190
+ )
191
+ return None
192
+ return next(iter(emails), None)
193
+
194
+ return _resolve
195
+
196
+
133
197
  async def _build_context(
134
198
  db: graph.Graph,
135
199
  org_slug: str,
@@ -167,6 +231,9 @@ async def _build_context(
167
231
  project_type_slugs=project_type_slugs,
168
232
  third_party_service_slug=resolved.tps_slug or None,
169
233
  service_connections=service_connections,
234
+ resolve_user_by_identity=_make_user_resolver(
235
+ db, resolved.service_plugins
236
+ ),
170
237
  )
171
238
 
172
239
 
@@ -210,6 +277,11 @@ def _now_iso() -> str:
210
277
  return datetime.datetime.now(datetime.UTC).isoformat()
211
278
 
212
279
 
280
+ def _is_write_conflict(exc: Exception) -> bool:
281
+ """True if *exc* is AGE's concurrent-update conflict (TM_Updated)."""
282
+ return 'failed to be updated' in str(exc)
283
+
284
+
213
285
  async def set_status(
214
286
  db: graph.Graph,
215
287
  project_id: str,
@@ -219,8 +291,17 @@ async def set_status(
219
291
  commits: int = 0,
220
292
  tags: int = 0,
221
293
  error: str = '',
294
+ retry: bool = True,
222
295
  ) -> None:
223
- """Persist last-sync state on the ``Project`` node (best-effort)."""
296
+ """Persist last-sync state on the ``Project`` node (best-effort).
297
+
298
+ *retry* re-attempts the write when AGE reports a transient concurrent
299
+ update, so the worker's authoritative transitions (running -> success/
300
+ failed) still land if a webhook touches the project mid-write. The
301
+ enqueue endpoint passes ``retry=False`` for its optimistic ``queued``
302
+ write: dropping that on conflict is correct, since the worker's newer
303
+ ``running`` write must win rather than be clobbered back to ``queued``.
304
+ """
224
305
  query: typing.LiteralString = """
225
306
  MATCH (p:Project {{id: {project_id}}})
226
307
  SET p.commit_sync_status = {status},
@@ -231,26 +312,39 @@ async def set_status(
231
312
  p.commit_sync_error = {error}
232
313
  RETURN p.id AS id
233
314
  """
234
- try:
235
- await db.execute(
236
- query,
237
- {
238
- 'project_id': project_id,
239
- 'status': status,
240
- 'at': _now_iso(),
241
- 'by': requested_by,
242
- 'commits': commits,
243
- 'tags': tags,
244
- 'error': error[:_MAX_ERROR_LEN],
245
- },
246
- ['id'],
247
- )
248
- except Exception: # noqa: BLE001
249
- LOGGER.warning(
250
- 'Failed to persist commit-sync status for project %s',
251
- project_id,
252
- exc_info=True,
253
- )
315
+ params = {
316
+ 'project_id': project_id,
317
+ 'status': status,
318
+ 'at': _now_iso(),
319
+ 'by': requested_by,
320
+ 'commits': commits,
321
+ 'tags': tags,
322
+ 'error': error[:_MAX_ERROR_LEN],
323
+ }
324
+ attempts = _STATUS_WRITE_RETRIES if retry else 1
325
+ for attempt in range(attempts):
326
+ try:
327
+ await db.execute(query, params, ['id'])
328
+ return
329
+ except Exception as exc: # noqa: BLE001
330
+ conflict = _is_write_conflict(exc)
331
+ if retry and conflict and attempt + 1 < attempts:
332
+ await asyncio.sleep(_STATUS_RETRY_BACKOFF * (attempt + 1))
333
+ continue
334
+ if conflict:
335
+ LOGGER.debug(
336
+ 'commit-sync status write for %s lost a concurrent '
337
+ 'update (status=%s); leaving the newer state in place',
338
+ project_id,
339
+ status,
340
+ )
341
+ else:
342
+ LOGGER.warning(
343
+ 'Failed to persist commit-sync status for project %s',
344
+ project_id,
345
+ exc_info=True,
346
+ )
347
+ return
254
348
 
255
349
 
256
350
  def _opt_str(value: object) -> str | None:
@@ -277,7 +371,7 @@ async def read_status(db: graph.Graph, project_id: str) -> CommitSyncStatus:
277
371
  MATCH (p:Project {{id: {project_id}}})
278
372
  RETURN p.commit_sync_status AS status,
279
373
  p.commit_sync_at AS at,
280
- p.commit_sync_by AS by,
374
+ p.commit_sync_by AS requested_by,
281
375
  p.commit_sync_commits AS commits,
282
376
  p.commit_sync_tags AS tags,
283
377
  p.commit_sync_error AS error
@@ -285,7 +379,7 @@ async def read_status(db: graph.Graph, project_id: str) -> CommitSyncStatus:
285
379
  records = await db.execute(
286
380
  query,
287
381
  {'project_id': project_id},
288
- ['status', 'at', 'by', 'commits', 'tags', 'error'],
382
+ ['status', 'at', 'requested_by', 'commits', 'tags', 'error'],
289
383
  )
290
384
  if not records:
291
385
  return CommitSyncStatus()
@@ -307,5 +401,5 @@ async def read_status(db: graph.Graph, project_id: str) -> CommitSyncStatus:
307
401
  commits_synced=_opt_int(graph.parse_agtype(row.get('commits'))),
308
402
  tags_synced=_opt_int(graph.parse_agtype(row.get('tags'))),
309
403
  error=_opt_str(graph.parse_agtype(row.get('error'))),
310
- requested_by=_opt_str(graph.parse_agtype(row.get('by'))),
404
+ requested_by=_opt_str(graph.parse_agtype(row.get('requested_by'))),
311
405
  )
@@ -55,6 +55,7 @@ __all__ = [
55
55
  'ServiceApplicationSecrets',
56
56
  'Session',
57
57
  'TOTPSecret',
58
+ 'TeamMembership',
58
59
  'ThirdPartyService',
59
60
  'ThirdPartyServiceCreate',
60
61
  'ThirdPartyServiceResponse',
@@ -224,6 +225,15 @@ class OrgMembership(pydantic.BaseModel):
224
225
  role: str
225
226
 
226
227
 
228
+ class TeamMembership(pydantic.BaseModel):
229
+ """Team membership for API responses."""
230
+
231
+ model_config = pydantic.ConfigDict(extra='ignore')
232
+ team_name: str
233
+ team_slug: str
234
+ organization_slug: str
235
+
236
+
227
237
  class Role(models.Node): # type: ignore[misc]
228
238
  """Role for grouping permissions."""
229
239
 
@@ -445,12 +455,14 @@ class CurrentUserResponse(UserResponse):
445
455
  """Response model for the authenticated user's own profile.
446
456
 
447
457
  Extends ``UserResponse`` with the caller's effective permission set
448
- so the UI can gate features in a single request. Only exposed via
458
+ and team memberships so the UI can gate features and offer
459
+ membership-scoped filters in a single request. Only exposed via
449
460
  ``GET /users/me`` — the per-email endpoint deliberately omits
450
461
  permissions to avoid disclosing one user's grants to another.
451
462
  """
452
463
 
453
464
  permissions: list[str] = []
465
+ teams: list[TeamMembership] = []
454
466
 
455
467
 
456
468
  class PasswordChangeRequest(pydantic.BaseModel):
@@ -58,7 +58,12 @@ class PolicyCreate(pydantic.BaseModel):
58
58
  slug: str
59
59
  description: str | None = None
60
60
  category: typing.Literal[
61
- 'attribute', 'presence', 'link_presence', 'age', 'analysis_result'
61
+ 'attribute',
62
+ 'presence',
63
+ 'link_presence',
64
+ 'age',
65
+ 'analysis_result',
66
+ 'deployment_status',
62
67
  ] = 'attribute'
63
68
  weight: int = pydantic.Field(ge=0, le=100)
64
69
  enabled: bool = True
@@ -67,6 +72,7 @@ class PolicyCreate(pydantic.BaseModel):
67
72
  attribute_name: str | None = None
68
73
  link_slug: str | None = None
69
74
  result_slug: str | None = None
75
+ environment_slug: str | None = None
70
76
  present_score: int | None = pydantic.Field(default=None, ge=0, le=100)
71
77
  missing_score: int | None = pydantic.Field(default=None, ge=0, le=100)
72
78
  value_score_map: dict[str, int] | None = None
@@ -85,6 +91,7 @@ class PolicyUpdate(pydantic.BaseModel):
85
91
  attribute_name: str | None = None
86
92
  link_slug: str | None = None
87
93
  result_slug: str | None = None
94
+ environment_slug: str | None = None
88
95
  weight: int | None = pydantic.Field(default=None, ge=0, le=100)
89
96
  enabled: bool | None = None
90
97
  priority: int | None = None
@@ -37,8 +37,11 @@ class DatastoreStatus(pydantic.BaseModel):
37
37
  status: typing.Literal['ok', 'error']
38
38
  latency_ms: float | None = None
39
39
  # Bytes on disk (Postgres/ClickHouse) or resident memory (Valkey,
40
- # which is in-memory). ``None`` when the size probe failed.
40
+ # which is in-memory). ``None`` when the size probe failed. For
41
+ # ClickHouse this is the Imbi application database; ``total_bytes``
42
+ # carries the whole-server footprint (incl. ``system.*``).
41
43
  size_bytes: int | None = None
44
+ total_bytes: int | None = None
42
45
  detail: str | None = None
43
46
 
44
47
 
@@ -134,13 +137,19 @@ async def _check_postgres(db: graph.Graph) -> DatastoreStatus:
134
137
 
135
138
 
136
139
  async def _check_clickhouse() -> DatastoreStatus:
137
- """Probe ClickHouse and read total on-disk size of active parts."""
140
+ """Probe ClickHouse for the app-database and whole-server disk size.
141
+
142
+ ``size`` is the active-part footprint of the connected (Imbi)
143
+ database; ``total`` spans every database, including ``system.*``.
144
+ """
138
145
  start = time.perf_counter()
139
146
  try:
140
147
  rows = await asyncio.wait_for(
141
148
  clickhouse.query(
142
- 'SELECT sum(bytes_on_disk) AS size FROM system.parts'
143
- " WHERE active AND database != 'system'"
149
+ 'SELECT'
150
+ ' sumIf(bytes_on_disk, database = currentDatabase()) AS size,'
151
+ ' sum(bytes_on_disk) AS total'
152
+ ' FROM system.parts WHERE active'
144
153
  ),
145
154
  _CHECK_TIMEOUT,
146
155
  )
@@ -152,14 +161,16 @@ async def _check_clickhouse() -> DatastoreStatus:
152
161
  status='error',
153
162
  detail=str(err),
154
163
  )
155
- raw = rows[0].get('size') if rows else None
156
- size = int(raw) if raw is not None else None
164
+ row = rows[0] if rows else {}
165
+ size_raw = row.get('size')
166
+ total_raw = row.get('total')
157
167
  return DatastoreStatus(
158
168
  name='ClickHouse',
159
169
  role='Timeseries data',
160
170
  status='ok',
161
171
  latency_ms=_ms(start),
162
- size_bytes=size,
172
+ size_bytes=int(size_raw) if size_raw is not None else None,
173
+ total_bytes=int(total_raw) if total_raw is not None else None,
163
174
  )
164
175
 
165
176
 
@@ -60,8 +60,15 @@ async def sync_commits_and_tags(
60
60
  valkey_client, org_slug, project_id, requested_by
61
61
  )
62
62
  if enqueued:
63
+ # Optimistic, best-effort: if the worker has already flipped the
64
+ # project to ``running``, that newer write must win -- so this one
65
+ # does not retry the concurrent-update conflict.
63
66
  await service.set_status(
64
- db, project_id, status='queued', requested_by=requested_by
67
+ db,
68
+ project_id,
69
+ status='queued',
70
+ requested_by=requested_by,
71
+ retry=False,
65
72
  )
66
73
  return CommitSyncEnqueueResponse(enqueued=enqueued)
67
74