imbi-api 2.9.0__tar.gz → 2.9.1__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 (288) hide show
  1. {imbi_api-2.9.0 → imbi_api-2.9.1}/PKG-INFO +3 -3
  2. {imbi_api-2.9.0 → imbi_api-2.9.1}/pyproject.toml +3 -3
  3. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/domain/models.py +4 -3
  4. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_deployments.py +1 -1
  5. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_plugins.py +3 -3
  6. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_type_plugins.py +3 -3
  7. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/service_plugins.py +26 -17
  8. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/third_party_services.py +3 -3
  9. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/assignment_writer.py +3 -3
  10. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/assignments.py +19 -11
  11. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/lifecycle_dispatch.py +1 -1
  12. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/resolution.py +22 -18
  13. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_plugins.py +8 -8
  14. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_type_plugins.py +7 -7
  15. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_service_plugins.py +25 -7
  16. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_assignment_writer.py +3 -3
  17. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_plugins.py +13 -13
  18. {imbi_api-2.9.0 → imbi_api-2.9.1}/uv.lock +9 -10
  19. {imbi_api-2.9.0 → imbi_api-2.9.1}/.github/workflows/deploy.yaml +0 -0
  20. {imbi_api-2.9.0 → imbi_api-2.9.1}/.github/workflows/docs.yaml +0 -0
  21. {imbi_api-2.9.0 → imbi_api-2.9.1}/.github/workflows/testing.yaml +0 -0
  22. {imbi_api-2.9.0 → imbi_api-2.9.1}/.gitignore +0 -0
  23. {imbi_api-2.9.0 → imbi_api-2.9.1}/.pre-commit-config.yaml +0 -0
  24. {imbi_api-2.9.0 → imbi_api-2.9.1}/.python-version +0 -0
  25. {imbi_api-2.9.0 → imbi_api-2.9.1}/CLAUDE.md +0 -0
  26. {imbi_api-2.9.0 → imbi_api-2.9.1}/CODE_REVIEW_PUNCHLIST.md +0 -0
  27. {imbi_api-2.9.0 → imbi_api-2.9.1}/LICENSE +0 -0
  28. {imbi_api-2.9.0 → imbi_api-2.9.1}/README.md +0 -0
  29. {imbi_api-2.9.0 → imbi_api-2.9.1}/coderabbit.yaml +0 -0
  30. {imbi_api-2.9.0 → imbi_api-2.9.1}/compose.yaml +0 -0
  31. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0001-record-architecture-decisions.md +0 -0
  32. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0002-authentication-and-authorization-architecture.md +0 -0
  33. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0003-email-sending-architecture.md +0 -0
  34. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0004-phase-5-authentication-enhancements.md +0 -0
  35. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0005-file-upload-storage-architecture.md +0 -0
  36. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0006-project-identity-and-multi-type.md +0 -0
  37. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0007-relationship-blueprints.md +0 -0
  38. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0008-plugin-system-architecture.md +0 -0
  39. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0009-database-driven-oauth-providers.md +0 -0
  40. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0010-identity-plugin-architecture.md +0 -0
  41. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0011-graph-based-project-scoring.md +0 -0
  42. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0012-plugin-manifest-service-template.md +0 -0
  43. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0013-deployment-plugin-type.md +0 -0
  44. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0014-generic-plugin-entity-abstraction.md +0 -0
  45. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr/0015-cyclonedx-1.7-sbom-standard.md +0 -0
  46. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/adr.md +0 -0
  47. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/configuration.md +0 -0
  48. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/index.md +0 -0
  49. {imbi_api-2.9.0 → imbi_api-2.9.1}/docs/restore-backup.md +0 -0
  50. {imbi_api-2.9.0 → imbi_api-2.9.1}/justfile +0 -0
  51. {imbi_api-2.9.0 → imbi_api-2.9.1}/mkdocs.yml +0 -0
  52. {imbi_api-2.9.0 → imbi_api-2.9.1}/mypy.ini +0 -0
  53. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/__init__.py +0 -0
  54. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/app.py +0 -0
  55. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/__init__.py +0 -0
  56. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/authorization_codes.py +0 -0
  57. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/local_auth.py +0 -0
  58. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/login_providers.py +0 -0
  59. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/membership.py +0 -0
  60. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/models.py +0 -0
  61. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/oauth.py +0 -0
  62. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/oauth_clients.py +0 -0
  63. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/password.py +0 -0
  64. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/permissions.py +0 -0
  65. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/seed.py +0 -0
  66. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/sessions.py +0 -0
  67. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/tokens.py +0 -0
  68. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/auth/totp.py +0 -0
  69. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/backfill_embeddings.py +0 -0
  70. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/blueprint_attributes.py +0 -0
  71. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/domain/__init__.py +0 -0
  72. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/domain/scoring.py +0 -0
  73. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/__init__.py +0 -0
  74. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/client.py +0 -0
  75. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/dependencies.py +0 -0
  76. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/models.py +0 -0
  77. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates/base.html +0 -0
  78. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates/base.txt +0 -0
  79. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates/password_reset.html +0 -0
  80. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates/password_reset.txt +0 -0
  81. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates/welcome.html +0 -0
  82. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates/welcome.txt +0 -0
  83. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/email/templates.py +0 -0
  84. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/__init__.py +0 -0
  85. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/_credentials.py +0 -0
  86. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/_helpers.py +0 -0
  87. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/_json_fields.py +0 -0
  88. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/_pagination.py +0 -0
  89. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/_request_urls.py +0 -0
  90. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/admin.py +0 -0
  91. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/admin_plugins.py +0 -0
  92. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/api_keys.py +0 -0
  93. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/auth.py +0 -0
  94. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/auth_providers.py +0 -0
  95. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/blueprints.py +0 -0
  96. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/client_credentials.py +0 -0
  97. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/comments.py +0 -0
  98. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/dashboard.py +0 -0
  99. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/document_templates.py +0 -0
  100. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/documents.py +0 -0
  101. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/environments.py +0 -0
  102. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/events.py +0 -0
  103. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/graph_query.py +0 -0
  104. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/identity_plugins.py +0 -0
  105. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/link_definitions.py +0 -0
  106. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/local_auth.py +0 -0
  107. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/mcp_servers.py +0 -0
  108. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/mfa.py +0 -0
  109. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/oauth_metadata.py +0 -0
  110. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/operations_log.py +0 -0
  111. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/organizations.py +0 -0
  112. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/plugin_edges.py +0 -0
  113. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/plugin_entities.py +0 -0
  114. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/plugins.py +0 -0
  115. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_analysis.py +0 -0
  116. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_configuration.py +0 -0
  117. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_logs.py +0 -0
  118. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/project_types.py +0 -0
  119. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/projects.py +0 -0
  120. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/pull_requests.py +0 -0
  121. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/releases.py +0 -0
  122. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/roles.py +0 -0
  123. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/sa_api_keys.py +0 -0
  124. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/scoring.py +0 -0
  125. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/scoring_policies.py +0 -0
  126. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/search.py +0 -0
  127. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/service_accounts.py +0 -0
  128. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/status.py +0 -0
  129. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/tags.py +0 -0
  130. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/teams.py +0 -0
  131. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/uploads.py +0 -0
  132. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/user_activity.py +0 -0
  133. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/users.py +0 -0
  134. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/endpoints/webhooks.py +0 -0
  135. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/entrypoint.py +0 -0
  136. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/graph_sql.py +0 -0
  137. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/__init__.py +0 -0
  138. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/endpoints.py +0 -0
  139. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/errors.py +0 -0
  140. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/flows.py +0 -0
  141. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/host_integration.py +0 -0
  142. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/models.py +0 -0
  143. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/repository.py +0 -0
  144. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/resolution.py +0 -0
  145. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/state.py +0 -0
  146. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/identity/sweeper.py +0 -0
  147. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/lifespans.py +0 -0
  148. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/llm/__init__.py +0 -0
  149. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/llm/dependencies.py +0 -0
  150. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/mcp_test.py +0 -0
  151. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/middleware/__init__.py +0 -0
  152. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/middleware/rate_limit.py +0 -0
  153. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/models.py +0 -0
  154. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/openapi.py +0 -0
  155. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/patch.py +0 -0
  156. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/__init__.py +0 -0
  157. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/credentials.py +0 -0
  158. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/installer.py +0 -0
  159. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/lifecycle.py +0 -0
  160. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/reload.py +0 -0
  161. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/plugins/schemas.py +0 -0
  162. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/prompts/__init__.py +0 -0
  163. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/prompts/release_notes_system.md +0 -0
  164. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/py.typed +0 -0
  165. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/relationships.py +0 -0
  166. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/sbom.py +0 -0
  167. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/scoring/__init__.py +0 -0
  168. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/scoring/queue.py +0 -0
  169. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/settings.py +0 -0
  170. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/storage/__init__.py +0 -0
  171. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/storage/client.py +0 -0
  172. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/storage/dependencies.py +0 -0
  173. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/storage/thumbnails.py +0 -0
  174. {imbi_api-2.9.0 → imbi_api-2.9.1}/src/imbi_api/storage/validation.py +0 -0
  175. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/__init__.py +0 -0
  176. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/__init__.py +0 -0
  177. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_api_key_auth.py +0 -0
  178. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_authentication.py +0 -0
  179. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_authorization.py +0 -0
  180. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_encryption.py +0 -0
  181. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_login_providers.py +0 -0
  182. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_membership.py +0 -0
  183. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_oauth.py +0 -0
  184. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_oauth_server.py +0 -0
  185. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_permissions.py +0 -0
  186. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_seed.py +0 -0
  187. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_sessions.py +0 -0
  188. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/auth/test_totp.py +0 -0
  189. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/clickhouse/__init__.py +0 -0
  190. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/clickhouse/test_client.py +0 -0
  191. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/clickhouse/test_init.py +0 -0
  192. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/clickhouse/test_privacy.py +0 -0
  193. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/email/__init__.py +0 -0
  194. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/email/test_client.py +0 -0
  195. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/email/test_init.py +0 -0
  196. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/email/test_integration.py +0 -0
  197. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/email/test_models.py +0 -0
  198. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/email/test_templates.py +0 -0
  199. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/__init__.py +0 -0
  200. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_admin.py +0 -0
  201. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_admin_plugins.py +0 -0
  202. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_api_keys.py +0 -0
  203. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_auth.py +0 -0
  204. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_auth_providers.py +0 -0
  205. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_blueprints.py +0 -0
  206. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_client_credentials.py +0 -0
  207. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_comments.py +0 -0
  208. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_credentials.py +0 -0
  209. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_dashboard.py +0 -0
  210. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_document_templates.py +0 -0
  211. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_documents.py +0 -0
  212. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_environments.py +0 -0
  213. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_events.py +0 -0
  214. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_graph_query.py +0 -0
  215. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_init.py +0 -0
  216. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_json_fields.py +0 -0
  217. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_link_definitions.py +0 -0
  218. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_local_auth.py +0 -0
  219. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_mcp_servers.py +0 -0
  220. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_mfa.py +0 -0
  221. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_operations_log.py +0 -0
  222. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_organizations.py +0 -0
  223. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_pagination.py +0 -0
  224. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_plugin_label_validation.py +0 -0
  225. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_plugins.py +0 -0
  226. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_analysis.py +0 -0
  227. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_configuration.py +0 -0
  228. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_deployments.py +0 -0
  229. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_logs.py +0 -0
  230. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_response_surfacing.py +0 -0
  231. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_project_types.py +0 -0
  232. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_projects.py +0 -0
  233. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_projects_helpers.py +0 -0
  234. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_pull_requests.py +0 -0
  235. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_releases.py +0 -0
  236. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_request_urls.py +0 -0
  237. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_roles.py +0 -0
  238. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_sa_api_keys.py +0 -0
  239. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_scoring.py +0 -0
  240. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_scoring_policies.py +0 -0
  241. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_search.py +0 -0
  242. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_service_accounts.py +0 -0
  243. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_service_writeback.py +0 -0
  244. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_status.py +0 -0
  245. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_tags.py +0 -0
  246. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_teams.py +0 -0
  247. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_third_party_services.py +0 -0
  248. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_uploads.py +0 -0
  249. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_user_activity.py +0 -0
  250. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_users.py +0 -0
  251. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/endpoints/test_webhooks.py +0 -0
  252. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/fixtures/sbom/npm-realistic.json +0 -0
  253. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/fixtures/sbom/pypi-realistic.json +0 -0
  254. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/fixtures/sbom/tiny.json +0 -0
  255. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/__init__.py +0 -0
  256. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_endpoints.py +0 -0
  257. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_errors.py +0 -0
  258. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_flows.py +0 -0
  259. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_host_integration.py +0 -0
  260. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_repository.py +0 -0
  261. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_resolution.py +0 -0
  262. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_state.py +0 -0
  263. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/identity/test_sweeper.py +0 -0
  264. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/middleware/test_rate_limit.py +0 -0
  265. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/storage/__init__.py +0 -0
  266. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/storage/test_client.py +0 -0
  267. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/storage/test_init.py +0 -0
  268. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/storage/test_thumbnails.py +0 -0
  269. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/storage/test_validation.py +0 -0
  270. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/support.py +0 -0
  271. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_app.py +0 -0
  272. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_backfill_embeddings.py +0 -0
  273. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_blueprint_attributes.py +0 -0
  274. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_blueprints.py +0 -0
  275. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_entrypoint.py +0 -0
  276. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_graph_sql.py +0 -0
  277. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_init.py +0 -0
  278. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_installer.py +0 -0
  279. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_lifecycle_dispatch.py +0 -0
  280. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_lifespans.py +0 -0
  281. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_models.py +0 -0
  282. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_openapi.py +0 -0
  283. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_patch.py +0 -0
  284. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_plugin_schemas.py +0 -0
  285. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_sbom.py +0 -0
  286. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_scoring_queue.py +0 -0
  287. {imbi_api-2.9.0 → imbi_api-2.9.1}/tests/test_scoring_triggers.py +0 -0
  288. {imbi_api-2.9.0 → imbi_api-2.9.1}/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.0
