imbi-api 2.32.3__tar.gz → 2.34.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.32.3 → imbi_api-2.34.0}/PKG-INFO +2 -2
- {imbi_api-2.32.3 → imbi_api-2.34.0}/pyproject.toml +2 -2
- imbi_api-2.34.0/src/imbi/api/commit_pushed_at_repair.py +249 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/dashboard.py +26 -2
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/projects.py +4 -2
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/pull_requests.py +223 -20
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/operations.py +79 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/registry.py +35 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_dashboard.py +19 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_projects_helpers.py +2 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_pull_requests.py +93 -0
- imbi_api-2.34.0/tests/test_commit_pushed_at_repair.py +292 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_operations.py +97 -0
- imbi_api-2.34.0/tests/test_pull_requests_pending_deploy.py +165 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/.gitignore +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/README.md +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/moon.yml +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/README.md +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/app.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/authorization_codes.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/autonomous.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/internal_services.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/local_auth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/login_providers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/membership.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/oauth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/oauth_clients.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/password.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/permissions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/principals.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/seed.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/sessions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/tokens.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/auth/totp.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/backfill_embeddings.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/blueprint_attributes.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/blueprint_compliance.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/commit_sync/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/commit_sync/queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/commit_sync/service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/component_facts.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_status_repair.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sweeper.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sync/queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/deployment_sync/service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/documents/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/documents/read_sweeper.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/domain/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/domain/models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/domain/scoring.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/drift.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/client.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/dependencies.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/base.html +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/base.txt +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/password_reset.html +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/welcome.html +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates/welcome.txt +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/email/templates.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_credentials.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_events.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_history.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_reads.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_helpers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_pagination.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/_search_index.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/admin.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/ai_models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/api_keys.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/auth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/blueprints.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/comments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/components.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_likes.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_presence.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_templates.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/document_versions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/documents.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/environments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/events.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/graph_query.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/integrations.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/local_auth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/maintenance.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/mfa.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/operations_log.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/organizations.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_analysis.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_configuration.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_deployments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_logs.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/project_types.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/releases.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/roles.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/scoring.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/search.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/status.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/tags.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/teams.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/uploads.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/user_activity.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/users.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/endpoints/webhooks.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/entrypoint.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/graph_sql.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/attribution.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/endpoints.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/errors.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/flows.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/host_integration.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/repository.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/resolution.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/state.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/identity/sweeper.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/lifespans.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/llm/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/llm/dependencies.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/log.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/state.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/maintenance/worker.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/mcp_test.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/middleware/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/middleware/rate_limit.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/openapi.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/orphan_releases.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/assignments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/credentials.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/installer.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/lifecycle.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/lifecycle_dispatch.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/reload.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/resolution.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/plugins/schemas.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/pr_sync/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/pr_sync/queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/pr_sync/service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/prompts/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/prompts/release_notes_system.md +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/py.typed +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/relationships.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/release_promote/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/release_promote/queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/release_promote/service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/sbom.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/scoring/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/scoring/queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/settings.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/client.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/dependencies.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/thumbnails.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/src/imbi/api/storage/validation.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_authentication.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_authorization.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_encryption.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_internal_services.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_login_providers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_membership.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_oauth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_oauth_server.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_permissions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_seed.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_sessions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/auth/test_totp.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/test_client.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/test_init.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/clickhouse/test_privacy.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_client.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_init.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_integration.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/email/test_templates.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_admin.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_admin_plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_ai_models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_ai_providers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_api_keys.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_auth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_auth_providers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_blueprints.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_client_credentials.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_comments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_components.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_credentials.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_analytics.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_likes.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_presence.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_reads.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_reads_sql.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_templates.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_document_versions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_documents.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_environments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_events.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_graph_query.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_iggy_connectors.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_init.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_integrations.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_json_fields.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_link_definitions.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_local_auth.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_maintenance.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_mcp_servers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_mfa.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_operations_log.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_organizations.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_pagination.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_analysis.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_commit_sync.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_configuration.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_deployments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_deployments_autonomous.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_incidents.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_integrations.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_lifecycle.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_logs.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_project_types.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_projects.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_releases.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_request_urls.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_roles.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_sa_api_keys.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_scoring.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_scoring_policies.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_search.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_search_index.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_service_accounts.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_service_writeback.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_status.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_tags.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_teams.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_uploads.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_user_activity.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_users.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/endpoints/test_webhooks.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/fixtures/sbom/tiny.json +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_endpoints.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_errors.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_flows.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_host_integration.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_repository.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_resolution.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_state.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/identity/test_sweeper.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/middleware/test_rate_limit.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/__init__.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_client.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_init.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_thumbnails.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/storage/test_validation.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/support.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_app.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_assignment_writer.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_backfill_embeddings.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_blueprint_attributes.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_blueprint_compliance.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_blueprints.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_commit_sync_queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_commit_sync_service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_component_facts.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_status_repair.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_sweeper.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_sync_queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_deployment_sync_service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_drift.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_entrypoint.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_graph_sql.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_identity_attribution.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_init.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_installer.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_lifecycle_dispatch.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_lifespans.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_log.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_state.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_maintenance_worker.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_models.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_openapi.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_orphan_releases.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugin_schemas.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugins.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugins_assignments.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_plugins_credentials.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_pr_sync_queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_pr_sync_service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_projects_drift_ranges.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_release_promote_service.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_sbom.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_scoring_queue.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_scoring_triggers.py +0 -0
- {imbi_api-2.32.3 → imbi_api-2.34.0}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: imbi-api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.34.0
|
|
4
4
|
Summary: Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications.
|
|
5
5
|
Author-email: Alex Campbell <alexc@aweber.com>, Dave Shawley <daves@aweber.com>, "Gavin M. Roy" <gavinr@aweber.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -19,7 +19,7 @@ Requires-Dist: cyclonedx-python-lib<12,>=11.7
|
|
|
19
19
|
Requires-Dist: fastapi
|
|
20
20
|
Requires-Dist: filetype
|
|
21
21
|
Requires-Dist: httpx<1,>=0.28.1
|
|
22
|
-
Requires-Dist: imbi-common[databases,llm,sentry,server]==2.
|
|
22
|
+
Requires-Dist: imbi-common[databases,llm,sentry,server]==2.34.0
|
|
23
23
|
Requires-Dist: jinja2
|
|
24
24
|
Requires-Dist: jsonpatch>=1.33
|
|
25
25
|
Requires-Dist: mcp>=1.26.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "imbi-api"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.34.0"
|
|
4
4
|
description = "Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -28,7 +28,7 @@ dependencies = [
|
|
|
28
28
|
"fastapi",
|
|
29
29
|
"filetype",
|
|
30
30
|
"httpx>=0.28.1,<1",
|
|
31
|
-
"imbi-common[databases,llm,sentry,server]==2.
|
|
31
|
+
"imbi-common[databases,llm,sentry,server]==2.34.0",
|
|
32
32
|
"jinja2",
|
|
33
33
|
"jsonpatch>=1.33",
|
|
34
34
|
"mcp>=1.26.0",
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Put back the push time commit re-syncs overwrote.
|
|
2
|
+
|
|
3
|
+
Every commit-sync path used to stamp ``commits.pushed_at`` with the
|
|
4
|
+
clock at ingest, and the table is a ``ReplacingMergeTree`` versioned on
|
|
5
|
+
``recorded_at`` -- so any re-sync of a commit already stored replaced
|
|
6
|
+
its row with a fresh ``pushed_at``. Two producers did that at scale:
|
|
7
|
+
|
|
8
|
+
* the full backfill (``sync_all_history``, the ``commit-sync``
|
|
9
|
+
maintenance operation), which stamps every commit a project has with
|
|
10
|
+
the single instant the run started. Three runs in August 2026 left
|
|
11
|
+
roughly 19,000 commits across 600 projects sharing a handful of
|
|
12
|
+
timestamps, their push order collapsed onto author order;
|
|
13
|
+
* the ``workflow_run`` webhook rule, which re-syncs a run's head commit
|
|
14
|
+
when the run completes. A release workflow runs against the commit
|
|
15
|
+
a release was *cut from*, so that commit was re-stamped minutes after
|
|
16
|
+
the release commit itself and sorted above it (#308).
|
|
17
|
+
|
|
18
|
+
``pushed_at`` orders the recent-commits feed, picks the commit-sync
|
|
19
|
+
gap-healing base, and picks the release-drift head, so the wrong values
|
|
20
|
+
mis-slice release ranges. The sync no longer re-stamps a stored commit,
|
|
21
|
+
but nothing that reads GitHub can put the old values back: the commits
|
|
22
|
+
API carries author and committer dates, never a push time.
|
|
23
|
+
|
|
24
|
+
The ``events`` table can. It holds every webhook delivery, and a
|
|
25
|
+
``push`` delivery names the commits it carried (``payload.commits[]``,
|
|
26
|
+
plus ``payload.after`` for the head) and the branch it was for
|
|
27
|
+
(``payload.ref``). Its ``recorded_at`` is within about a second of the
|
|
28
|
+
``pushed_at`` the sync originally wrote from it. So for each stored
|
|
29
|
+
commit, the earliest push delivery to the commit's own branch that
|
|
30
|
+
lists it is what its ``pushed_at`` was before anything overwrote it.
|
|
31
|
+
|
|
32
|
+
Deliberately conservative:
|
|
33
|
+
|
|
34
|
+
* only pushes to the branch the commit is stored under count. A pull
|
|
35
|
+
request's commits are also pushed to their feature branch, earlier,
|
|
36
|
+
and the sync's branch-gated rule never recorded those -- matching
|
|
37
|
+
them would order the commits by when their branch was pushed rather
|
|
38
|
+
than by when they reached the default branch, which is a different
|
|
39
|
+
wrong order.
|
|
40
|
+
* a row is rewritten only when its ``pushed_at`` is *later* than the
|
|
41
|
+
delivery by more than :data:`TOLERANCE`. Within tolerance it is the
|
|
42
|
+
original stamp; earlier means an even earlier sync recorded it and
|
|
43
|
+
that value is closer to the truth.
|
|
44
|
+
* a commit with no push delivery on record is left alone. Deliveries
|
|
45
|
+
only go back to June 2026, GitHub lists at most twenty commits per
|
|
46
|
+
push payload, and a compare-healed gap has no delivery of its own.
|
|
47
|
+
There is nothing to restore, and inventing a value would be worse
|
|
48
|
+
than the collapsed one.
|
|
49
|
+
|
|
50
|
+
The rewrite is a re-insert of the full row with the corrected
|
|
51
|
+
``pushed_at`` and a fresh ``recorded_at`` -- the same read-modify-insert
|
|
52
|
+
``opslog-backfill`` uses -- so ``FINAL`` readers see it at once and the
|
|
53
|
+
background merge collapses the superseded version. It composes with
|
|
54
|
+
the sync's carry-forward, which reads the *earliest* ``pushed_at``
|
|
55
|
+
across row versions: once the repaired row is on disk, every later
|
|
56
|
+
re-sync of that commit inherits the restored value.
|
|
57
|
+
|
|
58
|
+
There is no lock between this and a webhook re-sync, and the two are not
|
|
59
|
+
serialized: a re-sync that read the stored value *before* the repaired
|
|
60
|
+
row landed and inserted *after* it writes the stale value back with a
|
|
61
|
+
newer ``recorded_at``, and that version wins. The window is the few
|
|
62
|
+
milliseconds between the sync's lookup and its insert, it has to
|
|
63
|
+
coincide with a delivery for that exact commit, and the outcome is one
|
|
64
|
+
row back where it started rather than anything new being wrong. The
|
|
65
|
+
repair is idempotent, so the remedy is to run it again; the
|
|
66
|
+
``commit-pushed-at-check`` dry run after a repair is what shows whether
|
|
67
|
+
anything did slip through. A shared lock would have to span the API
|
|
68
|
+
worker and the gateway's webhook handler for a race this narrow and
|
|
69
|
+
this cheaply undone, which is not worth the coupling.
|
|
70
|
+
|
|
71
|
+
Runs as the ``commit-pushed-at-repair`` maintenance operation, with
|
|
72
|
+
``commit-pushed-at-check`` as its dry run.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
from __future__ import annotations
|
|
76
|
+
|
|
77
|
+
import datetime
|
|
78
|
+
import logging
|
|
79
|
+
import typing
|
|
80
|
+
|
|
81
|
+
import pydantic
|
|
82
|
+
|
|
83
|
+
from imbi.common import clickhouse, models
|
|
84
|
+
|
|
85
|
+
LOGGER = logging.getLogger(__name__)
|
|
86
|
+
|
|
87
|
+
#: How far a stored ``pushed_at`` may trail its push delivery before it
|
|
88
|
+
#: counts as re-stamped. The webhook sync stamps ``now()`` while
|
|
89
|
+
#: handling the delivery, so the original value lands within a second
|
|
90
|
+
#: or two of ``events.recorded_at``; the smallest re-stamp -- a CI run
|
|
91
|
+
#: completing -- is minutes later.
|
|
92
|
+
TOLERANCE = datetime.timedelta(seconds=60)
|
|
93
|
+
|
|
94
|
+
_BRANCH_REF_PREFIX = 'refs/heads/'
|
|
95
|
+
|
|
96
|
+
#: Every column ``CommitRecord`` carries except ``recorded_at``, which
|
|
97
|
+
#: the re-insert regenerates. Listed rather than ``SELECT *`` so a
|
|
98
|
+
#: column added to the table later is a loud failure here, not a row
|
|
99
|
+
#: silently rewritten with a default.
|
|
100
|
+
_COMMIT_COLUMNS: typing.Final = (
|
|
101
|
+
'project_id',
|
|
102
|
+
'sha',
|
|
103
|
+
'short_sha',
|
|
104
|
+
'ref',
|
|
105
|
+
'message',
|
|
106
|
+
'author_name',
|
|
107
|
+
'author_email',
|
|
108
|
+
'author_login',
|
|
109
|
+
'author_user',
|
|
110
|
+
'committer_name',
|
|
111
|
+
'ci_status',
|
|
112
|
+
'authored_at',
|
|
113
|
+
'committed_at',
|
|
114
|
+
'url',
|
|
115
|
+
'pushed_at',
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# ``payload`` is a JSON column; ``payload.commits[]`` is its array
|
|
119
|
+
# subcolumn and ``c.id`` the per-element path. ``metadata.event_type``
|
|
120
|
+
# is the gateway's per-source label ('push'), distinct from the
|
|
121
|
+
# top-level ``type`` category ('webhook').
|
|
122
|
+
_DELIVERIES_SQL: typing.Final = (
|
|
123
|
+
'SELECT recorded_at, '
|
|
124
|
+
'toString(payload.ref) AS ref, '
|
|
125
|
+
'toString(payload.after) AS after, '
|
|
126
|
+
'arrayMap(c -> toString(c.id), payload.commits[]) AS commit_ids '
|
|
127
|
+
'FROM events '
|
|
128
|
+
'WHERE project_id = {project_id:String} '
|
|
129
|
+
"AND type = 'webhook' "
|
|
130
|
+
"AND toString(metadata.event_type) = 'push' "
|
|
131
|
+
"AND toString(payload.ref) LIKE 'refs/heads/%'"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Column list is a module constant; values are bound parameters.
|
|
135
|
+
_COMMITS_SQL: typing.Final = (
|
|
136
|
+
'SELECT ' + ', '.join(_COMMIT_COLUMNS) + ' FROM commits FINAL ' # noqa: S608
|
|
137
|
+
'WHERE project_id = {project_id:String} '
|
|
138
|
+
'AND sha IN {shas:Array(String)}'
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class RepairSummary(typing.NamedTuple):
|
|
143
|
+
"""What one project's repair did (or, dry run, would do)."""
|
|
144
|
+
|
|
145
|
+
#: Push deliveries to a branch found for the project.
|
|
146
|
+
deliveries: int = 0
|
|
147
|
+
#: Stored commits a delivery to their own branch accounts for.
|
|
148
|
+
examined: int = 0
|
|
149
|
+
#: Rows rewritten -- or, on a dry run, rows that would be.
|
|
150
|
+
repaired: int = 0
|
|
151
|
+
#: Largest correction applied, in seconds. Tells an operator
|
|
152
|
+
#: whether a project's damage was the backfill (days) or a
|
|
153
|
+
#: ``workflow_run`` re-stamp (minutes).
|
|
154
|
+
largest_shift_seconds: int = 0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def _push_times(
|
|
158
|
+
project_id: str,
|
|
159
|
+
) -> tuple[int, dict[tuple[str, str], datetime.datetime]]:
|
|
160
|
+
"""Push deliveries read, and ``(branch, sha) -> earliest delivery``.
|
|
161
|
+
|
|
162
|
+
A commit pushed to several branches (a feature branch, then the
|
|
163
|
+
default branch on merge) gets one entry per branch, so the caller
|
|
164
|
+
can pick the delivery for the branch the commit is stored under.
|
|
165
|
+
"""
|
|
166
|
+
rows = await clickhouse.query(_DELIVERIES_SQL, {'project_id': project_id})
|
|
167
|
+
earliest: dict[tuple[str, str], datetime.datetime] = {}
|
|
168
|
+
for row in rows:
|
|
169
|
+
recorded_at = clickhouse.as_utc_or_none(row.get('recorded_at'))
|
|
170
|
+
ref = str(row.get('ref') or '')
|
|
171
|
+
if recorded_at is None or not ref.startswith(_BRANCH_REF_PREFIX):
|
|
172
|
+
continue
|
|
173
|
+
branch = ref[len(_BRANCH_REF_PREFIX) :]
|
|
174
|
+
listed = typing.cast('list[object]', row.get('commit_ids') or [])
|
|
175
|
+
shas = {str(sha) for sha in listed if sha}
|
|
176
|
+
# ``after`` is the head the delivery attests to even when the
|
|
177
|
+
# payload's commit list is capped (GitHub sends at most twenty)
|
|
178
|
+
# or empty (a branch created from an existing commit).
|
|
179
|
+
after = str(row.get('after') or '')
|
|
180
|
+
if after and after != '0' * 40:
|
|
181
|
+
shas.add(after)
|
|
182
|
+
for sha in shas:
|
|
183
|
+
key = (branch, sha.lower())
|
|
184
|
+
if key not in earliest or recorded_at < earliest[key]:
|
|
185
|
+
earliest[key] = recorded_at
|
|
186
|
+
return len(rows), earliest
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _repaired_record(
|
|
190
|
+
row: dict[str, typing.Any], pushed_at: datetime.datetime
|
|
191
|
+
) -> models.CommitRecord:
|
|
192
|
+
"""The row as a ``CommitRecord`` carrying the restored push time.
|
|
193
|
+
|
|
194
|
+
``recorded_at`` is left to the model's default so the re-insert
|
|
195
|
+
ranks newest under ``ReplacingMergeTree(recorded_at)``.
|
|
196
|
+
"""
|
|
197
|
+
fields = {name: row.get(name) for name in _COMMIT_COLUMNS}
|
|
198
|
+
fields['authored_at'] = clickhouse.as_utc_or_none(row.get('authored_at'))
|
|
199
|
+
fields['committed_at'] = clickhouse.as_utc_or_none(row.get('committed_at'))
|
|
200
|
+
fields['pushed_at'] = pushed_at
|
|
201
|
+
return models.CommitRecord.model_validate(fields)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def repair_project(
|
|
205
|
+
project_id: str, *, dry_run: bool = False
|
|
206
|
+
) -> RepairSummary:
|
|
207
|
+
"""Restore one project's re-stamped commit push times.
|
|
208
|
+
|
|
209
|
+
Idempotent: a repaired row sits within :data:`TOLERANCE` of its
|
|
210
|
+
delivery, so a second run finds nothing to rewrite.
|
|
211
|
+
"""
|
|
212
|
+
deliveries, push_times = await _push_times(project_id)
|
|
213
|
+
if not push_times:
|
|
214
|
+
return RepairSummary(deliveries=deliveries)
|
|
215
|
+
shas = sorted({sha for _, sha in push_times})
|
|
216
|
+
rows = await clickhouse.query(
|
|
217
|
+
_COMMITS_SQL, {'project_id': project_id, 'shas': shas}
|
|
218
|
+
)
|
|
219
|
+
examined = 0
|
|
220
|
+
largest = datetime.timedelta(0)
|
|
221
|
+
repaired: list[pydantic.BaseModel] = []
|
|
222
|
+
for row in rows:
|
|
223
|
+
sha = str(row.get('sha') or '').lower()
|
|
224
|
+
branch = str(row.get('ref') or '')
|
|
225
|
+
delivered = push_times.get((branch, sha))
|
|
226
|
+
if delivered is None:
|
|
227
|
+
continue # only pushed to some other branch; see module doc
|
|
228
|
+
examined += 1
|
|
229
|
+
stored = clickhouse.as_utc_or_none(row.get('pushed_at'))
|
|
230
|
+
if stored is None:
|
|
231
|
+
continue
|
|
232
|
+
shift = stored - delivered
|
|
233
|
+
if shift <= TOLERANCE:
|
|
234
|
+
continue
|
|
235
|
+
largest = max(largest, shift)
|
|
236
|
+
repaired.append(_repaired_record(row, delivered))
|
|
237
|
+
if repaired and not dry_run:
|
|
238
|
+
await clickhouse.insert('commits', repaired)
|
|
239
|
+
LOGGER.info(
|
|
240
|
+
'restored pushed_at on %d commit(s) for project %s',
|
|
241
|
+
len(repaired),
|
|
242
|
+
project_id,
|
|
243
|
+
)
|
|
244
|
+
return RepairSummary(
|
|
245
|
+
deliveries=deliveries,
|
|
246
|
+
examined=examined,
|
|
247
|
+
repaired=len(repaired),
|
|
248
|
+
largest_shift_seconds=int(largest.total_seconds()),
|
|
249
|
+
)
|
|
@@ -289,8 +289,24 @@ def _build_series(
|
|
|
289
289
|
return MetricSeries(total=sum(daily), daily=daily)
|
|
290
290
|
|
|
291
291
|
|
|
292
|
+
async def _active_project_ids(db: graph.Graph) -> list[str]:
|
|
293
|
+
"""IDs of every project that is not archived."""
|
|
294
|
+
query: typing.LiteralString = """
|
|
295
|
+
MATCH (p:Project)
|
|
296
|
+
WHERE coalesce(p.archived, false) = false
|
|
297
|
+
RETURN p.id AS id
|
|
298
|
+
"""
|
|
299
|
+
records = await db.execute(query, {}, ['id'])
|
|
300
|
+
return [
|
|
301
|
+
graph.parse_agtype(r['id'])
|
|
302
|
+
for r in records
|
|
303
|
+
if graph.parse_agtype(r['id'])
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
|
|
292
307
|
@dashboard_router.get('/metrics', response_model=DashboardMetrics)
|
|
293
308
|
async def get_dashboard_metrics(
|
|
309
|
+
db: graph.Pool,
|
|
294
310
|
auth: typing.Annotated[
|
|
295
311
|
permissions.AuthContext,
|
|
296
312
|
fastapi.Depends(
|
|
@@ -298,7 +314,10 @@ async def get_dashboard_metrics(
|
|
|
298
314
|
),
|
|
299
315
|
],
|
|
300
316
|
) -> DashboardMetrics:
|
|
301
|
-
"""Return 7-day activity metrics with per-day counts for the tiles.
|
|
317
|
+
"""Return 7-day activity metrics with per-day counts for the tiles.
|
|
318
|
+
|
|
319
|
+
The pull request series skips drafts and archived projects.
|
|
320
|
+
"""
|
|
302
321
|
today = datetime.datetime.now(datetime.UTC).date()
|
|
303
322
|
start_date = today - datetime.timedelta(days=_METRICS_WINDOW_DAYS - 1)
|
|
304
323
|
since = datetime.datetime.combine(
|
|
@@ -308,7 +327,10 @@ async def get_dashboard_metrics(
|
|
|
308
327
|
(start_date + datetime.timedelta(days=offset)).isoformat()
|
|
309
328
|
for offset in range(_METRICS_WINDOW_DAYS)
|
|
310
329
|
]
|
|
311
|
-
params: dict[str, typing.Any] = {
|
|
330
|
+
params: dict[str, typing.Any] = {
|
|
331
|
+
'since': since,
|
|
332
|
+
'project_ids': await _active_project_ids(db),
|
|
333
|
+
}
|
|
312
334
|
# One scan of the (FINAL, merge-on-read) deploy rows yields both the
|
|
313
335
|
# per-day series and the per-environment rollup.
|
|
314
336
|
deploys, events, ops, prs = await asyncio.gather(
|
|
@@ -339,6 +361,8 @@ async def get_dashboard_metrics(
|
|
|
339
361
|
'SELECT toDate(created_at) AS day, count() AS c'
|
|
340
362
|
' FROM pull_requests FINAL'
|
|
341
363
|
' WHERE created_at >= {since:DateTime64(3)}'
|
|
364
|
+
' AND NOT draft'
|
|
365
|
+
' AND project_id IN {project_ids:Array(String)}'
|
|
342
366
|
' GROUP BY day',
|
|
343
367
|
params,
|
|
344
368
|
),
|
|
@@ -553,8 +553,8 @@ async def _fetch_pr_counts(
|
|
|
553
553
|
) -> dict[str, tuple[int, int, int, int]]:
|
|
554
554
|
"""Return {project_id: (open, closed, viewer_open, viewer_closed)}.
|
|
555
555
|
|
|
556
|
-
Errors are swallowed — PR counts are
|
|
557
|
-
fail the project list endpoint.
|
|
556
|
+
Draft PRs are not counted. Errors are swallowed — PR counts are
|
|
557
|
+
best-effort and should not fail the project list endpoint.
|
|
558
558
|
"""
|
|
559
559
|
if not project_ids:
|
|
560
560
|
return {}
|
|
@@ -570,6 +570,7 @@ async def _fetch_pr_counts(
|
|
|
570
570
|
' AS viewer_closed_count'
|
|
571
571
|
' FROM pull_requests FINAL'
|
|
572
572
|
' WHERE project_id IN {project_ids:Array(String)}'
|
|
573
|
+
' AND NOT draft'
|
|
573
574
|
' GROUP BY project_id'
|
|
574
575
|
)
|
|
575
576
|
params['viewer'] = viewer
|
|
@@ -582,6 +583,7 @@ async def _fetch_pr_counts(
|
|
|
582
583
|
' 0 AS viewer_closed_count'
|
|
583
584
|
' FROM pull_requests FINAL'
|
|
584
585
|
' WHERE project_id IN {project_ids:Array(String)}'
|
|
586
|
+
' AND NOT draft'
|
|
585
587
|
' GROUP BY project_id'
|
|
586
588
|
)
|
|
587
589
|
try:
|