imbi-api 2.34.0__tar.gz → 2.35.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.34.0 → imbi_api-2.35.0}/PKG-INFO +2 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/pyproject.toml +2 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_pushed_at_repair.py +2 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/component_facts.py +5 -1
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/drift.py +10 -15
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/__init__.py +6 -6
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_events.py +3 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_history.py +4 -3
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_reads.py +21 -7
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/comments.py +13 -23
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/dashboard.py +33 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/operations_log.py +25 -13
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_configuration.py +2 -6
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_deployments.py +153 -19
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/projects.py +15 -28
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/lifespans.py +1 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/log.py +9 -16
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/operations.py +6 -19
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/lifecycle_dispatch.py +17 -32
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/prompts/release_notes_system.md +17 -5
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/sbom.py +16 -7
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/scoring/queue.py +5 -8
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/test_client.py +0 -81
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/test_init.py +0 -103
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_integration.py +12 -12
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_comments.py +18 -40
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_dashboard.py +46 -1
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_reads_sql.py +86 -1
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_versions.py +5 -5
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_documents.py +14 -14
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_operations_log.py +63 -35
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_configuration.py +6 -3
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_deployments.py +235 -28
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_deployments_autonomous.py +6 -1
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_projects.py +37 -52
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_releases.py +1 -1
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/support.py +61 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_commit_pushed_at_repair.py +3 -3
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_drift.py +10 -13
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_lifecycle_dispatch.py +22 -31
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_log.py +13 -14
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_operations.py +9 -5
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_sbom.py +2 -2
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_scoring_queue.py +5 -15
- {imbi_api-2.34.0 → imbi_api-2.35.0}/.gitignore +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/README.md +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/moon.yml +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/README.md +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/app.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/authorization_codes.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/autonomous.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/internal_services.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/local_auth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/login_providers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/membership.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/oauth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/oauth_clients.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/password.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/permissions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/principals.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/seed.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/sessions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/tokens.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/auth/totp.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/backfill_embeddings.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/blueprint_attributes.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/blueprint_compliance.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_sync/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_sync/queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/commit_sync/service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_status_repair.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sweeper.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sync/queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/deployment_sync/service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/documents/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/documents/read_sweeper.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/domain/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/domain/models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/domain/scoring.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/client.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/dependencies.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/base.html +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/base.txt +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/password_reset.html +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/welcome.html +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates/welcome.txt +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/email/templates.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_credentials.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_helpers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_pagination.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/_search_index.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/admin.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/ai_models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/api_keys.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/auth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/blueprints.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/components.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_likes.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_presence.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_templates.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/document_versions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/documents.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/environments.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/events.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/graph_query.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/integrations.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/local_auth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/maintenance.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/mfa.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/organizations.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_analysis.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_logs.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/project_types.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/pull_requests.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/releases.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/roles.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/scoring.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/search.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/status.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/tags.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/teams.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/uploads.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/user_activity.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/users.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/endpoints/webhooks.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/entrypoint.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/graph_sql.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/attribution.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/endpoints.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/errors.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/flows.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/host_integration.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/repository.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/resolution.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/state.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/identity/sweeper.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/llm/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/llm/dependencies.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/registry.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/state.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/maintenance/worker.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/mcp_test.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/middleware/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/middleware/rate_limit.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/openapi.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/orphan_releases.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/assignments.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/credentials.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/installer.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/lifecycle.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/reload.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/resolution.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/plugins/schemas.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/pr_sync/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/pr_sync/queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/pr_sync/service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/prompts/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/py.typed +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/relationships.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/release_promote/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/release_promote/queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/release_promote/service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/scoring/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/settings.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/client.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/dependencies.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/thumbnails.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/src/imbi/api/storage/validation.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_authentication.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_authorization.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_encryption.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_internal_services.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_login_providers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_membership.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_oauth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_oauth_server.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_permissions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_seed.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_sessions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/auth/test_totp.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/clickhouse/test_privacy.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_client.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_init.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/email/test_templates.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_admin.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_admin_plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_ai_models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_ai_providers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_api_keys.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_auth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_auth_providers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_blueprints.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_client_credentials.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_components.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_credentials.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_analytics.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_likes.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_presence.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_reads.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_document_templates.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_environments.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_events.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_graph_query.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_iggy_connectors.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_init.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_integrations.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_json_fields.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_link_definitions.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_local_auth.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_maintenance.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_mcp_servers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_mfa.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_organizations.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_pagination.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_analysis.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_commit_sync.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_incidents.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_integrations.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_lifecycle.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_logs.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_project_types.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_projects_helpers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_pull_requests.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_request_urls.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_roles.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_sa_api_keys.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_scoring.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_scoring_policies.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_search.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_search_index.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_service_accounts.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_service_writeback.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_status.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_tags.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_teams.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_uploads.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_user_activity.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_users.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/endpoints/test_webhooks.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/fixtures/sbom/tiny.json +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_endpoints.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_errors.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_flows.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_host_integration.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_repository.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_resolution.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_state.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/identity/test_sweeper.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/middleware/test_rate_limit.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/__init__.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_client.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_init.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_thumbnails.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/storage/test_validation.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_app.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_assignment_writer.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_backfill_embeddings.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_blueprint_attributes.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_blueprint_compliance.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_blueprints.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_commit_sync_queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_commit_sync_service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_component_facts.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_status_repair.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_sweeper.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_sync_queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_deployment_sync_service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_entrypoint.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_graph_sql.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_identity_attribution.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_init.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_installer.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_lifespans.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_state.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_maintenance_worker.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_models.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_openapi.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_orphan_releases.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugin_schemas.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugins.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugins_assignments.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_plugins_credentials.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_pr_sync_queue.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_pr_sync_service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_projects_drift_ranges.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_pull_requests_pending_deploy.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_release_promote_service.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.0}/tests/test_scoring_triggers.py +0 -0
- {imbi_api-2.34.0 → imbi_api-2.35.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.35.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.35.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.35.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.35.0",
|
|
32
32
|
"jinja2",
|
|
33
33
|
"jsonpatch>=1.33",
|
|
34
34
|
"mcp>=1.26.0",
|
|
@@ -80,7 +80,7 @@ import typing
|
|
|
80
80
|
|
|
81
81
|
import pydantic
|
|
82
82
|
|
|
83
|
-
from imbi.common import clickhouse, models
|
|
83
|
+
from imbi.common import clickhouse, iggy, models
|
|
84
84
|
|
|
85
85
|
LOGGER = logging.getLogger(__name__)
|
|
86
86
|
|
|
@@ -235,7 +235,7 @@ async def repair_project(
|
|
|
235
235
|
largest = max(largest, shift)
|
|
236
236
|
repaired.append(_repaired_record(row, delivered))
|
|
237
237
|
if repaired and not dry_run:
|
|
238
|
-
await
|
|
238
|
+
await iggy.publish('commits', 'maintenance', repaired)
|
|
239
239
|
LOGGER.info(
|
|
240
240
|
'restored pushed_at on %d commit(s) for project %s',
|
|
241
241
|
len(repaired),
|
|
@@ -332,10 +332,14 @@ async def release_components(
|
|
|
332
332
|
``ComponentRelease`` node and were never copied here, so the
|
|
333
333
|
listing endpoint fetches them from the graph by id -- a bounded
|
|
334
334
|
``IN`` over one release's versions, not a traversal.
|
|
335
|
+
|
|
336
|
+
``DISTINCT`` because the stream delivers at least once: a
|
|
337
|
+
redelivered fact row lands inside the winning batch, where the
|
|
338
|
+
batch ranking cannot remove it.
|
|
335
339
|
"""
|
|
336
340
|
rows = await clickhouse.query(
|
|
337
341
|
_by_release(
|
|
338
|
-
'SELECT c.component_id AS component_id,'
|
|
342
|
+
'SELECT DISTINCT c.component_id AS component_id,'
|
|
339
343
|
' c.component_release_id AS component_release_id,'
|
|
340
344
|
' c.purl_name AS purl_name,'
|
|
341
345
|
' c.ecosystem AS ecosystem,'
|
|
@@ -40,8 +40,7 @@ import typing
|
|
|
40
40
|
import fastapi
|
|
41
41
|
|
|
42
42
|
from imbi.api.auth import principals
|
|
43
|
-
from imbi.common import clickhouse, graph
|
|
44
|
-
from imbi.common.clickhouse import client as ch_client
|
|
43
|
+
from imbi.common import clickhouse, graph, iggy
|
|
45
44
|
from imbi.common.plugins import errors as plugin_errors
|
|
46
45
|
|
|
47
46
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -54,14 +53,6 @@ NAMESPACE = 'imbi-drift'
|
|
|
54
53
|
#: commit, which is what OR-ing a range needs.
|
|
55
54
|
VERDICT_TABLE = 'commit_drift'
|
|
56
55
|
|
|
57
|
-
_VERDICT_COLUMNS = [
|
|
58
|
-
'project_id',
|
|
59
|
-
'sha',
|
|
60
|
-
'drift_detected',
|
|
61
|
-
'paths',
|
|
62
|
-
'recorded_at',
|
|
63
|
-
]
|
|
64
|
-
|
|
65
56
|
REQUESTED_BY = principals.DRIFT_SYNC
|
|
66
57
|
|
|
67
58
|
#: How many unanswered Releases one sweep asks the remote about. Every
|
|
@@ -172,7 +163,7 @@ def parse_note(body: str | None) -> bool | None:
|
|
|
172
163
|
async def record_verdicts(
|
|
173
164
|
project_id: str, verdicts: dict[str, NoteVerdict]
|
|
174
165
|
) -> int | None:
|
|
175
|
-
"""Persist per-commit verdicts, one
|
|
166
|
+
"""Persist per-commit verdicts, one publish for the whole batch.
|
|
176
167
|
|
|
177
168
|
Keyed by the annotated commit's full SHA to join ``imbi.commits``,
|
|
178
169
|
so only callers holding full SHAs write here -- the notes tree is
|
|
@@ -191,16 +182,20 @@ async def record_verdicts(
|
|
|
191
182
|
"""
|
|
192
183
|
now = datetime.datetime.now(datetime.UTC)
|
|
193
184
|
rows = [
|
|
194
|
-
|
|
185
|
+
{
|
|
186
|
+
'project_id': project_id,
|
|
187
|
+
'sha': sha.lower(),
|
|
188
|
+
'drift_detected': verdict.drift_detected,
|
|
189
|
+
'paths': verdict.paths,
|
|
190
|
+
'recorded_at': now,
|
|
191
|
+
}
|
|
195
192
|
for sha, verdict in verdicts.items()
|
|
196
193
|
if verdict.drift_detected is not None
|
|
197
194
|
]
|
|
198
195
|
if not rows:
|
|
199
196
|
return 0
|
|
200
197
|
try:
|
|
201
|
-
await
|
|
202
|
-
VERDICT_TABLE, rows, _VERDICT_COLUMNS
|
|
203
|
-
)
|
|
198
|
+
await iggy.publish_rows(VERDICT_TABLE, 'drift', rows)
|
|
204
199
|
except Exception:
|
|
205
200
|
LOGGER.exception(
|
|
206
201
|
'could not record %d drift verdicts for project %s',
|
|
@@ -13,7 +13,7 @@ rendered using Jinja2 templates with both HTML and plain text versions.
|
|
|
13
13
|
import logging
|
|
14
14
|
from urllib import parse
|
|
15
15
|
|
|
16
|
-
from imbi.common import
|
|
16
|
+
from imbi.common import graph, iggy
|
|
17
17
|
|
|
18
18
|
from .client import EmailClient
|
|
19
19
|
from .dependencies import InjectEmailClient, InjectTemplateManager
|
|
@@ -154,18 +154,18 @@ async def send_password_reset(
|
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
async def _save_audit(audit: models.EmailAudit) -> None:
|
|
157
|
-
"""
|
|
157
|
+
"""Publish the email audit record to the ``email_audit`` stream.
|
|
158
158
|
|
|
159
159
|
Args:
|
|
160
160
|
audit: Email audit record to save
|
|
161
161
|
|
|
162
162
|
"""
|
|
163
163
|
try:
|
|
164
|
-
await
|
|
165
|
-
LOGGER.debug('Email audit
|
|
166
|
-
except
|
|
164
|
+
await iggy.publish('email_audit', 'email', [audit])
|
|
165
|
+
LOGGER.debug('Email audit published: %s', audit.to_email)
|
|
166
|
+
except iggy.PublishError as err:
|
|
167
167
|
# Log error but don't fail the email send
|
|
168
168
|
LOGGER.warning(
|
|
169
|
-
'Failed to
|
|
169
|
+
'Failed to publish email audit: %s',
|
|
170
170
|
err,
|
|
171
171
|
)
|
|
@@ -10,7 +10,7 @@ than surfaced.
|
|
|
10
10
|
import datetime
|
|
11
11
|
import logging
|
|
12
12
|
|
|
13
|
-
from imbi.common import
|
|
13
|
+
from imbi.common import iggy, models
|
|
14
14
|
|
|
15
15
|
LOGGER = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -34,8 +34,9 @@ async def emit_like_event(
|
|
|
34
34
|
activity feed, which keys on ``attributed_to`` rather than project.
|
|
35
35
|
"""
|
|
36
36
|
try:
|
|
37
|
-
await
|
|
37
|
+
await iggy.publish(
|
|
38
38
|
'events',
|
|
39
|
+
'documents',
|
|
39
40
|
[
|
|
40
41
|
models.Event(
|
|
41
42
|
project_id=project_id,
|
|
@@ -18,7 +18,7 @@ import typing
|
|
|
18
18
|
|
|
19
19
|
import pydantic
|
|
20
20
|
|
|
21
|
-
from imbi.common import clickhouse
|
|
21
|
+
from imbi.common import clickhouse, iggy
|
|
22
22
|
|
|
23
23
|
LOGGER = logging.getLogger(__name__)
|
|
24
24
|
|
|
@@ -79,8 +79,9 @@ async def record_created(
|
|
|
79
79
|
) -> None:
|
|
80
80
|
"""Record version 1 for a newly created document. Best-effort."""
|
|
81
81
|
try:
|
|
82
|
-
await
|
|
82
|
+
await iggy.publish(
|
|
83
83
|
'document_versions',
|
|
84
|
+
'documents',
|
|
84
85
|
[
|
|
85
86
|
DocumentVersionRow(
|
|
86
87
|
document_id=document_id,
|
|
@@ -162,7 +163,7 @@ async def record_updated(
|
|
|
162
163
|
recorded_at=now,
|
|
163
164
|
)
|
|
164
165
|
)
|
|
165
|
-
await
|
|
166
|
+
await iggy.publish('document_versions', 'documents', rows)
|
|
166
167
|
except Exception:
|
|
167
168
|
LOGGER.exception(
|
|
168
169
|
'failed to record version %s for document %s',
|
|
@@ -27,7 +27,7 @@ import pydantic
|
|
|
27
27
|
from valkey import asyncio as valkey_asyncio
|
|
28
28
|
|
|
29
29
|
from imbi.api.endpoints import _document_scope
|
|
30
|
-
from imbi.common import clickhouse, graph
|
|
30
|
+
from imbi.common import clickhouse, graph, iggy
|
|
31
31
|
|
|
32
32
|
LOGGER = logging.getLogger(__name__)
|
|
33
33
|
|
|
@@ -238,8 +238,9 @@ async def record_events(rows: list[DocumentReadEventRow]) -> None:
|
|
|
238
238
|
if not rows:
|
|
239
239
|
return
|
|
240
240
|
try:
|
|
241
|
-
await
|
|
241
|
+
await iggy.publish(
|
|
242
242
|
'document_read_events',
|
|
243
|
+
'documents',
|
|
243
244
|
typing.cast('list[pydantic.BaseModel]', rows),
|
|
244
245
|
)
|
|
245
246
|
except Exception:
|
|
@@ -352,7 +353,7 @@ async def finalize_sessions(session_ids: list[str]) -> int:
|
|
|
352
353
|
if not rows:
|
|
353
354
|
return 0
|
|
354
355
|
try:
|
|
355
|
-
await
|
|
356
|
+
await iggy.publish('document_read_sessions', 'documents', rows)
|
|
356
357
|
except Exception:
|
|
357
358
|
LOGGER.exception('failed to finalize %d session(s)', len(rows))
|
|
358
359
|
return 0
|
|
@@ -366,8 +367,20 @@ async def finalize_sessions(session_ids: list[str]) -> int:
|
|
|
366
367
|
#: never having been finalized.
|
|
367
368
|
SWEEP_LOOKBACK_HOURS = 24
|
|
368
369
|
|
|
369
|
-
# Sessions whose last heartbeat has aged past the idle timeout and
|
|
370
|
-
#
|
|
370
|
+
# Sessions whose last heartbeat has aged past the idle timeout and whose
|
|
371
|
+
# finalized row does not yet cover that heartbeat.
|
|
372
|
+
#
|
|
373
|
+
# The condition is "not finalized *through* the last beat", not "not
|
|
374
|
+
# finalized at all". Heartbeats reach ClickHouse through Iggy, so a
|
|
375
|
+
# session finalized inline by its own final flush aggregates only the
|
|
376
|
+
# beats the sink had already drained -- the last one or two are
|
|
377
|
+
# routinely missing, and `engaged_ms` and `max_scroll_pct` are short by
|
|
378
|
+
# that much. Skipping every session that has any row would make that
|
|
379
|
+
# shortfall permanent. Comparing `ended_at` against the last beat makes
|
|
380
|
+
# this sweep a repair pass instead: it re-finalizes the session once the
|
|
381
|
+
# sink has caught up, `finalize_sessions` is idempotent, and the
|
|
382
|
+
# rewritten row then covers the last beat, so the session stops
|
|
383
|
+
# matching.
|
|
371
384
|
#
|
|
372
385
|
# Both sides are pinned to the same recent window so neither scans more
|
|
373
386
|
# than the other needs: without the `recorded_at` bound the outer scan
|
|
@@ -386,11 +399,12 @@ FROM (
|
|
|
386
399
|
HAVING last_beat < now() - INTERVAL {idle_seconds:UInt32} SECOND
|
|
387
400
|
) AS e
|
|
388
401
|
LEFT JOIN (
|
|
389
|
-
SELECT
|
|
402
|
+
SELECT session_id, max(ended_at) AS finalized_through
|
|
390
403
|
FROM imbi.document_read_sessions
|
|
391
404
|
WHERE started_at > now() - INTERVAL {lookback_hours:UInt32} HOUR
|
|
405
|
+
GROUP BY session_id
|
|
392
406
|
) AS s USING (session_id)
|
|
393
|
-
WHERE s.session_id = ''
|
|
407
|
+
WHERE s.session_id = '' OR s.finalized_through < e.last_beat
|
|
394
408
|
LIMIT {batch:UInt32}
|
|
395
409
|
"""
|
|
396
410
|
|
|
@@ -25,9 +25,8 @@ import pydantic
|
|
|
25
25
|
|
|
26
26
|
from imbi.api.auth import permissions
|
|
27
27
|
from imbi.api.endpoints import _search_index
|
|
28
|
-
from imbi.common import graph, models
|
|
28
|
+
from imbi.common import graph, iggy, models
|
|
29
29
|
from imbi.common import patch as json_patch
|
|
30
|
-
from imbi.common.clickhouse import client as ch_client
|
|
31
30
|
|
|
32
31
|
LOGGER = logging.getLogger(__name__)
|
|
33
32
|
|
|
@@ -55,18 +54,19 @@ async def _emit_comment_event(
|
|
|
55
54
|
failed analytics write must not fail the comment request.
|
|
56
55
|
"""
|
|
57
56
|
try:
|
|
58
|
-
await
|
|
57
|
+
await iggy.publish_rows(
|
|
59
58
|
'events',
|
|
59
|
+
'comments',
|
|
60
60
|
[
|
|
61
|
-
|
|
62
|
-
nanoid.generate(),
|
|
63
|
-
project_id or '',
|
|
64
|
-
datetime.datetime.now(datetime.UTC),
|
|
65
|
-
_COMMENT_EVENT_TYPE,
|
|
66
|
-
'internal',
|
|
67
|
-
principal,
|
|
68
|
-
{},
|
|
69
|
-
{
|
|
61
|
+
{
|
|
62
|
+
'id': nanoid.generate(),
|
|
63
|
+
'project_id': project_id or '',
|
|
64
|
+
'recorded_at': datetime.datetime.now(datetime.UTC),
|
|
65
|
+
'type': _COMMENT_EVENT_TYPE,
|
|
66
|
+
'integration': 'internal',
|
|
67
|
+
'attributed_to': principal,
|
|
68
|
+
'metadata': {},
|
|
69
|
+
'payload': {
|
|
70
70
|
'document_id': document_id,
|
|
71
71
|
'thread_id': thread_id,
|
|
72
72
|
'comment_id': comment_id,
|
|
@@ -74,17 +74,7 @@ async def _emit_comment_event(
|
|
|
74
74
|
'action': action,
|
|
75
75
|
'excerpt': body[:140],
|
|
76
76
|
},
|
|
77
|
-
|
|
78
|
-
],
|
|
79
|
-
[
|
|
80
|
-
'id',
|
|
81
|
-
'project_id',
|
|
82
|
-
'recorded_at',
|
|
83
|
-
'type',
|
|
84
|
-
'integration',
|
|
85
|
-
'attributed_to',
|
|
86
|
-
'metadata',
|
|
87
|
-
'payload',
|
|
77
|
+
}
|
|
88
78
|
],
|
|
89
79
|
)
|
|
90
80
|
except Exception:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Admin dashboard system-health status endpoint.
|
|
2
2
|
|
|
3
|
-
Probes the datastores in-process (PostgreSQL/AGE, ClickHouse, Valkey
|
|
3
|
+
Probes the datastores in-process (PostgreSQL/AGE, ClickHouse, Valkey,
|
|
4
|
+
Iggy)
|
|
4
5
|
and the sibling HTTP services via their ``/status`` endpoints. Every
|
|
5
6
|
check runs concurrently with a short timeout so the endpoint never
|
|
6
7
|
blocks on a degraded dependency.
|
|
@@ -18,7 +19,7 @@ import pydantic
|
|
|
18
19
|
|
|
19
20
|
from imbi.api import settings, version
|
|
20
21
|
from imbi.api.auth import permissions
|
|
21
|
-
from imbi.common import clickhouse, graph, valkey
|
|
22
|
+
from imbi.common import clickhouse, graph, iggy, valkey
|
|
22
23
|
|
|
23
24
|
LOGGER = logging.getLogger(__name__)
|
|
24
25
|
|
|
@@ -207,6 +208,35 @@ async def _check_valkey() -> DatastoreStatus:
|
|
|
207
208
|
)
|
|
208
209
|
|
|
209
210
|
|
|
211
|
+
async def _check_iggy() -> DatastoreStatus:
|
|
212
|
+
"""Ping Iggy and total the bytes held by the topics Imbi publishes."""
|
|
213
|
+
start = time.perf_counter()
|
|
214
|
+
try:
|
|
215
|
+
await asyncio.wait_for(iggy.ping(), _CHECK_TIMEOUT)
|
|
216
|
+
except Exception as err: # noqa: BLE001
|
|
217
|
+
LOGGER.warning('Iggy health check failed: %s', err)
|
|
218
|
+
return DatastoreStatus(
|
|
219
|
+
name='Iggy',
|
|
220
|
+
role='Event streaming',
|
|
221
|
+
status='error',
|
|
222
|
+
detail=str(err),
|
|
223
|
+
)
|
|
224
|
+
latency = _ms(start)
|
|
225
|
+
# Size is best-effort; it must not fail the (already-ok) check.
|
|
226
|
+
try:
|
|
227
|
+
size = await asyncio.wait_for(iggy.stored_bytes(), _CHECK_TIMEOUT)
|
|
228
|
+
except Exception as err: # noqa: BLE001
|
|
229
|
+
LOGGER.warning('Iggy size probe failed: %s', err)
|
|
230
|
+
size = None
|
|
231
|
+
return DatastoreStatus(
|
|
232
|
+
name='Iggy',
|
|
233
|
+
role='Event streaming',
|
|
234
|
+
status='ok',
|
|
235
|
+
latency_ms=latency,
|
|
236
|
+
size_bytes=size,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
210
240
|
async def _check_service(
|
|
211
241
|
client: httpx.AsyncClient, name: str, base_url: str
|
|
212
242
|
) -> ServiceStatus:
|
|
@@ -251,6 +281,7 @@ async def get_dashboard_status(
|
|
|
251
281
|
_check_postgres(db),
|
|
252
282
|
_check_clickhouse(),
|
|
253
283
|
_check_valkey(),
|
|
284
|
+
_check_iggy(),
|
|
254
285
|
),
|
|
255
286
|
asyncio.gather(
|
|
256
287
|
_check_service(
|
|
@@ -12,6 +12,7 @@ are hidden.
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
import asyncio
|
|
15
16
|
import datetime
|
|
16
17
|
import logging
|
|
17
18
|
import typing
|
|
@@ -29,7 +30,7 @@ from imbi.api.endpoints._pagination import (
|
|
|
29
30
|
encode_cursor,
|
|
30
31
|
parse_iso,
|
|
31
32
|
)
|
|
32
|
-
from imbi.common import clickhouse, models
|
|
33
|
+
from imbi.common import clickhouse, iggy, models
|
|
33
34
|
from imbi.common import patch as json_patch
|
|
34
35
|
from imbi.common.plugins import OpsLogTemplate
|
|
35
36
|
from imbi.common.plugins.registry import list_plugins
|
|
@@ -147,18 +148,19 @@ def _model_to_row(entry: models.OperationLog) -> dict[str, typing.Any]:
|
|
|
147
148
|
return dumped
|
|
148
149
|
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
#: One poll interval of the ClickHouse sink. A row published moments
|
|
152
|
+
#: ago is not readable until the sink has drained it, so a read that
|
|
153
|
+
#: expects it waits this long once before concluding it is absent.
|
|
154
|
+
SINK_POLL_INTERVAL_SECONDS: typing.Final[float] = 0.25
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
|
|
157
|
+
async def _insert_row(row: dict[str, typing.Any], topic: str = 'api') -> None:
|
|
158
|
+
"""Publish a single ``operations_log`` row to the Iggy stream.
|
|
159
|
+
|
|
160
|
+
The row is a column-name mapping, so it goes through
|
|
161
|
+
``publish_rows``; the ``_row_version`` alias is already rendered.
|
|
156
162
|
"""
|
|
157
|
-
await
|
|
158
|
-
'operations_log',
|
|
159
|
-
[list(row.values())],
|
|
160
|
-
list(row.keys()),
|
|
161
|
-
)
|
|
163
|
+
await iggy.publish_rows('operations_log', topic, [row])
|
|
162
164
|
|
|
163
165
|
|
|
164
166
|
_BY_EXTERNAL_RUN_SQL: typing.Final[str] = (
|
|
@@ -176,14 +178,24 @@ async def complete_opslog_entry(
|
|
|
176
178
|
"""Mark the opslog entry for ``external_run_id`` as completed.
|
|
177
179
|
|
|
178
180
|
Looks up the latest in-progress row by ``external_run_id``, then
|
|
179
|
-
re-
|
|
181
|
+
re-publishes it with ``completed_at`` set and a bumped ``_row_version``
|
|
180
182
|
so ReplacingMergeTree converges to the completed state. Returns
|
|
181
183
|
``True`` when an entry was found and written, ``False`` otherwise.
|
|
182
184
|
Silently no-ops when the entry is already completed or missing.
|
|
185
|
+
|
|
186
|
+
The open row reaches ClickHouse through the sink, so when the lookup
|
|
187
|
+
finds nothing it is retried once after one poll interval before the
|
|
188
|
+
entry is treated as missing. The start and the completion are
|
|
189
|
+
normally seconds to minutes apart, so the retry is a safety net.
|
|
183
190
|
"""
|
|
184
191
|
rows = await clickhouse.client.Clickhouse.get_instance().query(
|
|
185
192
|
_BY_EXTERNAL_RUN_SQL, {'run_id': external_run_id}
|
|
186
193
|
)
|
|
194
|
+
if not rows:
|
|
195
|
+
await asyncio.sleep(SINK_POLL_INTERVAL_SECONDS)
|
|
196
|
+
rows = await clickhouse.client.Clickhouse.get_instance().query(
|
|
197
|
+
_BY_EXTERNAL_RUN_SQL, {'run_id': external_run_id}
|
|
198
|
+
)
|
|
187
199
|
if not rows:
|
|
188
200
|
LOGGER.debug(
|
|
189
201
|
'complete_opslog_entry: no open entry for external_run_id=%s',
|
|
@@ -193,7 +205,7 @@ async def complete_opslog_entry(
|
|
|
193
205
|
row = dict(rows[0])
|
|
194
206
|
row['completed_at'] = completed_at
|
|
195
207
|
row['_row_version'] = next_row_version(int(row['_row_version']))
|
|
196
|
-
await _insert_row(row)
|
|
208
|
+
await _insert_row(row, 'deployments')
|
|
197
209
|
return True
|
|
198
210
|
|
|
199
211
|
|
|
@@ -18,7 +18,7 @@ from imbi.api.endpoints._helpers import (
|
|
|
18
18
|
from imbi.api.identity.host_integration import call_with_identity_retry
|
|
19
19
|
from imbi.api.plugins import call_with_timeout
|
|
20
20
|
from imbi.api.plugins.resolution import ResolvedCapability, resolve_capability
|
|
21
|
-
from imbi.common import
|
|
21
|
+
from imbi.common import graph, iggy, valkey
|
|
22
22
|
from imbi.common import models as common_models
|
|
23
23
|
from imbi.common.plugins import decrypt_integration_credentials
|
|
24
24
|
from imbi.common.plugins.base import (
|
|
@@ -412,8 +412,4 @@ async def _write_audit(
|
|
|
412
412
|
)
|
|
413
413
|
row = entry.model_dump(by_alias=True, mode='python')
|
|
414
414
|
row['is_deleted'] = 1 if entry.is_deleted else 0
|
|
415
|
-
await
|
|
416
|
-
'operations_log',
|
|
417
|
-
[list(row.values())],
|
|
418
|
-
list(row.keys()),
|
|
419
|
-
)
|
|
415
|
+
await iggy.publish_rows('operations_log', 'configuration', [row])
|