3
+ Version: 2.9.1
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.0
23
+ Requires-Dist: imbi-common[databases,llm,sentry,server]==2.9.1
24
24
  Requires-Dist: jinja2
25
25
  Requires-Dist: jsonpatch>=1.33
26
26
  Requires-Dist: mcp>=1.26.0
@@ -31,7 +31,7 @@ Requires-Dist: pydantic[email]
31
31
  Requires-Dist: pyotp>=2.9.0
32
32
  Requires-Dist: python-multipart>=0.0.26
33
33
  Requires-Dist: qrcode>=8.0
34
- Requires-Dist: sentry-sdk>=2.48.0
34
+ Requires-Dist: sentry-sdk<3,>=2.48.0
35
35
  Requires-Dist: slowapi==0.1.9
36
36
  Requires-Dist: typer
37
37
  Requires-Dist: uvicorn
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "imbi-api"
3
- version = "2.9.0"
3
+ version = "2.9.1"
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.0",
31
+ "imbi-common[databases,llm,sentry,server]==2.9.1",
32
32
  "jinja2",
33
33
  "jsonpatch>=1.33",
34
34
  "mcp>=1.26.0",
@@ -38,7 +38,7 @@ dependencies = [
38
38
  "pyotp>=2.9.0",
39
39
  "python-multipart>=0.0.26",
40
40
  "qrcode>=8.0",
41
- "sentry-sdk>=2.48.0",
41
+ "sentry-sdk>=2.48.0,<3", # 3.0 line abandoned upstream; stay on 2.x
42
42
  "slowapi==0.1.9", # Pin: alpha quality, API may change
43
43
  "typer",
44
44
  "uvicorn",
@@ -12,6 +12,7 @@ import typing
12
12
 
13
13
  import pydantic
14
14
  from imbi_common import graph, models
15
+ from imbi_common.plugins import PluginType
15
16
 
16
17
  __all__ = [
17
18
  'SECRET_FIELDS',
@@ -880,7 +881,7 @@ class PluginAssignmentCreate(pydantic.BaseModel):
880
881
  """Request model for assigning a plugin to a project-type or project."""
881
882
 
882
883
  plugin_id: str
883
- tab: typing.Literal['configuration', 'logs', 'deployment', 'lifecycle']
884
+ plugin_type: PluginType
884
885
  default: bool = False
885
886
  options: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
886
887
  identity_plugin_id: str | None = None
@@ -899,7 +900,7 @@ class PluginAssignmentResponse(pydantic.BaseModel):
899
900
  plugin_id: str
900
901
  plugin_slug: str
901
902
  label: str
902
- tab: typing.Literal['configuration', 'logs', 'deployment', 'lifecycle']
903
+ plugin_type: PluginType
903
904
  default: bool
904
905
  options: dict[str, typing.Any] = {}
905
906
  source: typing.Literal['project', 'project_type', 'merged'] = (
@@ -1227,7 +1228,7 @@ class WebhookResponse(pydantic.BaseModel):
1227
1228
  config: dict[str, typing.Any] | list[typing.Any]
1228
1229
  try:
1229
1230
  config = json.loads(raw_config) if raw_config else {}
1230
- except (json.JSONDecodeError, TypeError):
1231
+ except json.JSONDecodeError, TypeError:
1231
1232
  config = {}
1232
1233
  rules.append(
1233
1234
  WebhookRuleResponse(
@@ -1,6 +1,6 @@
1
1
  """Project deployment plugin endpoints.
2
2
 
3
- Pass-through endpoints that resolve the project's ``tab='deployment'``
3
+ Pass-through endpoints that resolve the project's ``plugin_type='deployment'``
4
4
  plugin and call its handler methods. Covers ref / commit discovery,
5
5
  comparison, ``deploy`` / ``redeploy`` workflow dispatch (Phase 1), and
6
6
  the ``promote`` flow with AI-drafted release notes plus tag + Release
@@ -12,7 +12,7 @@ from imbi_api.plugins.assignments import (
12
12
  PluginAssignmentRow,
13
13
  build_assignment_response,
14
14
  validate_identity_plugin_ids,
15
- validate_one_default_per_tab,
15
+ validate_one_default_per_plugin_type,
16
16
  )
17
17
 
18
18
  project_plugins_router = fastapi.APIRouter(tags=['Project Plugins'])
@@ -104,7 +104,7 @@ async def replace_project_plugins(
104
104
  rows: list[PluginAssignmentRow] = [
105
105
  PluginAssignmentRow(
106
106
  plugin_id=a.plugin_id,
107
- tab=a.tab,
107
+ plugin_type=a.plugin_type,
108
108
  default=a.default,
109
109
  options=a.options,
110
110
  identity_plugin_id=a.identity_plugin_id,
@@ -115,7 +115,7 @@ async def replace_project_plugins(
115
115
 
116
116
  if rows:
117
117
  try:
118
- validate_one_default_per_tab(rows)
118
+ validate_one_default_per_plugin_type(rows)
119
119
  except ValueError as exc:
120
120
  raise fastapi.HTTPException(
121
121
  status_code=400,
@@ -12,7 +12,7 @@ from imbi_api.plugins.assignments import (
12
12
  PluginAssignmentRow,
13
13
  build_assignment_response,
14
14
  validate_identity_plugin_ids,
15
- validate_one_default_per_tab,
15
+ validate_one_default_per_plugin_type,
16
16
  )
17
17
 
18
18
  project_type_plugins_router = fastapi.APIRouter(
@@ -74,7 +74,7 @@ async def replace_project_type_plugins(
74
74
  rows: list[PluginAssignmentRow] = [
75
75
  PluginAssignmentRow(
76
76
  plugin_id=a.plugin_id,
77
- tab=a.tab,
77
+ plugin_type=a.plugin_type,
78
78
  default=a.default,
79
79
  options=a.options,
80
80
  identity_plugin_id=a.identity_plugin_id,
@@ -83,7 +83,7 @@ async def replace_project_type_plugins(
83
83
  for a in assignments
84
84
  ]
85
85
  try:
86
- validate_one_default_per_tab(rows)
86
+ validate_one_default_per_plugin_type(rows)
87
87
  except ValueError as exc:
88
88
  raise fastapi.HTTPException(
89
89
  status_code=400,
@@ -7,6 +7,7 @@ import fastapi
7
7
  import nanoid
8
8
  import pydantic
9
9
  from imbi_common import graph
10
+ from imbi_common.plugins import PluginType
10
11
  from imbi_common.plugins.errors import (
11
12
  PluginNotFoundError,
12
13
  )
@@ -654,7 +655,7 @@ async def patch_service_plugin_configuration(
654
655
 
655
656
  class _AssignmentInput(pydantic.BaseModel):
656
657
  project_type_slug: str
657
- tab: typing.Literal['configuration', 'logs', 'deployment', 'lifecycle']
658
+ plugin_type: PluginType
658
659
  default: bool = False
659
660
  options: dict[str, typing.Any] = {}
660
661
  identity_plugin_id: str | None = None
@@ -663,7 +664,7 @@ class _AssignmentInput(pydantic.BaseModel):
663
664
  class _AssignmentRow(pydantic.BaseModel):
664
665
  project_type_slug: str
665
666
  project_type_name: str
666
- tab: typing.Literal['configuration', 'logs', 'deployment', 'lifecycle']
667
+ plugin_type: PluginType
667
668
  default: bool
668
669
  options: dict[str, typing.Any]
669
670
  identity_plugin_id: str | None = None
@@ -671,7 +672,7 @@ class _AssignmentRow(pydantic.BaseModel):
671
672
 
672
673
  _ASSIGNMENT_ROW_FIELDS: tuple[tuple[str, str], ...] = (
673
674
  ('project_type_slug', 'pt'),
674
- ('tab', 'tab'),
675
+ ('plugin_type', 'ptype'),
675
676
  ('default', 'default'),
676
677
  ('options', 'options'),
677
678
  ('identity_plugin_id', 'idp'),
@@ -694,7 +695,7 @@ def _assignment_rows_template(
694
695
  for i, a in enumerate(body):
695
696
  values: dict[str, typing.Any] = {
696
697
  'pt': a.project_type_slug,
697
- 'tab': a.tab,
698
+ 'ptype': a.plugin_type,
698
699
  'default': a.default,
699
700
  'options': json.dumps(a.options),
700
701
  'idp': a.identity_plugin_id or None,
@@ -735,7 +736,11 @@ async def _list_assignments(
735
736
  _AssignmentRow(
736
737
  project_type_slug=pt.get('slug', ''),
737
738
  project_type_name=pt.get('name', ''),
738
- tab=edge.get('tab', 'configuration'),
739
+ # Transitional: prefer the new ``plugin_type`` edge
740
+ # property, fall back to legacy ``tab`` until migrated.
741
+ plugin_type=edge.get(
742
+ 'plugin_type', edge.get('tab', 'configuration')
743
+ ),
739
744
  default=bool(edge.get('default', False)),
740
745
  options=_parse_options(edge.get('options')),
741
746
  identity_plugin_id=coerce_identity_plugin_id(
@@ -795,26 +800,27 @@ async def replace_plugin_assignments(
795
800
  detail=f'Plugin {plugin_slug!r} is not loaded',
796
801
  ) from exc
797
802
 
798
- allowed_tab = entry.manifest.plugin_type
799
- bad_tab = [a for a in body if a.tab != allowed_tab]
800
- if bad_tab:
803
+ allowed_plugin_type = entry.manifest.plugin_type
804
+ mismatched = [a for a in body if a.plugin_type != allowed_plugin_type]
805
+ if mismatched:
801
806
  raise fastapi.HTTPException(
802
807
  status_code=400,
803
808
  detail=(
804
- f'Plugin type {allowed_tab!r} can only be assigned to'
805
- f' the {allowed_tab!r} tab'
809
+ f'Assignments for this plugin must use plugin_type'
810
+ f' {allowed_plugin_type!r}'
806
811
  ),
807
812
  )
808
813
 
809
814
  seen: set[tuple[str, str]] = set()
810
815
  for a in body:
811
- key = (a.project_type_slug, a.tab)
816
+ key = (a.project_type_slug, a.plugin_type)
812
817
  if key in seen:
813
818
  raise fastapi.HTTPException(
814
819
  status_code=400,
815
820
  detail=(
816
821
  f'Duplicate assignment for project type'
817
- f' {a.project_type_slug!r} on tab {a.tab!r}'
822
+ f' {a.project_type_slug!r} with plugin_type'
823
+ f' {a.plugin_type!r}'
818
824
  ),
819
825
  )
820
826
  seen.add(key)
@@ -860,9 +866,10 @@ async def replace_plugin_assignments(
860
866
  # plugin; without it the ``OPTIONAL MATCH`` would emit one row per
861
867
  # pre-existing edge and the ``UNWIND`` would multiply the CREATEs.
862
868
  #
863
- # The trailing demotion runs after the CREATEs: for every (pt, tab)
864
- # where this plugin is now default, any *other* plugin that was
865
- # default on the same tab is demoted. Plain ``MATCH``/``SET`` only --
869
+ # The trailing demotion runs after the CREATEs: for every
870
+ # (pt, plugin_type) where this plugin is now default, any *other*
871
+ # plugin that was default for the same plugin_type is demoted. Plain
872
+ # ``MATCH``/``SET`` only --
866
873
  # a zero-row match (no default edges, or no competing siblings) is a
867
874
  # harmless no-op and the already-applied CREATEs still commit.
868
875
  if not body:
@@ -889,14 +896,16 @@ async def replace_plugin_assignments(
889
896
  f' UNWIND {rows_tpl} AS row'
890
897
  ' MATCH (:Organization {{slug: {org_slug}}})<-[:BELONGS_TO]-'
891
898
  '(cpt:ProjectType {{slug: row.project_type_slug}})'
892
- ' CREATE (cpt)-[:USES_PLUGIN {{tab: row.tab,'
899
+ ' CREATE (cpt)-[:USES_PLUGIN {{plugin_type: row.plugin_type,'
893
900
  ' default: row.default, options: row.options,'
894
901
  ' identity_plugin_id: row.identity_plugin_id}}]->(p)'
895
902
  ' WITH p, count(cpt) AS _new'
896
903
  ' MATCH (mpt:ProjectType)-[mine:USES_PLUGIN]->(p)'
897
904
  ' WHERE mine.default = true'
898
905
  ' MATCH (mpt)-[sibling:USES_PLUGIN]->(other:Plugin)'
899
- ' WHERE other.id <> {plugin_id} AND sibling.tab = mine.tab'
906
+ ' WHERE other.id <> {plugin_id}'
907
+ ' AND coalesce(sibling.plugin_type, sibling.tab) ='
908
+ ' coalesce(mine.plugin_type, mine.tab)'
900
909
  ' AND sibling.default = true'
901
910
  ' SET sibling.default = false'
902
911
  )
@@ -469,7 +469,7 @@ async def _projects_using_tps_for_deployment(
469
469
  """Return project ids whose deployment plugin is owned by this TPS.
470
470
 
471
471
  Walks both project-level and project-type-level ``USES_PLUGIN``
472
- edges with ``tab='deployment'`` whose target ``Plugin`` is owned
472
+ edges with ``plugin_type='deployment'`` whose target ``Plugin`` is owned
473
473
  (via ``HAS_PLUGIN``) by the named ThirdPartyService. Returns a
474
474
  de-duplicated, deterministic list so the resync fan-out is stable
475
475
  across calls.
@@ -492,11 +492,11 @@ async def _projects_using_tps_for_deployment(
492
492
  MATCH (tps)-[:HAS_PLUGIN]->(plugin:Plugin)
493
493
  OPTIONAL MATCH (p:Project)-[upe:USES_PLUGIN]->(plugin),
494
494
  (p)-[:OWNED_BY]->(:Team)-[:BELONGS_TO]->(o)
495
- WHERE upe.tab = 'deployment'
495
+ WHERE coalesce(upe.plugin_type, upe.tab) = 'deployment'
496
496
  OPTIONAL MATCH (p2:Project)-[:TYPE]
497
497
  ->(:ProjectType)-[upte:USES_PLUGIN]->(plugin),
498
498
  (p2)-[:OWNED_BY]->(:Team)-[:BELONGS_TO]->(o)
499
- WHERE upte.tab = 'deployment'
499
+ WHERE coalesce(upte.plugin_type, upte.tab) = 'deployment'
500
500
  WITH collect(DISTINCT p.id) AS direct_ids,
501
501
  collect(DISTINCT p2.id) AS typed_ids
502
502
  RETURN direct_ids + typed_ids AS project_ids
@@ -25,7 +25,7 @@ from imbi_api.plugins.assignments import PluginAssignmentRow
25
25
 
26
26
  _ROW_KEYS: tuple[str, ...] = (
27
27
  'plugin_id',
28
- 'tab',
28
+ 'plugin_type',
29
29
  'default',
30
30
  'options',
31
31
  'identity_plugin_id',
@@ -87,7 +87,7 @@ async def replace_assignments(
87
87
  edges by guessing the parent key.
88
88
 
89
89
  Callers must validate plugin ids, identity_plugin_ids, and the
90
- one-default-per-tab invariant up front; passing an empty ``rows``
90
+ one-default-per-plugin-type invariant up front; passing an empty ``rows``
91
91
  list clears all assignments.
92
92
  """
93
93
  parent_head = (
@@ -117,7 +117,7 @@ async def replace_assignments(
117
117
  ' WITH parent, count(old) AS _del'
118
118
  f' UNWIND {rows_tpl} AS row'
119
119
  ' MATCH (p:Plugin {{id: row.plugin_id}})'
120
- ' CREATE (parent)-[:USES_PLUGIN {{tab: row.tab,'
120
+ ' CREATE (parent)-[:USES_PLUGIN {{plugin_type: row.plugin_type,'
121
121
  ' default: row.default,'
122
122
  ' options: row.options,'
123
123
  ' identity_plugin_id: row.identity_plugin_id,'
@@ -4,6 +4,7 @@ import typing
4
4
 
5
5
  import fastapi
6
6
  from imbi_common import graph
7
+ from imbi_common.plugins import PluginType
7
8
  from imbi_common.plugins.errors import PluginNotFoundError
8
9
  from imbi_common.plugins.registry import get_plugin
9
10
 
@@ -13,33 +14,37 @@ from imbi_api.plugins import parse_options
13
14
 
14
15
  class PluginAssignmentRow(typing.TypedDict):
15
16
  plugin_id: str
16
- tab: typing.Literal['configuration', 'logs', 'deployment', 'lifecycle']
17
+ plugin_type: PluginType
17
18
  default: bool
18
19
  options: dict[str, typing.Any]
19
20
  identity_plugin_id: typing.NotRequired[str | None]
20
21
  env_payloads: typing.NotRequired[dict[str, dict[str, typing.Any]]]
21
22
 
22
23
 
23
- def validate_one_default_per_tab(
24
+ def validate_one_default_per_plugin_type(
24
25
  assignments: list[PluginAssignmentRow],
25
26
  ) -> None:
26
- """Ensure exactly one default per tab in the assignment list.
27
+ """Ensure exactly one default per plugin type in the assignment list.
27
28
 
28
29
  Raises:
29
- ValueError: If a tab has 0 or >1 defaults.
30
+ ValueError: If a plugin type has 0 or >1 defaults.
30
31
  """
31
- by_tab: dict[str, list[bool]] = {}
32
+ by_plugin_type: dict[str, list[bool]] = {}
32
33
  for row in assignments:
33
- by_tab.setdefault(row['tab'], []).append(row['default'])
34
+ by_plugin_type.setdefault(row['plugin_type'], []).append(
35
+ row['default']
36
+ )
34
37
 
35
- for tab, defaults in by_tab.items():
38
+ for plugin_type, defaults in by_plugin_type.items():
36
39
  count = sum(1 for d in defaults if d)
37
40
  if count == 0:
38
- raise ValueError(f'Tab {tab!r} has no default plugin assignment')
41
+ raise ValueError(
42
+ f'Plugin type {plugin_type!r} has no default plugin assignment'
43
+ )
39
44
  if count > 1:
40
45
  raise ValueError(
41
- f'Tab {tab!r} has {count} default plugin assignments;'
42
- f' exactly one is required'
46
+ f'Plugin type {plugin_type!r} has {count} default plugin'
47
+ f' assignments; exactly one is required'
43
48
  )
44
49
 
45
50
 
@@ -121,7 +126,10 @@ def build_assignment_response(
121
126
  plugin_id=plugin['id'],
122
127
  plugin_slug=plugin['plugin_slug'],
123
128
  label=plugin['label'],
124
- tab=edge.get('tab', 'configuration'),
129
+ # Transitional: read the new ``plugin_type`` edge property,
130
+ # falling back to the legacy ``tab`` name until the rename
131
+ # migration has run. Drop the ``tab`` fallback afterward.
132
+ plugin_type=edge.get('plugin_type', edge.get('tab', 'configuration')),
125
133
  default=bool(edge.get('default', False)),
126
134
  options=parse_options(edge.get('options')),
127
135
  source=source,
@@ -2,7 +2,7 @@
2
2
 
3
3
  When a project state changes -- create, update, archive, unarchive,
4
4
  delete, or relocate -- the API dispatches the event to every plugin
5
- assigned to ``tab='lifecycle'`` (at the project or project-type level).
5
+ assigned to ``plugin_type='lifecycle'`` (project or project-type level).
6
6
  Plugins receive a :class:`PluginContext` with hydrated identity and
7
7
  return a :class:`LifecycleResult` describing their per-plugin outcome.
8
8
 
@@ -1,4 +1,4 @@
1
- """Plugin resolution — find the correct plugin for a project+tab."""
1
+ """Plugin resolution — find the plugin for a project + plugin_type."""
2
2
 
3
3
  import logging
4
4
  import typing
@@ -71,10 +71,10 @@ def _merge_env_payloads(
71
71
  async def resolve_plugin(
72
72
  db: graph.Graph,
73
73
  project_id: str,
74
- tab: str,
74
+ plugin_type: str,
75
75
  source: str | None,
76
76
  ) -> ResolvedPlugin:
77
- """Find the plugin assigned to a project for a given tab.
77
+ """Find the plugin assigned to a project for a given plugin type.
78
78
 
79
79
  Merges project-type defaults with project-level overrides.
80
80
  If multiple plugins are assigned, ``source`` selects which one
@@ -96,10 +96,10 @@ async def resolve_plugin(
96
96
  query: typing.LiteralString = """
97
97
  MATCH (proj:Project {{id: {project_id}}})
98
98
  OPTIONAL MATCH (proj)-[pe:USES_PLUGIN]->(p:Plugin)
99
- WHERE pe.tab = {tab}
99
+ WHERE coalesce(pe.plugin_type, pe.tab) = {plugin_type}
100
100
  OPTIONAL MATCH (proj)-[:TYPE]->(pt:ProjectType)
101
101
  -[pte:USES_PLUGIN]->(p2:Plugin)
102
- WHERE pte.tab = {tab}
102
+ WHERE coalesce(pte.plugin_type, pte.tab) = {plugin_type}
103
103
  WITH
104
104
  collect(DISTINCT {{id: p.id, slug: p.plugin_slug,
105
105
  edge_options: pe.options,
@@ -123,7 +123,7 @@ async def resolve_plugin(
123
123
  """
124
124
  records = await db.execute(
125
125
  query,
126
- {'project_id': project_id, 'tab': tab},
126
+ {'project_id': project_id, 'plugin_type': plugin_type},
127
127
  ['proj_plugins', 'pt_plugins'],
128
128
  )
129
129
  if not records:
@@ -176,7 +176,10 @@ async def resolve_plugin(
176
176
  if not candidates:
177
177
  raise fastapi.HTTPException(
178
178
  status_code=404,
179
- detail=f'No plugin assigned to tab {tab!r} for this project',
179
+ detail=(
180
+ f'No plugin assigned for plugin_type {plugin_type!r}'
181
+ f' for this project'
182
+ ),
180
183
  )
181
184
 
182
185
  if source:
@@ -266,9 +269,9 @@ async def resolve_plugin(
266
269
  async def resolve_all_plugins(
267
270
  db: graph.Graph,
268
271
  project_id: str,
269
- tab: str,
272
+ plugin_type: str,
270
273
  ) -> list[ResolvedPlugin]:
271
- """Return every plugin assigned to ``project_id`` for ``tab``.
274
+ """Return every plugin assigned to ``project_id`` for ``plugin_type``.
272
275
 
273
276
  Sibling to :func:`resolve_plugin` for fan-out call sites (e.g.
274
277
  lifecycle hooks) that must invoke *every* assigned plugin rather
@@ -287,11 +290,11 @@ async def resolve_all_plugins(
287
290
  query: typing.LiteralString = """
288
291
  MATCH (proj:Project {{id: {project_id}}})
289
292
  OPTIONAL MATCH (proj)-[pe:USES_PLUGIN]->(p:Plugin)
290
- WHERE pe.tab = {tab}
293
+ WHERE coalesce(pe.plugin_type, pe.tab) = {plugin_type}
291
294
  OPTIONAL MATCH (tps1:ThirdPartyService)-[:HAS_PLUGIN]->(p)
292
295
  OPTIONAL MATCH (proj)-[:TYPE]->(pt:ProjectType)
293
296
  -[pte:USES_PLUGIN]->(p2:Plugin)
294
- WHERE pte.tab = {tab}
297
+ WHERE coalesce(pte.plugin_type, pte.tab) = {plugin_type}
295
298
  OPTIONAL MATCH (tps2:ThirdPartyService)-[:HAS_PLUGIN]->(p2)
296
299
  WITH
297
300
  collect(DISTINCT {{id: p.id, slug: p.plugin_slug,
@@ -318,7 +321,7 @@ async def resolve_all_plugins(
318
321
  """
319
322
  records = await db.execute(
320
323
  query,
321
- {'project_id': project_id, 'tab': tab},
324
+ {'project_id': project_id, 'plugin_type': plugin_type},
322
325
  ['proj_plugins', 'pt_plugins'],
323
326
  )
324
327
  if not records:
@@ -454,10 +457,11 @@ async def resolve_analysis_plugins(
454
457
 
455
458
  The union covers three discovery paths:
456
459
 
457
- 1. ``(:Project)-[:USES_PLUGIN {tab:'analysis'}]->(:Plugin)`` — project
458
- override.
459
- 2. ``(:Project)-[:TYPE]->(:ProjectType)-[:USES_PLUGIN {tab:'analysis'}]
460
- ->(:Plugin)`` — project-type default. Project override wins per
460
+ 1. ``(:Project)-[:USES_PLUGIN {plugin_type:'analysis'}]->(:Plugin)`` —
461
+ project override.
462
+ 2. ``(:Project)-[:TYPE]->(:ProjectType)
463
+ -[:USES_PLUGIN {plugin_type:'analysis'}]->(:Plugin)`` — project-type
464
+ default. Project override wins per
461
465
  plugin id, mirroring :func:`resolve_all_plugins`.
462
466
  3. ``(:Project)-[:EXISTS_IN]->(:ThirdPartyService)-[:HAS_PLUGIN]
463
467
  ->(:Plugin)`` filtered to ``plugin_type='analysis'``. The
@@ -476,10 +480,10 @@ async def resolve_analysis_plugins(
476
480
  query: typing.LiteralString = """
477
481
  MATCH (proj:Project {{id: {project_id}}})
478
482
  OPTIONAL MATCH (proj)-[pe:USES_PLUGIN]->(p:Plugin)
479
- WHERE pe.tab = 'analysis'
483
+ WHERE coalesce(pe.plugin_type, pe.tab) = 'analysis'
480
484
  OPTIONAL MATCH (proj)-[:TYPE]->(pt:ProjectType)
481
485
  -[pte:USES_PLUGIN]->(p2:Plugin)
482
- WHERE pte.tab = 'analysis'
486
+ WHERE coalesce(pte.plugin_type, pte.tab) = 'analysis'
483
487
  OPTIONAL MATCH (proj)-[:EXISTS_IN]->(tps:ThirdPartyService)
484
488
  -[:HAS_PLUGIN]->(p3:Plugin)
485
489
  WHERE p3.plugin_type = 'analysis'
@@ -36,8 +36,8 @@ class ProjectPluginsEndpointTestCase(support.SharedAppTestCase):
36
36
  mock_get_current_user
37
37
  )
38
38
  self.mock_db = mock.AsyncMock(spec=graph.Graph)
39
- self.test_app.dependency_overrides[graph._inject_graph] = (
40
- lambda: self.mock_db
39
+ self.test_app.dependency_overrides[graph._inject_graph] = lambda: (
40
+ self.mock_db
41
41
  )
42
42
 
43
43
  def test_get_project_plugins_not_found(self) -> None:
@@ -70,7 +70,7 @@ class ProjectPluginsEndpointTestCase(support.SharedAppTestCase):
70
70
  {
71
71
  'plugin': plugin_a,
72
72
  'edge': {
73
- 'tab': 'configuration',
73
+ 'plugin_type': 'configuration',
74
74
  'default': True,
75
75
  'options': '{}',
76
76
  },
@@ -83,7 +83,7 @@ class ProjectPluginsEndpointTestCase(support.SharedAppTestCase):
83
83
  {
84
84
  'plugin': plugin_b,
85
85
  'edge': {
86
- 'tab': 'logs',
86
+ 'plugin_type': 'logs',
87
87
  'default': True,
88
88
  'options': '{}',
89
89
  },
@@ -110,13 +110,13 @@ class ProjectPluginsEndpointTestCase(support.SharedAppTestCase):
110
110
  json=[
111
111
  {
112
112
  'plugin_id': 'p1',
113
- 'tab': 'configuration',
113
+ 'plugin_type': 'configuration',
114
114
  'default': True,
115
115
  'options': {},
116
116
  },
117
117
  {
118
118
  'plugin_id': 'p2',
119
- 'tab': 'configuration',
119
+ 'plugin_type': 'configuration',
120
120
  'default': True,
121
121
  'options': {},
122
122
  },
@@ -164,7 +164,7 @@ class ProjectPluginsEndpointTestCase(support.SharedAppTestCase):
164
164
  {
165
165
  'plugin': plugin_raw,
166
166
  'edge': {
167
- 'tab': 'configuration',
167
+ 'plugin_type': 'configuration',
168
168
  'default': True,
169
169
  'options': '{}',
170
170
  },
@@ -181,7 +181,7 @@ class ProjectPluginsEndpointTestCase(support.SharedAppTestCase):
181
181
  json=[
182
182
  {
183
183
  'plugin_id': 'p1',
184
- 'tab': 'configuration',
184
+ 'plugin_type': 'configuration',
185
185
  'default': True,
186
186
  'options': {},
187
187
  }
@@ -36,8 +36,8 @@ class ProjectTypePluginsEndpointTestCase(support.SharedAppTestCase):
36
36
  mock_get_current_user
37
37
  )
38
38
  self.mock_db = mock.AsyncMock(spec=graph.Graph)
39
- self.test_app.dependency_overrides[graph._inject_graph] = (
40
- lambda: self.mock_db
39
+ self.test_app.dependency_overrides[graph._inject_graph] = lambda: (
40
+ self.mock_db
41
41
  )
42
42
 
43
43
  def test_list_empty(self) -> None:
@@ -60,7 +60,7 @@ class ProjectTypePluginsEndpointTestCase(support.SharedAppTestCase):
60
60
  'api_version': 1,
61
61
  },
62
62
  'edge': {
63
- 'tab': 'configuration',
63
+ 'plugin_type': 'configuration',
64
64
  'default': True,
65
65
  'options': '{}',
66
66
  },
@@ -82,7 +82,7 @@ class ProjectTypePluginsEndpointTestCase(support.SharedAppTestCase):
82
82
  json=[
83
83
  {
84
84
  'plugin_id': 'p1',
85
- 'tab': 'configuration',
85
+ 'plugin_type': 'configuration',
86
86
  'default': False,
87
87
  'options': {},
88
88
  }
@@ -101,7 +101,7 @@ class ProjectTypePluginsEndpointTestCase(support.SharedAppTestCase):
101
101
  'api_version': 1,
102
102
  },
103
103
  'edge': {
104
- 'tab': 'configuration',
104
+ 'plugin_type': 'configuration',
105
105
  'default': True,
106
106
  'options': '{}',
107
107
  },
@@ -117,7 +117,7 @@ class ProjectTypePluginsEndpointTestCase(support.SharedAppTestCase):
117
117
  json=[
118
118
  {
119
119
  'plugin_id': 'p1',
120
- 'tab': 'configuration',
120
+ 'plugin_type': 'configuration',
121
121
  'default': True,
122
122
  'options': {},
123
123
  }
@@ -141,7 +141,7 @@ class ProjectTypePluginsEndpointTestCase(support.SharedAppTestCase):
141
141
  ),
142
142
  'edge': json.dumps(
143
143
  {
144
- 'tab': 'configuration',
144
+ 'plugin_type': 'configuration',
145
145
  'default': True,
146
146
  'options': '{}',
147
147
  }