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.
- {imbi_api-2.41.0 → imbi_api-2.42.0}/PKG-INFO +3 -3
- {imbi_api-2.41.0 → imbi_api-2.42.0}/pyproject.toml +3 -3
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/harness.py +2 -2
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_deployments.py +121 -27
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/projects.py +95 -23
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_deployments.py +403 -11
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_projects.py +161 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_agent_platform.py +15 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_projects_drift_ranges.py +56 -1
- {imbi_api-2.41.0 → imbi_api-2.42.0}/.gitignore +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/README.md +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/moon.yml +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/README.md +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/log.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/schemata.toml +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/store.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tasks/sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/agent_tools.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/app.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/agent_api.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/agent_api_routes.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/authorization_codes.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/autonomous.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/internal_services.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/local_auth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/login_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/membership.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/oauth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/oauth_clients.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/organizations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/password.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/permissions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/principals.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/seed.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/sessions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/tokens.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/auth/totp.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/backfill_embeddings.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/blueprint_attributes.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/blueprint_compliance.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_pushed_at_repair.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_sync/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_sync/queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/commit_sync/service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/component_facts.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_status_repair.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sync/queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/deployment_sync/service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/documents/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/documents/read_sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/domain/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/domain/models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/domain/scoring.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/drift.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/client.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/dependencies.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/base.html +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/base.txt +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/password_reset.html +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/welcome.html +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates/welcome.txt +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/email/templates.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_credentials.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_events.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_history.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_reads.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_helpers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_pagination.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/_search_index.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/admin.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_platform.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_task_harness.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_tasks.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agent_usage.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/agents.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/ai_models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/api_keys.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/auth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/blueprints.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/comments.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/components.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/dashboard.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_likes.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_presence.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_templates.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/document_versions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/documents.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/environments.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/events.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/graph_query.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/integrations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/local_auth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/maintenance.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/mfa.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/operations_log.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/organizations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_analysis.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_configuration.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_logs.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/project_types.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/prompts.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/pull_requests.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/releases.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/roles.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/scoring.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/search.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/secret_broker.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/status.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/tags.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/teams.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/uploads.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/user_activity.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/users.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/webhooks.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/workflow_runs.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/endpoints/workflows.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/entrypoint.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/graph_ids.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/graph_sql.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/auth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/egress.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/marketplaces.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/metrics.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/platform.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/schemata.toml +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/store.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/harness/tokens.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/attribution.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/endpoints.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/errors.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/flows.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/host_integration.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/repository.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/resolution.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/state.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/identity/sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/iggy_metrics.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/lifespans.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/llm/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/llm/dependencies.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/log.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/operations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/registry.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/state.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/maintenance/worker.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/mcp_test.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/middleware/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/middleware/rate_limit.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/openapi.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/orphan_releases.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/assignments.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/credentials.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/installer.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/lifecycle.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/lifecycle_dispatch.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/reload.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/resolution.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/plugins/schemas.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/pr_sync/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/pr_sync/queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/pr_sync/service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/prompts/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/prompts/release_notes_system.md +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/prompts/seed.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/py.typed +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/relationships.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/release_promote/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/release_promote/queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/release_promote/service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/sbom.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/scoring/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/scoring/queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/audit.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/broker.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/github_app.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/schemata.toml +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/secret_broker/store.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/settings.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/client.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/dependencies.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/thumbnails.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/storage/validation.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/user_document_orgs.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/runs.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/runtime_schemata.toml +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/schemata.toml +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/store.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/src/imbi/api/workflows/sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_authentication.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_authorization.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_encryption.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_internal_services.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_login_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_membership.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_oauth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_oauth_server.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_organizations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_permissions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_seed.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_sessions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/auth/test_totp.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/test_client.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/test_init.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/clickhouse/test_privacy.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_audit_clickhouse.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_client.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_init.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_integration.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/email/test_templates.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_admin.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_admin_plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_api.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_platform.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_actions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_harness.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_log.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_task_relations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_tasks.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agent_usage.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_agents.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_ai_models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_ai_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_api_keys.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_auth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_auth_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_blueprints.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_client_credentials.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_comments.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_components.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_credentials.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_cross_org_reads.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_dashboard.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_analytics.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_likes.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_presence.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_reads.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_reads_sql.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_templates.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_document_versions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_documents.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_environments.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_events.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_github_app_repository.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_graph_query.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_harness.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_harness_egress.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_harness_secrets.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_iggy_connectors.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_init.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_integrations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_json_fields.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_link_definitions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_local_auth.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_maintenance.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_mcp_servers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_mfa.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_operations_log.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_organizations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_pagination.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_analysis.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_commit_sync.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_configuration.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_deployments_autonomous.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_incidents.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_integrations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_lifecycle.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_logs.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_project_types.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_projects_helpers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_prompt_decisions.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_prompts.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_pull_requests.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_releases.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_request_urls.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_roles.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_sa_api_keys.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_scoring.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_scoring_policies.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_search.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_search_index.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_secret_broker.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_service_accounts.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_service_writeback.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_status.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_tags.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_teams.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_uploads.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_user_activity.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_users.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_webhooks.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflow_run_history.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflow_runs.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflow_triggers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/endpoints/test_workflows.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/marketplaces/claude-code.json +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/fixtures/sbom/tiny.json +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_endpoints.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_errors.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_flows.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_host_integration.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_repository.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_resolution.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_state.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/identity/test_sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/middleware/test_rate_limit.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/secret_support.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/__init__.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_client.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_init.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_thumbnails.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/storage/test_validation.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/support.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_agent_task_events_e2e.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_agent_tools.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_app.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_assignment_writer.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_auth_settings.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_backfill_embeddings.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_blueprint_attributes.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_blueprint_compliance.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_blueprints.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_commit_pushed_at_repair.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_commit_sync_queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_commit_sync_service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_component_facts.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_status_repair.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_sweeper.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_sync_queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_deployment_sync_service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_drift.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_entrypoint.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_graph_ids.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_graph_sql.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_identity_attribution.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_iggy_metrics.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_init.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_installer.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_lifecycle_dispatch.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_lifespans.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_log.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_operations.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_state.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_maintenance_worker.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_models.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_openapi.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_orphan_releases.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugin_schemas.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugins.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugins_assignments.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_plugins_credentials.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_pr_sync_queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_pr_sync_service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_prompt_seed.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_pull_requests_pending_deploy.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_release_promote_service.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_sbom.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_scoring_queue.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_scoring_triggers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_broker.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_github_app.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_github_app_live.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_secret_providers.py +0 -0
- {imbi_api-2.41.0 → imbi_api-2.42.0}/tests/test_settings.py +0 -0
- {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.
|
|
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.
|
|
23
|
-
Requires-Dist: imbi-plugin-github==2.
|
|
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.
|
|
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.
|
|
32
|
-
"imbi-plugin-github==2.
|
|
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 ``
|
|
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
|
-
'
|
|
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
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
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
|
|
3295
|
-
'
|
|
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
|
|
4550
|
-
|
|
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
|
-
|
|
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
|
|
4555
|
-
|
|
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
|
|
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(
|
|
4607
|
+
rows = await db.execute(
|
|
4608
|
+
query, {'project_id': project_id}, ['mode', 'flags', 'releasable']
|
|
4609
|
+
)
|
|
4563
4610
|
if not rows:
|
|
4564
|
-
return False
|
|
4565
|
-
|
|
4566
|
-
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
)
|
|
1170
|
-
|
|
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
|
-
|
|
2440
|
-
await _evaluate_environment_drift(project_data_list)
|
|
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'] =
|
|
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
|
|