imbi-api 2.41.0__tar.gz → 2.42.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 (423) hide show
  1. {imbi_api-2.41.0 → imbi_api-2.42.0}/PKG-INFO +3 -3
  2. {imbi_api-2.41.0 → imbi_api-2.42.0}/pyproject.toml +3 -3
  3. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/harness.py +2 -2
  4. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_deployments.py +121 -27
  5. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/projects.py +95 -23
  6. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_deployments.py +403 -11
  7. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_projects.py +161 -0
  8. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_agent_platform.py +15 -0
  9. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_projects_drift_ranges.py +56 -1
  10. {imbi_api-2.41.0 → imbi_api-2.42.0}/.gitignore +0 -0
  11. {imbi_api-2.41.0 → imbi_api-2.42.0}/README.md +0 -0
  12. {imbi_api-2.41.0 → imbi_api-2.42.0}/moon.yml +0 -0
  13. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/README.md +0 -0
  14. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/__init__.py +0 -0
  15. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/__init__.py +0 -0
  16. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/log.py +0 -0
  17. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/schemata.toml +0 -0
  18. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/store.py +0 -0
  19. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/sweeper.py +0 -0
  20. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tools.py +0 -0
  21. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/app.py +0 -0
  22. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/__init__.py +0 -0
  23. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/agent_api.py +0 -0
  24. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/agent_api_routes.py +0 -0
  25. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/authorization_codes.py +0 -0
  26. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/autonomous.py +0 -0
  27. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/internal_services.py +0 -0
  28. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/local_auth.py +0 -0
  29. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/login_providers.py +0 -0
  30. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/membership.py +0 -0
  31. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/models.py +0 -0
  32. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/oauth.py +0 -0
  33. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/oauth_clients.py +0 -0
  34. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/organizations.py +0 -0
  35. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/password.py +0 -0
  36. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/permissions.py +0 -0
  37. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/principals.py +0 -0
  38. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/seed.py +0 -0
  39. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/sessions.py +0 -0
  40. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/tokens.py +0 -0
  41. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/totp.py +0 -0
  42. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/backfill_embeddings.py +0 -0
  43. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/blueprint_attributes.py +0 -0
  44. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/blueprint_compliance.py +0 -0
  45. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_pushed_at_repair.py +0 -0
  46. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_sync/__init__.py +0 -0
  47. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_sync/queue.py +0 -0
  48. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_sync/service.py +0 -0
  49. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/component_facts.py +0 -0
  50. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_status_repair.py +0 -0
  51. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sweeper.py +0 -0
  52. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
  53. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sync/queue.py +0 -0
  54. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sync/service.py +0 -0
  55. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/documents/__init__.py +0 -0
  56. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/documents/read_sweeper.py +0 -0
  57. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/domain/__init__.py +0 -0
  58. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/domain/models.py +0 -0
  59. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/domain/scoring.py +0 -0
  60. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/drift.py +0 -0
  61. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/__init__.py +0 -0
  62. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/client.py +0 -0
  63. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/dependencies.py +0 -0
  64. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/models.py +0 -0
  65. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/base.html +0 -0
  66. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/base.txt +0 -0
  67. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/password_reset.html +0 -0
  68. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
  69. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/welcome.html +0 -0
  70. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/welcome.txt +0 -0
  71. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates.py +0 -0
  72. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/__init__.py +0 -0
  73. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_credentials.py +0 -0
  74. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_events.py +0 -0
  75. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_history.py +0 -0
  76. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_reads.py +0 -0
  77. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
  78. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_helpers.py +0 -0
  79. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
  80. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_pagination.py +0 -0
  81. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
  82. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_search_index.py +0 -0
  83. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/admin.py +0 -0
  84. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
  85. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_platform.py +0 -0
  86. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_task_harness.py +0 -0
  87. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_tasks.py +0 -0
  88. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_usage.py +0 -0
  89. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agents.py +0 -0
  90. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/ai_models.py +0 -0
  91. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
  92. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/api_keys.py +0 -0
  93. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/auth.py +0 -0
  94. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
  95. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/blueprints.py +0 -0
  96. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
  97. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/comments.py +0 -0
  98. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/components.py +0 -0
  99. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/dashboard.py +0 -0
  100. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
  101. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_likes.py +0 -0
  102. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_presence.py +0 -0
  103. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_templates.py +0 -0
  104. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_versions.py +0 -0
  105. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/documents.py +0 -0
  106. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/environments.py +0 -0
  107. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/events.py +0 -0
  108. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/graph_query.py +0 -0
  109. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
  110. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/integrations.py +0 -0
  111. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
  112. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/local_auth.py +0 -0
  113. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/maintenance.py +0 -0
  114. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
  115. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/mfa.py +0 -0
  116. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
  117. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/operations_log.py +0 -0
  118. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/organizations.py +0 -0
  119. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
  120. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
  121. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/plugins.py +0 -0
  122. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_analysis.py +0 -0
  123. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
  124. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_configuration.py +0 -0
  125. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
  126. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
  127. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
  128. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_logs.py +0 -0
  129. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
  130. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
  131. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_types.py +0 -0
  132. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/prompts.py +0 -0
  133. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/pull_requests.py +0 -0
  134. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/releases.py +0 -0
  135. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/roles.py +0 -0
  136. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
  137. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/scoring.py +0 -0
  138. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
  139. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/search.py +0 -0
  140. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/secret_broker.py +0 -0
  141. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
  142. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/status.py +0 -0
  143. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/tags.py +0 -0
  144. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/teams.py +0 -0
  145. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/uploads.py +0 -0
  146. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/user_activity.py +0 -0
  147. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/users.py +0 -0
  148. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/webhooks.py +0 -0
  149. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/workflow_runs.py +0 -0
  150. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/workflows.py +0 -0
  151. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/entrypoint.py +0 -0
  152. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/graph_ids.py +0 -0
  153. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/graph_sql.py +0 -0
  154. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/__init__.py +0 -0
  155. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/auth.py +0 -0
  156. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/egress.py +0 -0
  157. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/marketplaces.py +0 -0
  158. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/metrics.py +0 -0
  159. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/platform.py +0 -0
  160. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/schemata.toml +0 -0
  161. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/store.py +0 -0
  162. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/sweeper.py +0 -0
  163. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/tokens.py +0 -0
  164. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/__init__.py +0 -0
  165. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/attribution.py +0 -0
  166. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/endpoints.py +0 -0
  167. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/errors.py +0 -0
  168. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/flows.py +0 -0
  169. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/host_integration.py +0 -0
  170. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/models.py +0 -0
  171. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/repository.py +0 -0
  172. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/resolution.py +0 -0
  173. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/state.py +0 -0
  174. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/sweeper.py +0 -0
  175. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/iggy_metrics.py +0 -0
  176. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/lifespans.py +0 -0
  177. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/llm/__init__.py +0 -0
  178. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/llm/dependencies.py +0 -0
  179. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/__init__.py +0 -0
  180. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/log.py +0 -0
  181. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/operations.py +0 -0
  182. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/registry.py +0 -0
  183. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/state.py +0 -0
  184. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/worker.py +0 -0
  185. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/mcp_test.py +0 -0
  186. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/middleware/__init__.py +0 -0
  187. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/middleware/rate_limit.py +0 -0
  188. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/models.py +0 -0
  189. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/openapi.py +0 -0
  190. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/orphan_releases.py +0 -0
  191. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/__init__.py +0 -0
  192. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
  193. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/assignments.py +0 -0
  194. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/credentials.py +0 -0
  195. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/installer.py +0 -0
  196. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/lifecycle.py +0 -0
  197. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/lifecycle_dispatch.py +0 -0
  198. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/reload.py +0 -0
  199. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/resolution.py +0 -0
  200. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/schemas.py +0 -0
  201. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/pr_sync/__init__.py +0 -0
  202. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/pr_sync/queue.py +0 -0
  203. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/pr_sync/service.py +0 -0
  204. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/prompts/__init__.py +0 -0
  205. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/prompts/release_notes_system.md +0 -0
  206. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/prompts/seed.py +0 -0
  207. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/py.typed +0 -0
  208. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/relationships.py +0 -0
  209. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/release_promote/__init__.py +0 -0
  210. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/release_promote/queue.py +0 -0
  211. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/release_promote/service.py +0 -0
  212. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/sbom.py +0 -0
  213. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/scoring/__init__.py +0 -0
  214. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/scoring/queue.py +0 -0
  215. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/__init__.py +0 -0
  216. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/audit.py +0 -0
  217. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/broker.py +0 -0
  218. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/github_app.py +0 -0
  219. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/providers.py +0 -0
  220. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/schemata.toml +0 -0
  221. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/store.py +0 -0
  222. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/settings.py +0 -0
  223. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/__init__.py +0 -0
  224. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/client.py +0 -0
  225. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/dependencies.py +0 -0
  226. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/thumbnails.py +0 -0
  227. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/validation.py +0 -0
  228. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/user_document_orgs.py +0 -0
  229. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/__init__.py +0 -0
  230. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/runs.py +0 -0
  231. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/runtime_schemata.toml +0 -0
  232. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/schemata.toml +0 -0
  233. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/store.py +0 -0
  234. {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/sweeper.py +0 -0
  235. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/__init__.py +0 -0
  236. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/__init__.py +0 -0
  237. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_authentication.py +0 -0
  238. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_authorization.py +0 -0
  239. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_encryption.py +0 -0
  240. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_internal_services.py +0 -0
  241. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_login_providers.py +0 -0
  242. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_membership.py +0 -0
  243. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_oauth.py +0 -0
  244. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_oauth_server.py +0 -0
  245. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_organizations.py +0 -0
  246. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_permissions.py +0 -0
  247. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_seed.py +0 -0
  248. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_sessions.py +0 -0
  249. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_totp.py +0 -0
  250. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/__init__.py +0 -0
  251. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/test_client.py +0 -0
  252. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/test_init.py +0 -0
  253. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/test_privacy.py +0 -0
  254. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/__init__.py +0 -0
  255. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_audit_clickhouse.py +0 -0
  256. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_client.py +0 -0
  257. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_init.py +0 -0
  258. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_integration.py +0 -0
  259. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_models.py +0 -0
  260. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_templates.py +0 -0
  261. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/__init__.py +0 -0
  262. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_admin.py +0 -0
  263. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_admin_plugins.py +0 -0
  264. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_api.py +0 -0
  265. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_platform.py +0 -0
  266. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_actions.py +0 -0
  267. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_harness.py +0 -0
  268. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_log.py +0 -0
  269. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_relations.py +0 -0
  270. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_tasks.py +0 -0
  271. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_usage.py +0 -0
  272. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agents.py +0 -0
  273. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_ai_models.py +0 -0
  274. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_ai_providers.py +0 -0
  275. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_api_keys.py +0 -0
  276. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_auth.py +0 -0
  277. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_auth_providers.py +0 -0
  278. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_blueprints.py +0 -0
  279. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_client_credentials.py +0 -0
  280. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_comments.py +0 -0
  281. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_components.py +0 -0
  282. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_credentials.py +0 -0
  283. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_cross_org_reads.py +0 -0
  284. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_dashboard.py +0 -0
  285. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_analytics.py +0 -0
  286. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_likes.py +0 -0
  287. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_presence.py +0 -0
  288. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_reads.py +0 -0
  289. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_reads_sql.py +0 -0
  290. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_templates.py +0 -0
  291. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_versions.py +0 -0
  292. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_documents.py +0 -0
  293. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_environments.py +0 -0
  294. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_events.py +0 -0
  295. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_github_app_repository.py +0 -0
  296. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_graph_query.py +0 -0
  297. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_harness.py +0 -0
  298. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_harness_egress.py +0 -0
  299. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_harness_secrets.py +0 -0
  300. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_iggy_connectors.py +0 -0
  301. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_init.py +0 -0
  302. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_integrations.py +0 -0
  303. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_json_fields.py +0 -0
  304. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_link_definitions.py +0 -0
  305. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_local_auth.py +0 -0
  306. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_maintenance.py +0 -0
  307. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_mcp_servers.py +0 -0
  308. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_mfa.py +0 -0
  309. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_operations_log.py +0 -0
  310. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_organizations.py +0 -0
  311. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_pagination.py +0 -0
  312. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
  313. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_plugins.py +0 -0
  314. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_analysis.py +0 -0
  315. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_commit_sync.py +0 -0
  316. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_configuration.py +0 -0
  317. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_deployments_autonomous.py +0 -0
  318. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_incidents.py +0 -0
  319. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_integrations.py +0 -0
  320. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_lifecycle.py +0 -0
  321. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_logs.py +0 -0
  322. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_plugins.py +0 -0
  323. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
  324. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_types.py +0 -0
  325. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_projects_helpers.py +0 -0
  326. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_prompt_decisions.py +0 -0
  327. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_prompts.py +0 -0
  328. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_pull_requests.py +0 -0
  329. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_releases.py +0 -0
  330. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_request_urls.py +0 -0
  331. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_roles.py +0 -0
  332. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_sa_api_keys.py +0 -0
  333. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_scoring.py +0 -0
  334. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_scoring_policies.py +0 -0
  335. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_search.py +0 -0
  336. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_search_index.py +0 -0
  337. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_secret_broker.py +0 -0
  338. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_service_accounts.py +0 -0
  339. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_service_writeback.py +0 -0
  340. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_status.py +0 -0
  341. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_tags.py +0 -0
  342. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_teams.py +0 -0
  343. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_uploads.py +0 -0
  344. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_user_activity.py +0 -0
  345. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_users.py +0 -0
  346. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_webhooks.py +0 -0
  347. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflow_run_history.py +0 -0
  348. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflow_runs.py +0 -0
  349. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflow_triggers.py +0 -0
  350. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflows.py +0 -0
  351. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/marketplaces/claude-code.json +0 -0
  352. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
  353. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
  354. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/sbom/tiny.json +0 -0
  355. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/__init__.py +0 -0
  356. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_endpoints.py +0 -0
  357. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_errors.py +0 -0
  358. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_flows.py +0 -0
  359. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_host_integration.py +0 -0
  360. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_repository.py +0 -0
  361. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_resolution.py +0 -0
  362. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_state.py +0 -0
  363. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_sweeper.py +0 -0
  364. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/middleware/test_rate_limit.py +0 -0
  365. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/secret_support.py +0 -0
  366. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/__init__.py +0 -0
  367. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_client.py +0 -0
  368. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_init.py +0 -0
  369. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_thumbnails.py +0 -0
  370. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_validation.py +0 -0
  371. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/support.py +0 -0
  372. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_agent_task_events_e2e.py +0 -0
  373. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_agent_tools.py +0 -0
  374. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_app.py +0 -0
  375. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_assignment_writer.py +0 -0
  376. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_auth_settings.py +0 -0
  377. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_backfill_embeddings.py +0 -0
  378. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_blueprint_attributes.py +0 -0
  379. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_blueprint_compliance.py +0 -0
  380. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_blueprints.py +0 -0
  381. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_commit_pushed_at_repair.py +0 -0
  382. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_commit_sync_queue.py +0 -0
  383. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_commit_sync_service.py +0 -0
  384. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_component_facts.py +0 -0
  385. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_status_repair.py +0 -0
  386. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_sweeper.py +0 -0
  387. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_sync_queue.py +0 -0
  388. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_sync_service.py +0 -0
  389. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_drift.py +0 -0
  390. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_entrypoint.py +0 -0
  391. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_graph_ids.py +0 -0
  392. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_graph_sql.py +0 -0
  393. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_identity_attribution.py +0 -0
  394. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_iggy_metrics.py +0 -0
  395. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_init.py +0 -0
  396. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_installer.py +0 -0
  397. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_lifecycle_dispatch.py +0 -0
  398. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_lifespans.py +0 -0
  399. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_log.py +0 -0
  400. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_operations.py +0 -0
  401. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_state.py +0 -0
  402. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_worker.py +0 -0
  403. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_models.py +0 -0
  404. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_openapi.py +0 -0
  405. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_orphan_releases.py +0 -0
  406. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugin_schemas.py +0 -0
  407. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugins.py +0 -0
  408. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugins_assignments.py +0 -0
  409. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugins_credentials.py +0 -0
  410. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_pr_sync_queue.py +0 -0
  411. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_pr_sync_service.py +0 -0
  412. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_prompt_seed.py +0 -0
  413. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_pull_requests_pending_deploy.py +0 -0
  414. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_release_promote_service.py +0 -0
  415. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_sbom.py +0 -0
  416. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_scoring_queue.py +0 -0
  417. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_scoring_triggers.py +0 -0
  418. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_broker.py +0 -0
  419. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_github_app.py +0 -0
  420. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_github_app_live.py +0 -0
  421. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_providers.py +0 -0
  422. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_settings.py +0 -0
  423. {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_workflow_reconcile.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: imbi-api
3
- Version: 2.41.0
3
+ Version: 2.42.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,8 +19,8 @@ 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,mcp,sentry,server]==2.41.0
23
- Requires-Dist: imbi-plugin-github==2.41.0
22
+ Requires-Dist: imbi-common[databases,llm,mcp,sentry,server]==2.42.0
23
+ Requires-Dist: imbi-plugin-github==2.42.0
24
24
  Requires-Dist: jinja2
25
25
  Requires-Dist: jsonpatch>=1.33
26
26
  Requires-Dist: limits<6,>=5
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "imbi-api"
3
- version = "2.41.0"
3
+ version = "2.42.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,8 +28,8 @@ dependencies = [
28
28
  "fastapi",
29
29
  "filetype",
30
30
  "httpx>=0.28.1,<1",
31
- "imbi-common[databases,llm,mcp,sentry,server]==2.41.0",
32
- "imbi-plugin-github==2.41.0",
31
+ "imbi-common[databases,llm,mcp,sentry,server]==2.42.0",
32
+ "imbi-plugin-github==2.42.0",
33
33
  "jinja2",
34
34
  "jsonpatch>=1.33",
35
35
  "limits>=5,<6",
@@ -688,7 +688,7 @@ async def _rendered_prompt(
688
688
  """Render the pinned prompt version; return it or the error.
689
689
 
690
690
  The template gets the variables that it declares among
691
- ``organization``, ``agent``, ``task``, and ``project`` (slugs and
691
+ ``organization``, ``agent``, ``task``, and ``project_slug`` (slugs and
692
692
  the short id).
693
693
  """
694
694
  if bundle.prompt_ref is None or bundle.prompt_version is None:
@@ -705,7 +705,7 @@ async def _rendered_prompt(
705
705
  'organization': org_slug,
706
706
  'agent': bundle.agent_slug or '',
707
707
  'task': task['short_id'],
708
- 'project': task.get('project_slug') or '',
708
+ 'project_slug': task.get('project_slug') or '',
709
709
  }
710
710
  rendered = await rendering.render(
711
711
  version,
@@ -2758,6 +2758,7 @@ async def _trigger_deployment(
2758
2758
  source: str | None,
2759
2759
  valkey_client: typing.Any,
2760
2760
  ) -> DeploymentTriggerResponse:
2761
+ await _require_delivery_enabled(db, project_id)
2761
2762
  if body.action == 'promote':
2762
2763
  return await _handle_promote(
2763
2764
  db,
@@ -3024,6 +3025,7 @@ async def resync_project_deployments(
3024
3025
  button using the plugin manifest flag. Returns ``enqueued=False``
3025
3026
  when the job was debounced or Valkey is unavailable.
3026
3027
  """
3028
+ await _require_delivery_enabled(db, project_id)
3027
3029
  resolved = await resolve_capability(db, project_id, 'deployment', source)
3028
3030
  _require_deployment_sync_support(resolved)
3029
3031
  requested_by = auth.principal_name
@@ -3258,12 +3260,31 @@ async def _handle_deploy(
3258
3260
  if body.ref_label and _SEMVER_REF_RE.match(body.ref_label)
3259
3261
  else None
3260
3262
  )
3263
+
3264
+ async def _append(
3265
+ release_id: str,
3266
+ ) -> tuple[ReleaseEnvironmentEdgeResponse, AppendOutcome] | None:
3267
+ appended = await append_deployment_event(
3268
+ db,
3269
+ org_slug=org_slug,
3270
+ project_id=project_id,
3271
+ release_id=release_id,
3272
+ env_slug=body.environment,
3273
+ status='in_progress',
3274
+ note=note,
3275
+ external_run_id=str(run.run_id) if run.run_id else None,
3276
+ external_run_url=run.run_url,
3277
+ credential=ctx.credential_note,
3278
+ )
3279
+ return None if isinstance(appended, str) else appended
3280
+
3261
3281
  # Look up the release that was deployed. Try (committish, tag) first
3262
3282
  # so a SHA that ships under a tag matches the tagged Release node;
3263
3283
  # fall back to (committish, None) so a raw-SHA deploy still finds
3264
3284
  # an untagged Release if one exists.
3265
3285
  result: tuple[ReleaseEnvironmentEdgeResponse, AppendOutcome] | None = None
3266
3286
  matched_tag: str | None = None
3287
+ release_found = False
3267
3288
  for try_tag in [candidate_tag, None] if candidate_tag else [None]:
3268
3289
  release_id = await _release_id_for(
3269
3290
  db,
@@ -3273,26 +3294,44 @@ async def _handle_deploy(
3273
3294
  )
3274
3295
  if release_id is None:
3275
3296
  continue
3276
- appended = await append_deployment_event(
3277
- db,
3278
- org_slug=org_slug,
3279
- project_id=project_id,
3280
- release_id=release_id,
3281
- env_slug=body.environment,
3282
- status='in_progress',
3283
- note=note,
3284
- external_run_id=str(run.run_id) if run.run_id else None,
3285
- external_run_url=run.run_url,
3286
- credential=ctx.credential_note,
3287
- )
3288
- if not isinstance(appended, str):
3289
- result = appended
3297
+ release_found = True
3298
+ result = await _append(release_id)
3299
+ if result is not None:
3290
3300
  matched_tag = try_tag
3291
3301
  break
3302
+ if not release_found:
3303
+ # No Release names this deploy yet -- typically a raw commit
3304
+ # shipped to the first environment. Create it the way resync
3305
+ # would on observing the same deployment, so the rollout is
3306
+ # recorded now rather than only if a webhook later reports it.
3307
+ try:
3308
+ matched_tag = candidate_tag or await _existing_tag_for_committish(
3309
+ db, project_id=project_id, committish=committish_short
3310
+ )
3311
+ except ValueError:
3312
+ LOGGER.warning(
3313
+ 'Deploy triggered for project=%s committish=%s but several'
3314
+ ' tagged Releases carry that commit; not recorded',
3315
+ project_id,
3316
+ committish_short,
3317
+ )
3318
+ else:
3319
+ result = await _append(
3320
+ await _upsert_release_node(
3321
+ db,
3322
+ project_id=project_id,
3323
+ tag=matched_tag,
3324
+ committish=committish_short,
3325
+ title=matched_tag or body.ref_label or committish_short,
3326
+ notes_markdown='',
3327
+ release_url=None,
3328
+ created_by=auth.principal_name,
3329
+ )
3330
+ )
3292
3331
  if result is None:
3293
3332
  LOGGER.warning(
3294
- 'Deploy triggered for project=%s committish=%s tag=%s but no'
3295
- ' matching Release node was found; audit row suppressed',
3333
+ 'Deploy triggered for project=%s committish=%s tag=%s but it'
3334
+ ' could not be recorded; audit row suppressed',
3296
3335
  project_id,
3297
3336
  committish_short,
3298
3337
  candidate_tag,
@@ -4546,26 +4585,79 @@ def _watcher_auth() -> permissions.AuthContext:
4546
4585
  )
4547
4586
 
4548
4587
 
4549
- async def _project_is_deployable(db: graph.Graph, project_id: str) -> bool:
4550
- """True when any of the project's types is ``deployable``.
4588
+ async def _project_delivery(
4589
+ db: graph.Graph, project_id: str
4590
+ ) -> common_models.Delivery:
4591
+ """Resolve how *project_id* is delivered: deploy, release, or neither.
4551
4592
 
4552
- A project can carry more than one ``ProjectType``, so this aggregates
4593
+ The project's own ``delivery_mode`` wins; ``project-type`` (and a
4594
+ node written before the property existed) defers to the types. A
4595
+ project can carry more than one ``ProjectType``, so this aggregates
4553
4596
  rather than reading one: ``deployable`` XOR ``releasable`` holds per
4554
- type, not per project. A project with no type at all answers
4555
- ``False`` -- there is nothing to deploy into.
4597
+ type, not per project. A project with no type at all, or one that
4598
+ does not exist, answers neither -- there is nothing to ship.
4556
4599
  """
4557
4600
  query: typing.LiteralString = """
4558
4601
  MATCH (p:Project {{id: {project_id}}})
4559
4602
  OPTIONAL MATCH (p)-[:TYPE]->(pt:ProjectType)
4560
- RETURN collect(COALESCE(pt.deployable, false)) AS flags
4603
+ RETURN p.delivery_mode AS mode,
4604
+ collect(COALESCE(pt.deployable, false)) AS flags,
4605
+ collect(COALESCE(pt.releasable, false)) AS releasable
4561
4606
  """
4562
- rows = await db.execute(query, {'project_id': project_id}, ['flags'])
4607
+ rows = await db.execute(
4608
+ query, {'project_id': project_id}, ['mode', 'flags', 'releasable']
4609
+ )
4563
4610
  if not rows:
4564
- return False
4565
- flags = typing.cast(
4566
- 'list[object]', graph.parse_agtype(rows[0].get('flags')) or []
4611
+ return common_models.Delivery(deployable=False, releasable=False)
4612
+ row = rows[0]
4613
+
4614
+ def _any(column: str) -> bool:
4615
+ flags = typing.cast(
4616
+ 'list[object]', graph.parse_agtype(row.get(column)) or []
4617
+ )
4618
+ return any(bool(flag) for flag in flags)
4619
+
4620
+ mode = graph.parse_agtype(row.get('mode'))
4621
+ return common_models.resolve_delivery(
4622
+ mode if isinstance(mode, str) else None,
4623
+ deployable=_any('flags'),
4624
+ releasable=_any('releasable'),
4567
4625
  )
4568
- return any(bool(flag) for flag in flags)
4626
+
4627
+
4628
+ async def _project_is_deployable(db: graph.Graph, project_id: str) -> bool:
4629
+ """True when the project resolves to ``deployable``."""
4630
+ return (await _project_delivery(db, project_id)).deployable
4631
+
4632
+
4633
+ async def _require_delivery_enabled(db: graph.Graph, project_id: str) -> None:
4634
+ """Refuse to ship a project whose ``delivery_mode`` is ``disabled``.
4635
+
4636
+ Raises a 409: the project exists and the caller may act on it, but
4637
+ its releases are governed outside Imbi, so deploying, promoting,
4638
+ cutting a release, drafting notes, or resyncing it is off.
4639
+ """
4640
+ if await _delivery_mode_is_disabled(db, project_id):
4641
+ raise fastapi.HTTPException(
4642
+ status_code=409,
4643
+ detail=(
4644
+ 'Release and deployment processing is disabled for this '
4645
+ 'project (delivery_mode is "disabled").'
4646
+ ),
4647
+ )
4648
+
4649
+
4650
+ async def _delivery_mode_is_disabled(db: graph.Graph, project_id: str) -> bool:
4651
+ """True when the project node's ``delivery_mode`` is ``disabled``."""
4652
+ query: typing.LiteralString = """
4653
+ MATCH (p:Project {{id: {project_id}}})
4654
+ RETURN p.delivery_mode AS mode
4655
+ """
4656
+ rows = await db.execute(query, {'project_id': project_id}, ['mode'])
4657
+ if not rows:
4658
+ return False
4659
+ mode = graph.parse_agtype(rows[0].get('mode'))
4660
+ return isinstance(mode, str) and mode == 'disabled'
4569
4661
 
4570
4662
 
4571
4663
  async def poll_artifact_run(
@@ -5813,6 +5905,7 @@ async def draft_release_notes(
5813
5905
  grouping with ``degraded=true`` when Claude is unavailable, the
5814
5906
  response can't be parsed, or schema validation fails.
5815
5907
  """
5908
+ await _require_delivery_enabled(db, project_id)
5816
5909
  resolved, ctx, credentials = await _resolve_and_context(
5817
5910
  db, org_slug, project_id, auth, source=source
5818
5911
  )
@@ -6774,6 +6867,7 @@ async def cut_release(
6774
6867
  minus the deploy step), upsert the matching ``Release`` node, and
6775
6868
  record an audit row.
6776
6869
  """
6870
+ await _require_delivery_enabled(db, project_id)
6777
6871
  tag_formats = await _resolve_tag_formats(db, org_slug, project_id)
6778
6872
  patterns = [fmt.pattern for fmt in tag_formats]
6779
6873
  if not versioning.matches_tag_formats(body.tag, patterns):
@@ -95,6 +95,7 @@ class ProjectCreate(pydantic.BaseModel):
95
95
  )
96
96
  links: dict[str, pydantic.AnyUrl] = {}
97
97
  identifiers: dict[str, int | str] = {}
98
+ delivery_mode: models.DeliveryMode = 'project-type'
98
99
 
99
100
  @pydantic.field_validator('project_type_slugs')
100
101
  @classmethod
@@ -138,6 +139,7 @@ class ProjectUpdate(pydantic.BaseModel):
138
139
  )
139
140
  links: dict[str, pydantic.AnyUrl] | None = None
140
141
  identifiers: dict[str, int | str] | None = None
142
+ delivery_mode: models.DeliveryMode | None = None
141
143
 
142
144
 
143
145
  # ProjectRelationships now lives in imbi.common.models so the OpenAPI
@@ -214,6 +216,10 @@ class ReleaseSummary(pydantic.BaseModel):
214
216
  #: much of the range's ``drift_detected`` rests on verdicts rather
215
217
  #: than on the fail-closed default.
216
218
  drift_answered: int = 0
219
+ #: When the oldest unreleased commit that needs action landed:
220
+ #: the same commits ``drift_detected`` counts, so a commit CI
221
+ #: answered ``false`` is skipped. ``None`` when none needs action.
222
+ oldest_actionable_at: datetime.datetime | None = None
217
223
 
218
224
 
219
225
  class ProjectListTeamRef(pydantic.BaseModel):
@@ -264,6 +270,7 @@ class ProjectListItem(pydantic.BaseModel):
264
270
  team: ProjectListTeamRef
265
271
  project_types: list[ProjectListProjectTypeRef] = []
266
272
  environments: list[ProjectListEnvironmentRef] = []
273
+ delivery_mode: models.DeliveryMode = 'project-type'
267
274
  open_pr_count: int = 0
268
275
  closed_pr_count: int = 0
269
276
  viewer_open_pr_count: int = 0
@@ -286,6 +293,13 @@ class ProjectListItem(pydantic.BaseModel):
286
293
  #: every pair it draws. A pair with no entry was never evaluated,
287
294
  #: which the client must fail closed on and show.
288
295
  drift_ranges: dict[str, bool] = pydantic.Field(default_factory=dict)
296
+ #: ``"<base>..<head>"`` -> when the oldest commit that needs action
297
+ #: in that range landed. Only ranges that ``drift_ranges`` marks
298
+ #: ``true`` from their commits have an entry: a range that needs
299
+ #: action only because it could not be evaluated has no date.
300
+ drift_range_oldest: dict[str, datetime.datetime] = pydantic.Field(
301
+ default_factory=dict
302
+ )
289
303
 
290
304
 
291
305
  class ProjectResponse(pydantic.BaseModel):
@@ -307,6 +321,7 @@ class ProjectResponse(pydantic.BaseModel):
307
321
  environments: list[EnvironmentRef] = []
308
322
  links: dict[str, pydantic.AnyUrl] = {}
309
323
  identifiers: dict[str, int | str] = {}
324
+ delivery_mode: models.DeliveryMode = 'project-type'
310
325
  # The project's EXISTS_IN connections, one entry per
311
326
  # ``(:Project)-[:EXISTS_IN]->(:Integration)`` edge. Read-only
312
327
  # structured surface (identifier + canonical API URL + the dashboard
@@ -1089,9 +1104,21 @@ def _range_key(base: str, head: str) -> str:
1089
1104
  return f'{base}..{head}'
1090
1105
 
1091
1106
 
1107
+ class EnvironmentDrift(typing.NamedTuple):
1108
+ """The per-project results of :func:`_evaluate_environment_drift`.
1109
+
1110
+ Both map project id -> range key -> value. ``ranges`` is whether
1111
+ the range needs action; ``oldest`` is when its oldest commit that
1112
+ needs action landed.
1113
+ """
1114
+
1115
+ ranges: dict[str, dict[str, bool]]
1116
+ oldest: dict[str, dict[str, datetime.datetime]]
1117
+
1118
+
1092
1119
  async def _evaluate_environment_drift(
1093
1120
  project_data_list: list[dict[str, typing.Any]],
1094
- ) -> dict[str, dict[str, bool]]:
1121
+ ) -> EnvironmentDrift:
1095
1122
  """Apply the rule to every project's promotion steps.
1096
1123
 
1097
1124
  Two sequential reads: the endpoint commit times first, then the
@@ -1113,6 +1140,10 @@ async def _evaluate_environment_drift(
1113
1140
  range must not read as clean. The same precision means a distinct
1114
1141
  commit can share the *base* commit's timestamp, so the lower bound
1115
1142
  is inclusive and only the base commit itself is excluded, by sha.
1143
+
1144
+ The oldest date comes from the same in-range commits, so it needs no
1145
+ third read. A range that needs action only because its endpoints
1146
+ are unorderable or the verdict read failed gets no date.
1116
1147
  """
1117
1148
  ranges_by_pid: dict[str, list[tuple[str, str]]] = {}
1118
1149
  for project_data in project_data_list:
@@ -1123,7 +1154,7 @@ async def _evaluate_environment_drift(
1123
1154
  if found:
1124
1155
  ranges_by_pid[pid] = found
1125
1156
  if not ranges_by_pid:
1126
- return {}
1157
+ return EnvironmentDrift({}, {})
1127
1158
  pids = list(ranges_by_pid)
1128
1159
  shas = sorted(
1129
1160
  {
@@ -1150,6 +1181,7 @@ async def _evaluate_environment_drift(
1150
1181
  )
1151
1182
  actionable = await _fetch_actionable_commit_times(windows)
1152
1183
  out: dict[str, dict[str, bool]] = {}
1184
+ oldest: dict[str, dict[str, datetime.datetime]] = {}
1153
1185
  for pid, pairs in ranges_by_pid.items():
1154
1186
  marks = actionable.get(pid, []) if actionable is not None else None
1155
1187
  for base, head in pairs:
@@ -1158,16 +1190,18 @@ async def _evaluate_environment_drift(
1158
1190
  if lo is None or hi is None:
1159
1191
  continue
1160
1192
  key = _range_key(base, head)
1161
- if hi <= lo:
1162
- out.setdefault(pid, {})[key] = True
1163
- elif marks is None:
1193
+ if hi <= lo or marks is None:
1164
1194
  out.setdefault(pid, {})[key] = True
1165
- else:
1166
- out.setdefault(pid, {})[key] = any(
1167
- (lo < at <= hi) or (at == lo and sha != base)
1168
- for sha, at in marks
1169
- )
1170
- return out
1195
+ continue
1196
+ in_range = [
1197
+ at
1198
+ for sha, at in marks
1199
+ if (lo < at <= hi) or (at == lo and sha != base)
1200
+ ]
1201
+ out.setdefault(pid, {})[key] = bool(in_range)
1202
+ if in_range:
1203
+ oldest.setdefault(pid, {})[key] = min(in_range)
1204
+ return EnvironmentDrift(out, oldest)
1171
1205
 
1172
1206
 
1173
1207
  def _release_range_cutoffs(
@@ -1361,6 +1395,18 @@ async def _fetch_authored_times(
1361
1395
  return authored_by_project
1362
1396
 
1363
1397
 
1398
+ def _delivery_of(project_data: dict[str, typing.Any]) -> models.Delivery:
1399
+ """Resolve a listed project's delivery flags from its graph record."""
1400
+ project_types = typing.cast(
1401
+ list[dict[str, typing.Any]], project_data.get('project_types') or []
1402
+ )
1403
+ return models.resolve_delivery(
1404
+ project_data.get('delivery_mode'),
1405
+ deployable=any(pt.get('deployable') for pt in project_types),
1406
+ releasable=any(pt.get('releasable') for pt in project_types),
1407
+ )
1408
+
1409
+
1364
1410
  async def _fetch_release_summaries(
1365
1411
  project_ids: list[str],
1366
1412
  ) -> dict[str, ReleaseSummary]:
@@ -1446,6 +1492,7 @@ async def _fetch_release_summaries(
1446
1492
  ]
1447
1493
  counts_since_tag: dict[str, int] = {}
1448
1494
  drift_by_pid: dict[str, tuple[bool, int]] = {}
1495
+ oldest_by_pid: dict[str, datetime.datetime] = {}
1449
1496
  if tagged_pids:
1450
1497
  try:
1451
1498
  pid_cutoff = _release_range_cutoffs(
@@ -1469,7 +1516,12 @@ async def _fetch_release_summaries(
1469
1516
  ' count() AS c,'
1470
1517
  ' countIf(d.answered = 1 AND d.drift_detected)'
1471
1518
  ' AS drifted,'
1472
- ' countIf(d.answered = 1) AS answered'
1519
+ ' countIf(d.answered = 1) AS answered,'
1520
+ ' countIf(d.answered = 0 OR d.drift_detected)'
1521
+ ' AS actionable,'
1522
+ ' minIf(COALESCE(c.committed_at, c.authored_at),'
1523
+ ' d.answered = 0 OR d.drift_detected)'
1524
+ ' AS oldest_actionable_at'
1473
1525
  ' FROM commits AS c FINAL'
1474
1526
  ' LEFT JOIN (SELECT project_id, sha, drift_detected,'
1475
1527
  ' toUInt8(1) AS answered FROM commit_drift FINAL'
@@ -1510,6 +1562,21 @@ async def _fetch_release_summaries(
1510
1562
  for row in count_rows
1511
1563
  if row.get('project_id') is not None
1512
1564
  }
1565
+ # ``minIf`` over no matching rows gives the type's zero
1566
+ # (the epoch), not NULL, so read it only when a commit
1567
+ # needs action.
1568
+ oldest_by_pid = {
1569
+ str(row['project_id']): at
1570
+ for row in count_rows
1571
+ if row.get('project_id') is not None
1572
+ and int(row.get('actionable') or 0) > 0
1573
+ and (
1574
+ at := clickhouse.as_utc_or_none(
1575
+ row.get('oldest_actionable_at')
1576
+ )
1577
+ )
1578
+ is not None
1579
+ }
1513
1580
  except Exception: # noqa: BLE001
1514
1581
  LOGGER.warning(
1515
1582
  'Failed to fetch commit counts since tag',
@@ -1559,6 +1626,7 @@ async def _fetch_release_summaries(
1559
1626
  commits_since_tag=counts_since_tag.get(pid, 0),
1560
1627
  drift_detected=drift_by_pid.get(pid, (False, 0))[0],
1561
1628
  drift_answered=drift_by_pid.get(pid, (False, 0))[1],
1629
+ oldest_actionable_at=oldest_by_pid.get(pid),
1562
1630
  )
1563
1631
  return release_result
1564
1632
 
@@ -1765,6 +1833,7 @@ _SLIM_RETURN_FRAGMENT: typing.LiteralString = """
1765
1833
  END) AS envs
1766
1834
  RETURN p{{.id, .name, .slug, .description, .score,
1767
1835
  archived: coalesce(p.archived, false),
1836
+ delivery_mode: coalesce(p.delivery_mode, 'project-type'),
1768
1837
  team: t{{.name, .slug}},
1769
1838
  project_types: pts,
1770
1839
  environments: envs}} AS project
@@ -1819,6 +1888,7 @@ async def create_project(
1819
1888
  description=data.description,
1820
1889
  icon=data.icon,
1821
1890
  links=data.links,
1891
+ delivery_mode=data.delivery_mode,
1822
1892
  **{
1823
1893
  k: v
1824
1894
  for k, v in typing.cast(
@@ -2409,14 +2479,7 @@ async def list_projects(
2409
2479
  releasable_ids = [
2410
2480
  str(p.get('id', ''))
2411
2481
  for p in project_data_list
2412
- if p.get('id')
2413
- and any(
2414
- pt.get('releasable')
2415
- for pt in typing.cast(
2416
- list[dict[str, typing.Any]],
2417
- p.get('project_types') or [],
2418
- )
2419
- )
2482
+ if p.get('id') and _delivery_of(p).releasable
2420
2483
  ]
2421
2484
  viewer = auth.identity_for('github-enterprise-cloud')
2422
2485
  pr_counts, releases, release_summaries = await asyncio.gather(
@@ -2436,8 +2499,10 @@ async def list_projects(
2436
2499
  project_data['latest_deployments'] = releases.latest.get(pid, {})
2437
2500
  # Only ``ProjectListItem`` carries ``drift_ranges``, so the two
2438
2501
  # reads behind it are pure waste on the full-response path.
2439
- drift_ranges = (
2440
- await _evaluate_environment_drift(project_data_list) if slim else {}
2502
+ drift = (
2503
+ await _evaluate_environment_drift(project_data_list)
2504
+ if slim
2505
+ else EnvironmentDrift({}, {})
2441
2506
  )
2442
2507
 
2443
2508
  for project_data in project_data_list:
@@ -2449,7 +2514,8 @@ async def list_projects(
2449
2514
  project_data['closed_pr_count'] = closed_count
2450
2515
  project_data['viewer_open_pr_count'] = viewer_open
2451
2516
  project_data['viewer_closed_pr_count'] = viewer_closed
2452
- project_data['drift_ranges'] = drift_ranges.get(pid, {})
2517
+ project_data['drift_ranges'] = drift.ranges.get(pid, {})
2518
+ project_data['drift_range_oldest'] = drift.oldest.get(pid, {})
2453
2519
  summary = release_summaries.get(pid)
2454
2520
  if summary is not None:
2455
2521
  project_data['release_summary'] = summary.model_dump()
@@ -3347,6 +3413,11 @@ async def _execute_project_update(
3347
3413
  if data.identifiers is not None
3348
3414
  else existing_identifiers
3349
3415
  ),
3416
+ 'delivery_mode': (
3417
+ data.delivery_mode
3418
+ or existing_p.get('delivery_mode')
3419
+ or 'project-type'
3420
+ ),
3350
3421
  }
3351
3422
 
3352
3423
  # Merge blueprint extra fields: start from existing unknown keys,
@@ -3620,6 +3691,7 @@ async def patch_project(
3620
3691
  'environments': current_environments,
3621
3692
  'links': parsed_links,
3622
3693
  'identifiers': parsed_identifiers,
3694
+ 'delivery_mode': project_data.get('delivery_mode') or 'project-type',
3623
3695
  **extras,
3624
3696
  }
3625
3697