imbi-api 2.9.2__tar.gz → 2.10.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.9.2 → imbi_api-2.10.0}/PKG-INFO +2 -2
- {imbi_api-2.9.2 → imbi_api-2.10.0}/pyproject.toml +2 -2
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/commit_sync/service.py +118 -24
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/domain/models.py +13 -1
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/domain/scoring.py +8 -1
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/dashboard.py +18 -7
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_commit_sync.py +8 -1
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/projects.py +177 -41
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/releases.py +43 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/scoring_policies.py +2 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/users.py +28 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/models.py +2 -1
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/scoring/queue.py +12 -6
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_projects.py +12 -4
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_projects_helpers.py +5 -5
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_releases.py +29 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_scoring_policies.py +46 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_users.py +33 -12
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_commit_sync_service.py +131 -2
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_scoring_triggers.py +23 -0
- imbi_api-2.10.0/uv.lock +3209 -0
- imbi_api-2.9.2/uv.lock +0 -3209
- {imbi_api-2.9.2 → imbi_api-2.10.0}/.github/workflows/deploy.yaml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/.github/workflows/docs.yaml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/.github/workflows/testing.yaml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/.gitignore +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/.pre-commit-config.yaml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/.python-version +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/CLAUDE.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/CODE_REVIEW_PUNCHLIST.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/LICENSE +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/README.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/coderabbit.yaml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/compose.yaml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0001-record-architecture-decisions.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0002-authentication-and-authorization-architecture.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0003-email-sending-architecture.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0004-phase-5-authentication-enhancements.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0005-file-upload-storage-architecture.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0006-project-identity-and-multi-type.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0007-relationship-blueprints.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0008-plugin-system-architecture.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0009-database-driven-oauth-providers.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0010-identity-plugin-architecture.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0011-graph-based-project-scoring.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0012-plugin-manifest-service-template.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0013-deployment-plugin-type.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0014-generic-plugin-entity-abstraction.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr/0015-cyclonedx-1.7-sbom-standard.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/adr.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/configuration.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/index.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/docs/restore-backup.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/justfile +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/mkdocs.yml +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/mypy.ini +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/app.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/authorization_codes.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/local_auth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/login_providers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/membership.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/models.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/oauth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/oauth_clients.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/password.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/permissions.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/seed.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/sessions.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/tokens.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/auth/totp.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/backfill_embeddings.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/blueprint_attributes.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/commit_sync/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/commit_sync/queue.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/domain/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/client.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/dependencies.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/models.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/base.html +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/base.txt +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/password_reset.html +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/password_reset.txt +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/welcome.html +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates/welcome.txt +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/email/templates.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_credentials.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_helpers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_json_fields.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_pagination.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/_request_urls.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/admin.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/admin_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/api_keys.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/auth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/auth_providers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/blueprints.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/client_credentials.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/comments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/document_templates.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/documents.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/environments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/events.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/graph_query.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/identity_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/link_definitions.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/local_auth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/mcp_servers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/mfa.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/oauth_metadata.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/operations_log.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/organizations.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/plugin_edges.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/plugin_entities.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_analysis.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_configuration.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_deployments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_logs.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_type_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/project_types.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/pull_requests.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/roles.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/sa_api_keys.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/scoring.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/search.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/service_accounts.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/service_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/status.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/tags.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/teams.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/third_party_services.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/uploads.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/user_activity.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/endpoints/webhooks.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/entrypoint.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/graph_sql.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/endpoints.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/errors.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/flows.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/host_integration.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/models.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/repository.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/resolution.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/state.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/identity/sweeper.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/lifespans.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/llm/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/llm/dependencies.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/mcp_test.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/middleware/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/middleware/rate_limit.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/openapi.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/patch.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/assignment_writer.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/assignments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/credentials.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/installer.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/lifecycle.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/lifecycle_dispatch.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/reload.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/resolution.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/plugins/schemas.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/prompts/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/prompts/release_notes_system.md +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/py.typed +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/relationships.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/sbom.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/scoring/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/settings.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/client.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/dependencies.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/thumbnails.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/src/imbi_api/storage/validation.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_api_key_auth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_authentication.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_authorization.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_encryption.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_login_providers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_membership.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_oauth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_oauth_server.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_permissions.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_seed.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_sessions.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/auth/test_totp.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/test_client.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/test_init.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/clickhouse/test_privacy.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_client.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_init.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_integration.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_models.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/email/test_templates.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_admin.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_admin_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_api_keys.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_auth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_auth_providers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_blueprints.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_client_credentials.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_comments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_credentials.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_dashboard.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_document_templates.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_documents.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_environments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_events.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_graph_query.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_init.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_json_fields.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_link_definitions.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_local_auth.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_mcp_servers.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_mfa.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_operations_log.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_organizations.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_pagination.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_analysis.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_commit_sync.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_configuration.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_deployments.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_logs.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_type_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_project_types.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_pull_requests.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_request_urls.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_roles.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_sa_api_keys.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_scoring.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_search.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_service_accounts.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_service_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_service_writeback.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_status.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_tags.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_teams.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_third_party_services.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_uploads.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_user_activity.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/endpoints/test_webhooks.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/fixtures/sbom/tiny.json +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_endpoints.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_errors.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_flows.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_host_integration.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_repository.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_resolution.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_state.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/identity/test_sweeper.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/middleware/test_rate_limit.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/__init__.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_client.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_init.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_thumbnails.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/storage/test_validation.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/support.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_app.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_assignment_writer.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_backfill_embeddings.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_blueprint_attributes.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_blueprints.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_commit_sync_queue.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_entrypoint.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_graph_sql.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_init.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_installer.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_lifecycle_dispatch.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_lifespans.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_models.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_openapi.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_patch.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_plugin_schemas.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_plugins.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_sbom.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_scoring_queue.py +0 -0
- {imbi_api-2.9.2 → imbi_api-2.10.0}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: imbi-api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.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: BSD-3-Clause
|
|
@@ -20,7 +20,7 @@ Requires-Dist: cyclonedx-python-lib<12,>=11.7
|
|
|
20
20
|
Requires-Dist: fastapi
|
|
21
21
|
Requires-Dist: filetype
|
|
22
22
|
Requires-Dist: httpx<1,>=0.28.1
|
|
23
|
-
Requires-Dist: imbi-common[databases,llm,sentry,server]==2.
|
|
23
|
+
Requires-Dist: imbi-common[databases,llm,sentry,server]==2.10.0
|
|
24
24
|
Requires-Dist: jinja2
|
|
25
25
|
Requires-Dist: jsonpatch>=1.33
|
|
26
26
|
Requires-Dist: mcp>=1.26.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "imbi-api"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.10.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.10.0",
|
|
32
32
|
"jinja2",
|
|
33
33
|
"jsonpatch>=1.33",
|
|
34
34
|
"mcp>=1.26.0",
|
|
@@ -13,6 +13,7 @@ node so the UI can poll it without a dedicated status store.
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import asyncio
|
|
16
17
|
import datetime
|
|
17
18
|
import logging
|
|
18
19
|
import typing
|
|
@@ -24,6 +25,8 @@ from imbi_common.plugins.base import PluginContext, ServicePlugin
|
|
|
24
25
|
from imbi_common.plugins.errors import PluginNotFoundError
|
|
25
26
|
from imbi_common.plugins.registry import RegistryEntry, get_plugin
|
|
26
27
|
|
|
28
|
+
from imbi_api import models
|
|
29
|
+
from imbi_api.identity import repository as identity_repository
|
|
27
30
|
from imbi_api.plugins import parse_options
|
|
28
31
|
from imbi_api.plugins.credentials import get_plugin_credentials
|
|
29
32
|
|
|
@@ -33,6 +36,12 @@ _COMMIT_SYNC_SLUG = 'github-commit-sync'
|
|
|
33
36
|
# Persisted error strings are truncated so a noisy upstream message can't
|
|
34
37
|
# bloat the Project node.
|
|
35
38
|
_MAX_ERROR_LEN = 500
|
|
39
|
+
# Apache AGE aborts a SET with "Entity failed to be updated: <n>"
|
|
40
|
+
# (heap_update -> TM_Updated) when another transaction updates the same
|
|
41
|
+
# Project vertex concurrently. The worker's authoritative status writes
|
|
42
|
+
# retry the transient conflict so they still land under contention.
|
|
43
|
+
_STATUS_WRITE_RETRIES = 3
|
|
44
|
+
_STATUS_RETRY_BACKOFF = 0.05
|
|
36
45
|
|
|
37
46
|
SyncState = typing.Literal['idle', 'queued', 'running', 'success', 'failed']
|
|
38
47
|
|
|
@@ -130,6 +139,61 @@ async def _resolve_plugin(
|
|
|
130
139
|
)
|
|
131
140
|
|
|
132
141
|
|
|
142
|
+
def _identity_plugin_slugs(service_plugins: list[ServicePlugin]) -> list[str]:
|
|
143
|
+
"""Return connected plugin slugs registered as identity plugins."""
|
|
144
|
+
slugs: list[str] = []
|
|
145
|
+
for plugin in service_plugins:
|
|
146
|
+
try:
|
|
147
|
+
entry = get_plugin(plugin.slug)
|
|
148
|
+
except PluginNotFoundError:
|
|
149
|
+
continue
|
|
150
|
+
if entry.manifest.plugin_type == 'identity':
|
|
151
|
+
slugs.append(plugin.slug)
|
|
152
|
+
return slugs
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _make_user_resolver(
|
|
156
|
+
db: graph.Graph, service_plugins: list[ServicePlugin]
|
|
157
|
+
) -> abc.Callable[[str], abc.Awaitable[str | None]] | None:
|
|
158
|
+
"""Build the ``PluginContext.resolve_user_by_identity`` resolver.
|
|
159
|
+
|
|
160
|
+
Maps an external identity *subject* (a GitHub numeric user id) to the
|
|
161
|
+
matching Imbi user's email by querying the graph directly -- no HTTP,
|
|
162
|
+
since this runs inside imbi-api on a background backfill. Connected
|
|
163
|
+
plugins are filtered to identity plugins; ``None`` is returned when
|
|
164
|
+
none qualify so the action skips the lookups entirely. A subject
|
|
165
|
+
resolving to two different users across plugins is treated as
|
|
166
|
+
unresolved (logged), never mis-attributed.
|
|
167
|
+
"""
|
|
168
|
+
identity_slugs = _identity_plugin_slugs(service_plugins)
|
|
169
|
+
if not identity_slugs:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
async def _resolve(subject: str) -> str | None:
|
|
173
|
+
emails: set[str] = set()
|
|
174
|
+
for slug in identity_slugs:
|
|
175
|
+
user_id = await identity_repository.find_user_by_subject(
|
|
176
|
+
db, slug, subject
|
|
177
|
+
)
|
|
178
|
+
if user_id is None:
|
|
179
|
+
continue
|
|
180
|
+
users = await db.match(models.User, {'id': user_id})
|
|
181
|
+
if users:
|
|
182
|
+
emails.add(users[0].email)
|
|
183
|
+
if len(emails) > 1:
|
|
184
|
+
LOGGER.error(
|
|
185
|
+
'Identity subject %r resolved to multiple Imbi users via '
|
|
186
|
+
'plugins %r: %r — leaving unattributed',
|
|
187
|
+
subject,
|
|
188
|
+
identity_slugs,
|
|
189
|
+
sorted(emails),
|
|
190
|
+
)
|
|
191
|
+
return None
|
|
192
|
+
return next(iter(emails), None)
|
|
193
|
+
|
|
194
|
+
return _resolve
|
|
195
|
+
|
|
196
|
+
|
|
133
197
|
async def _build_context(
|
|
134
198
|
db: graph.Graph,
|
|
135
199
|
org_slug: str,
|
|
@@ -167,6 +231,9 @@ async def _build_context(
|
|
|
167
231
|
project_type_slugs=project_type_slugs,
|
|
168
232
|
third_party_service_slug=resolved.tps_slug or None,
|
|
169
233
|
service_connections=service_connections,
|
|
234
|
+
resolve_user_by_identity=_make_user_resolver(
|
|
235
|
+
db, resolved.service_plugins
|
|
236
|
+
),
|
|
170
237
|
)
|
|
171
238
|
|
|
172
239
|
|
|
@@ -210,6 +277,11 @@ def _now_iso() -> str:
|
|
|
210
277
|
return datetime.datetime.now(datetime.UTC).isoformat()
|
|
211
278
|
|
|
212
279
|
|
|
280
|
+
def _is_write_conflict(exc: Exception) -> bool:
|
|
281
|
+
"""True if *exc* is AGE's concurrent-update conflict (TM_Updated)."""
|
|
282
|
+
return 'failed to be updated' in str(exc)
|
|
283
|
+
|
|
284
|
+
|
|
213
285
|
async def set_status(
|
|
214
286
|
db: graph.Graph,
|
|
215
287
|
project_id: str,
|
|
@@ -219,8 +291,17 @@ async def set_status(
|
|
|
219
291
|
commits: int = 0,
|
|
220
292
|
tags: int = 0,
|
|
221
293
|
error: str = '',
|
|
294
|
+
retry: bool = True,
|
|
222
295
|
) -> None:
|
|
223
|
-
"""Persist last-sync state on the ``Project`` node (best-effort).
|
|
296
|
+
"""Persist last-sync state on the ``Project`` node (best-effort).
|
|
297
|
+
|
|
298
|
+
*retry* re-attempts the write when AGE reports a transient concurrent
|
|
299
|
+
update, so the worker's authoritative transitions (running -> success/
|
|
300
|
+
failed) still land if a webhook touches the project mid-write. The
|
|
301
|
+
enqueue endpoint passes ``retry=False`` for its optimistic ``queued``
|
|
302
|
+
write: dropping that on conflict is correct, since the worker's newer
|
|
303
|
+
``running`` write must win rather than be clobbered back to ``queued``.
|
|
304
|
+
"""
|
|
224
305
|
query: typing.LiteralString = """
|
|
225
306
|
MATCH (p:Project {{id: {project_id}}})
|
|
226
307
|
SET p.commit_sync_status = {status},
|
|
@@ -231,26 +312,39 @@ async def set_status(
|
|
|
231
312
|
p.commit_sync_error = {error}
|
|
232
313
|
RETURN p.id AS id
|
|
233
314
|
"""
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
['id']
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
315
|
+
params = {
|
|
316
|
+
'project_id': project_id,
|
|
317
|
+
'status': status,
|
|
318
|
+
'at': _now_iso(),
|
|
319
|
+
'by': requested_by,
|
|
320
|
+
'commits': commits,
|
|
321
|
+
'tags': tags,
|
|
322
|
+
'error': error[:_MAX_ERROR_LEN],
|
|
323
|
+
}
|
|
324
|
+
attempts = _STATUS_WRITE_RETRIES if retry else 1
|
|
325
|
+
for attempt in range(attempts):
|
|
326
|
+
try:
|
|
327
|
+
await db.execute(query, params, ['id'])
|
|
328
|
+
return
|
|
329
|
+
except Exception as exc: # noqa: BLE001
|
|
330
|
+
conflict = _is_write_conflict(exc)
|
|
331
|
+
if retry and conflict and attempt + 1 < attempts:
|
|
332
|
+
await asyncio.sleep(_STATUS_RETRY_BACKOFF * (attempt + 1))
|
|
333
|
+
continue
|
|
334
|
+
if conflict:
|
|
335
|
+
LOGGER.debug(
|
|
336
|
+
'commit-sync status write for %s lost a concurrent '
|
|
337
|
+
'update (status=%s); leaving the newer state in place',
|
|
338
|
+
project_id,
|
|
339
|
+
status,
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
LOGGER.warning(
|
|
343
|
+
'Failed to persist commit-sync status for project %s',
|
|
344
|
+
project_id,
|
|
345
|
+
exc_info=True,
|
|
346
|
+
)
|
|
347
|
+
return
|
|
254
348
|
|
|
255
349
|
|
|
256
350
|
def _opt_str(value: object) -> str | None:
|
|
@@ -277,7 +371,7 @@ async def read_status(db: graph.Graph, project_id: str) -> CommitSyncStatus:
|
|
|
277
371
|
MATCH (p:Project {{id: {project_id}}})
|
|
278
372
|
RETURN p.commit_sync_status AS status,
|
|
279
373
|
p.commit_sync_at AS at,
|
|
280
|
-
p.commit_sync_by AS
|
|
374
|
+
p.commit_sync_by AS requested_by,
|
|
281
375
|
p.commit_sync_commits AS commits,
|
|
282
376
|
p.commit_sync_tags AS tags,
|
|
283
377
|
p.commit_sync_error AS error
|
|
@@ -285,7 +379,7 @@ async def read_status(db: graph.Graph, project_id: str) -> CommitSyncStatus:
|
|
|
285
379
|
records = await db.execute(
|
|
286
380
|
query,
|
|
287
381
|
{'project_id': project_id},
|
|
288
|
-
['status', 'at', '
|
|
382
|
+
['status', 'at', 'requested_by', 'commits', 'tags', 'error'],
|
|
289
383
|
)
|
|
290
384
|
if not records:
|
|
291
385
|
return CommitSyncStatus()
|
|
@@ -307,5 +401,5 @@ async def read_status(db: graph.Graph, project_id: str) -> CommitSyncStatus:
|
|
|
307
401
|
commits_synced=_opt_int(graph.parse_agtype(row.get('commits'))),
|
|
308
402
|
tags_synced=_opt_int(graph.parse_agtype(row.get('tags'))),
|
|
309
403
|
error=_opt_str(graph.parse_agtype(row.get('error'))),
|
|
310
|
-
requested_by=_opt_str(graph.parse_agtype(row.get('
|
|
404
|
+
requested_by=_opt_str(graph.parse_agtype(row.get('requested_by'))),
|
|
311
405
|
)
|
|
@@ -55,6 +55,7 @@ __all__ = [
|
|
|
55
55
|
'ServiceApplicationSecrets',
|
|
56
56
|
'Session',
|
|
57
57
|
'TOTPSecret',
|
|
58
|
+
'TeamMembership',
|
|
58
59
|
'ThirdPartyService',
|
|
59
60
|
'ThirdPartyServiceCreate',
|
|
60
61
|
'ThirdPartyServiceResponse',
|
|
@@ -224,6 +225,15 @@ class OrgMembership(pydantic.BaseModel):
|
|
|
224
225
|
role: str
|
|
225
226
|
|
|
226
227
|
|
|
228
|
+
class TeamMembership(pydantic.BaseModel):
|
|
229
|
+
"""Team membership for API responses."""
|
|
230
|
+
|
|
231
|
+
model_config = pydantic.ConfigDict(extra='ignore')
|
|
232
|
+
team_name: str
|
|
233
|
+
team_slug: str
|
|
234
|
+
organization_slug: str
|
|
235
|
+
|
|
236
|
+
|
|
227
237
|
class Role(models.Node): # type: ignore[misc]
|
|
228
238
|
"""Role for grouping permissions."""
|
|
229
239
|
|
|
@@ -445,12 +455,14 @@ class CurrentUserResponse(UserResponse):
|
|
|
445
455
|
"""Response model for the authenticated user's own profile.
|
|
446
456
|
|
|
447
457
|
Extends ``UserResponse`` with the caller's effective permission set
|
|
448
|
-
so the UI can gate features
|
|
458
|
+
and team memberships so the UI can gate features and offer
|
|
459
|
+
membership-scoped filters in a single request. Only exposed via
|
|
449
460
|
``GET /users/me`` — the per-email endpoint deliberately omits
|
|
450
461
|
permissions to avoid disclosing one user's grants to another.
|
|
451
462
|
"""
|
|
452
463
|
|
|
453
464
|
permissions: list[str] = []
|
|
465
|
+
teams: list[TeamMembership] = []
|
|
454
466
|
|
|
455
467
|
|
|
456
468
|
class PasswordChangeRequest(pydantic.BaseModel):
|
|
@@ -58,7 +58,12 @@ class PolicyCreate(pydantic.BaseModel):
|
|
|
58
58
|
slug: str
|
|
59
59
|
description: str | None = None
|
|
60
60
|
category: typing.Literal[
|
|
61
|
-
'attribute',
|
|
61
|
+
'attribute',
|
|
62
|
+
'presence',
|
|
63
|
+
'link_presence',
|
|
64
|
+
'age',
|
|
65
|
+
'analysis_result',
|
|
66
|
+
'deployment_status',
|
|
62
67
|
] = 'attribute'
|
|
63
68
|
weight: int = pydantic.Field(ge=0, le=100)
|
|
64
69
|
enabled: bool = True
|
|
@@ -67,6 +72,7 @@ class PolicyCreate(pydantic.BaseModel):
|
|
|
67
72
|
attribute_name: str | None = None
|
|
68
73
|
link_slug: str | None = None
|
|
69
74
|
result_slug: str | None = None
|
|
75
|
+
environment_slug: str | None = None
|
|
70
76
|
present_score: int | None = pydantic.Field(default=None, ge=0, le=100)
|
|
71
77
|
missing_score: int | None = pydantic.Field(default=None, ge=0, le=100)
|
|
72
78
|
value_score_map: dict[str, int] | None = None
|
|
@@ -85,6 +91,7 @@ class PolicyUpdate(pydantic.BaseModel):
|
|
|
85
91
|
attribute_name: str | None = None
|
|
86
92
|
link_slug: str | None = None
|
|
87
93
|
result_slug: str | None = None
|
|
94
|
+
environment_slug: str | None = None
|
|
88
95
|
weight: int | None = pydantic.Field(default=None, ge=0, le=100)
|
|
89
96
|
enabled: bool | None = None
|
|
90
97
|
priority: int | None = None
|
|
@@ -37,8 +37,11 @@ class DatastoreStatus(pydantic.BaseModel):
|
|
|
37
37
|
status: typing.Literal['ok', 'error']
|
|
38
38
|
latency_ms: float | None = None
|
|
39
39
|
# Bytes on disk (Postgres/ClickHouse) or resident memory (Valkey,
|
|
40
|
-
# which is in-memory). ``None`` when the size probe failed.
|
|
40
|
+
# which is in-memory). ``None`` when the size probe failed. For
|
|
41
|
+
# ClickHouse this is the Imbi application database; ``total_bytes``
|
|
42
|
+
# carries the whole-server footprint (incl. ``system.*``).
|
|
41
43
|
size_bytes: int | None = None
|
|
44
|
+
total_bytes: int | None = None
|
|
42
45
|
detail: str | None = None
|
|
43
46
|
|
|
44
47
|
|
|
@@ -134,13 +137,19 @@ async def _check_postgres(db: graph.Graph) -> DatastoreStatus:
|
|
|
134
137
|
|
|
135
138
|
|
|
136
139
|
async def _check_clickhouse() -> DatastoreStatus:
|
|
137
|
-
"""Probe ClickHouse
|
|
140
|
+
"""Probe ClickHouse for the app-database and whole-server disk size.
|
|
141
|
+
|
|
142
|
+
``size`` is the active-part footprint of the connected (Imbi)
|
|
143
|
+
database; ``total`` spans every database, including ``system.*``.
|
|
144
|
+
"""
|
|
138
145
|
start = time.perf_counter()
|
|
139
146
|
try:
|
|
140
147
|
rows = await asyncio.wait_for(
|
|
141
148
|
clickhouse.query(
|
|
142
|
-
'SELECT
|
|
143
|
-
|
|
149
|
+
'SELECT'
|
|
150
|
+
' sumIf(bytes_on_disk, database = currentDatabase()) AS size,'
|
|
151
|
+
' sum(bytes_on_disk) AS total'
|
|
152
|
+
' FROM system.parts WHERE active'
|
|
144
153
|
),
|
|
145
154
|
_CHECK_TIMEOUT,
|
|
146
155
|
)
|
|
@@ -152,14 +161,16 @@ async def _check_clickhouse() -> DatastoreStatus:
|
|
|
152
161
|
status='error',
|
|
153
162
|
detail=str(err),
|
|
154
163
|
)
|
|
155
|
-
|
|
156
|
-
|
|
164
|
+
row = rows[0] if rows else {}
|
|
165
|
+
size_raw = row.get('size')
|
|
166
|
+
total_raw = row.get('total')
|
|
157
167
|
return DatastoreStatus(
|
|
158
168
|
name='ClickHouse',
|
|
159
169
|
role='Timeseries data',
|
|
160
170
|
status='ok',
|
|
161
171
|
latency_ms=_ms(start),
|
|
162
|
-
size_bytes=
|
|
172
|
+
size_bytes=int(size_raw) if size_raw is not None else None,
|
|
173
|
+
total_bytes=int(total_raw) if total_raw is not None else None,
|
|
163
174
|
)
|
|
164
175
|
|
|
165
176
|
|
|
@@ -60,8 +60,15 @@ async def sync_commits_and_tags(
|
|
|
60
60
|
valkey_client, org_slug, project_id, requested_by
|
|
61
61
|
)
|
|
62
62
|
if enqueued:
|
|
63
|
+
# Optimistic, best-effort: if the worker has already flipped the
|
|
64
|
+
# project to ``running``, that newer write must win -- so this one
|
|
65
|
+
# does not retry the concurrent-update conflict.
|
|
63
66
|
await service.set_status(
|
|
64
|
-
db,
|
|
67
|
+
db,
|
|
68
|
+
project_id,
|
|
69
|
+
status='queued',
|
|
70
|
+
requested_by=requested_by,
|
|
71
|
+
retry=False,
|
|
65
72
|
)
|
|
66
73
|
return CommitSyncEnqueueResponse(enqueued=enqueued)
|
|
67
74
|
|