kubiya-control-plane-api 0.9.15__py3-none-any.whl
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.
- control_plane_api/LICENSE +676 -0
- control_plane_api/README.md +350 -0
- control_plane_api/__init__.py +4 -0
- control_plane_api/__version__.py +8 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +121 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
- control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
- control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
- control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
- control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
- control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
- control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
- control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
- control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
- control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
- control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
- control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
- control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
- control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
- control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
- control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
- control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
- control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +384 -0
- control_plane_api/app/activities/plan_generation_activities.py +499 -0
- control_plane_api/app/activities/team_activities.py +424 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +469 -0
- control_plane_api/app/config/config_loader.py +224 -0
- control_plane_api/app/config/model_pricing.py +323 -0
- control_plane_api/app/config/storage_config.py +159 -0
- control_plane_api/app/config.py +115 -0
- control_plane_api/app/controllers/__init__.py +0 -0
- control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/environment.py +65 -0
- control_plane_api/app/lib/event_bus/__init__.py +17 -0
- control_plane_api/app/lib/event_bus/base.py +136 -0
- control_plane_api/app/lib/event_bus/manager.py +335 -0
- control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
- control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
- control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
- control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
- control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
- control_plane_api/app/lib/job_executor.py +330 -0
- control_plane_api/app/lib/kubiya_client.py +293 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/mcp_validation.py +163 -0
- control_plane_api/app/lib/nats/__init__.py +13 -0
- control_plane_api/app/lib/nats/credentials_manager.py +288 -0
- control_plane_api/app/lib/nats/listener.py +374 -0
- control_plane_api/app/lib/planning_prompt_builder.py +153 -0
- control_plane_api/app/lib/planning_tools/__init__.py +41 -0
- control_plane_api/app/lib/planning_tools/agents.py +409 -0
- control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
- control_plane_api/app/lib/planning_tools/base.py +119 -0
- control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
- control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
- control_plane_api/app/lib/planning_tools/environments.py +218 -0
- control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
- control_plane_api/app/lib/planning_tools/models.py +93 -0
- control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
- control_plane_api/app/lib/planning_tools/resources.py +242 -0
- control_plane_api/app/lib/planning_tools/teams.py +334 -0
- control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
- control_plane_api/app/lib/redis_client.py +803 -0
- control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
- control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
- control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
- control_plane_api/app/lib/storage/__init__.py +20 -0
- control_plane_api/app/lib/storage/base_provider.py +274 -0
- control_plane_api/app/lib/storage/provider_factory.py +157 -0
- control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/supabase_utils.py +138 -0
- control_plane_api/app/lib/task_planning/__init__.py +138 -0
- control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
- control_plane_api/app/lib/task_planning/agents.py +389 -0
- control_plane_api/app/lib/task_planning/cache.py +218 -0
- control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
- control_plane_api/app/lib/task_planning/helpers.py +293 -0
- control_plane_api/app/lib/task_planning/hooks.py +474 -0
- control_plane_api/app/lib/task_planning/models.py +503 -0
- control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
- control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
- control_plane_api/app/lib/task_planning/runner.py +656 -0
- control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
- control_plane_api/app/lib/task_planning/workflow.py +424 -0
- control_plane_api/app/lib/templating/__init__.py +88 -0
- control_plane_api/app/lib/templating/compiler.py +278 -0
- control_plane_api/app/lib/templating/engine.py +178 -0
- control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
- control_plane_api/app/lib/templating/parsers/base.py +96 -0
- control_plane_api/app/lib/templating/parsers/env.py +85 -0
- control_plane_api/app/lib/templating/parsers/graph.py +112 -0
- control_plane_api/app/lib/templating/parsers/secret.py +87 -0
- control_plane_api/app/lib/templating/parsers/simple.py +81 -0
- control_plane_api/app/lib/templating/resolver.py +366 -0
- control_plane_api/app/lib/templating/types.py +214 -0
- control_plane_api/app/lib/templating/validator.py +201 -0
- control_plane_api/app/lib/temporal_client.py +232 -0
- control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
- control_plane_api/app/lib/temporal_credentials_service.py +203 -0
- control_plane_api/app/lib/validation/__init__.py +24 -0
- control_plane_api/app/lib/validation/runtime_validation.py +388 -0
- control_plane_api/app/main.py +531 -0
- control_plane_api/app/middleware/__init__.py +10 -0
- control_plane_api/app/middleware/auth.py +645 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/prometheus_middleware.py +173 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +40 -0
- control_plane_api/app/models/agent.py +90 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +107 -0
- control_plane_api/app/models/auth_user.py +73 -0
- control_plane_api/app/models/context.py +161 -0
- control_plane_api/app/models/custom_integration.py +99 -0
- control_plane_api/app/models/environment.py +64 -0
- control_plane_api/app/models/execution.py +125 -0
- control_plane_api/app/models/execution_transition.py +50 -0
- control_plane_api/app/models/job.py +159 -0
- control_plane_api/app/models/llm_model.py +78 -0
- control_plane_api/app/models/orchestration.py +66 -0
- control_plane_api/app/models/plan_execution.py +102 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +61 -0
- control_plane_api/app/models/project_management.py +85 -0
- control_plane_api/app/models/session.py +29 -0
- control_plane_api/app/models/skill.py +155 -0
- control_plane_api/app/models/system_tables.py +43 -0
- control_plane_api/app/models/task_planning.py +372 -0
- control_plane_api/app/models/team.py +86 -0
- control_plane_api/app/models/trace.py +257 -0
- control_plane_api/app/models/user_profile.py +54 -0
- control_plane_api/app/models/worker.py +221 -0
- control_plane_api/app/models/workflow.py +161 -0
- control_plane_api/app/models/workspace.py +50 -0
- control_plane_api/app/observability/__init__.py +177 -0
- control_plane_api/app/observability/context_logging.py +475 -0
- control_plane_api/app/observability/decorators.py +337 -0
- control_plane_api/app/observability/local_span_processor.py +702 -0
- control_plane_api/app/observability/metrics.py +303 -0
- control_plane_api/app/observability/middleware.py +246 -0
- control_plane_api/app/observability/optional.py +115 -0
- control_plane_api/app/observability/tracing.py +382 -0
- control_plane_api/app/policies/README.md +149 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_enforcement/README.md +336 -0
- control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
- control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
- control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
- control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
- control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +382 -0
- control_plane_api/app/routers/agents_v2.py +1598 -0
- control_plane_api/app/routers/analytics.py +1310 -0
- control_plane_api/app/routers/auth.py +59 -0
- control_plane_api/app/routers/client_config.py +57 -0
- control_plane_api/app/routers/context_graph.py +561 -0
- control_plane_api/app/routers/context_manager.py +577 -0
- control_plane_api/app/routers/custom_integrations.py +490 -0
- control_plane_api/app/routers/enforcer.py +132 -0
- control_plane_api/app/routers/environment_context.py +252 -0
- control_plane_api/app/routers/environments.py +761 -0
- control_plane_api/app/routers/execution_environment.py +847 -0
- control_plane_api/app/routers/executions/__init__.py +28 -0
- control_plane_api/app/routers/executions/router.py +286 -0
- control_plane_api/app/routers/executions/services/__init__.py +22 -0
- control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
- control_plane_api/app/routers/executions/services/status_service.py +420 -0
- control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
- control_plane_api/app/routers/executions/services/worker_health.py +514 -0
- control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
- control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
- control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
- control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
- control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
- control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
- control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
- control_plane_api/app/routers/executions.py +4888 -0
- control_plane_api/app/routers/health.py +165 -0
- control_plane_api/app/routers/health_v2.py +394 -0
- control_plane_api/app/routers/integration_templates.py +496 -0
- control_plane_api/app/routers/integrations.py +287 -0
- control_plane_api/app/routers/jobs.py +1809 -0
- control_plane_api/app/routers/metrics.py +517 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +628 -0
- control_plane_api/app/routers/plan_executions.py +1481 -0
- control_plane_api/app/routers/plan_generation_async.py +304 -0
- control_plane_api/app/routers/policies.py +669 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +987 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +171 -0
- control_plane_api/app/routers/skills.py +1010 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/storage.py +456 -0
- control_plane_api/app/routers/task_planning.py +611 -0
- control_plane_api/app/routers/task_queues.py +650 -0
- control_plane_api/app/routers/team_context.py +274 -0
- control_plane_api/app/routers/teams.py +1747 -0
- control_plane_api/app/routers/templates.py +248 -0
- control_plane_api/app/routers/traces.py +571 -0
- control_plane_api/app/routers/websocket_client.py +479 -0
- control_plane_api/app/routers/websocket_executions_status.py +437 -0
- control_plane_api/app/routers/websocket_gateway.py +323 -0
- control_plane_api/app/routers/websocket_traces.py +576 -0
- control_plane_api/app/routers/worker_queues.py +2555 -0
- control_plane_api/app/routers/worker_websocket.py +419 -0
- control_plane_api/app/routers/workers.py +1004 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/__init__.py +1 -0
- control_plane_api/app/schemas/job_schemas.py +302 -0
- control_plane_api/app/schemas/mcp_schemas.py +311 -0
- control_plane_api/app/schemas/template_schemas.py +133 -0
- control_plane_api/app/schemas/trace_schemas.py +168 -0
- control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_planning_strategy.py +233 -0
- control_plane_api/app/services/agno_service.py +838 -0
- control_plane_api/app/services/claude_code_planning_service.py +203 -0
- control_plane_api/app/services/context_graph_client.py +224 -0
- control_plane_api/app/services/custom_integration_service.py +415 -0
- control_plane_api/app/services/integration_resolution_service.py +345 -0
- control_plane_api/app/services/litellm_service.py +394 -0
- control_plane_api/app/services/plan_generator.py +79 -0
- control_plane_api/app/services/planning_strategy.py +66 -0
- control_plane_api/app/services/planning_strategy_factory.py +118 -0
- control_plane_api/app/services/policy_service.py +615 -0
- control_plane_api/app/services/state_transition_service.py +755 -0
- control_plane_api/app/services/storage_service.py +593 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
- control_plane_api/app/services/trace_retention.py +354 -0
- control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
- control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
- control_plane_api/app/services/workflow_operations_service.py +611 -0
- control_plane_api/app/skills/__init__.py +100 -0
- control_plane_api/app/skills/base.py +239 -0
- control_plane_api/app/skills/builtin/__init__.py +37 -0
- control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
- control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
- control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
- control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
- control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
- control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
- control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
- control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
- control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
- control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
- control_plane_api/app/skills/builtin/docker/skill.py +104 -0
- control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
- control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
- control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
- control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
- control_plane_api/app/skills/builtin/python/__init__.py +4 -0
- control_plane_api/app/skills/builtin/python/skill.py +92 -0
- control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
- control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
- control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
- control_plane_api/app/skills/builtin/shell/skill.py +161 -0
- control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/app/skills/builtin/slack/skill.py +302 -0
- control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
- control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/config.py +63 -0
- control_plane_api/app/skills/loaders/__init__.py +14 -0
- control_plane_api/app/skills/loaders/base.py +73 -0
- control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
- control_plane_api/app/skills/registry.py +125 -0
- control_plane_api/app/utils/helpers.py +12 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +520 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/plan_generation.py +254 -0
- control_plane_api/app/workflows/team_execution.py +442 -0
- control_plane_api/scripts/seed_models.py +240 -0
- control_plane_api/scripts/validate_existing_tool_names.py +492 -0
- control_plane_api/shared/__init__.py +8 -0
- control_plane_api/shared/version.py +17 -0
- control_plane_api/test_deduplication.py +274 -0
- control_plane_api/test_executor_deduplication_e2e.py +309 -0
- control_plane_api/test_job_execution_e2e.py +283 -0
- control_plane_api/test_real_integration.py +193 -0
- control_plane_api/version.py +38 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1585 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/job_activities.py +199 -0
- control_plane_api/worker/activities/runtime_activities.py +1167 -0
- control_plane_api/worker/activities/skill_activities.py +282 -0
- control_plane_api/worker/activities/team_activities.py +479 -0
- control_plane_api/worker/agent_runtime_server.py +370 -0
- control_plane_api/worker/binary_manager.py +333 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +273 -0
- control_plane_api/worker/control_plane_client.py +1491 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/health_monitor.py +159 -0
- control_plane_api/worker/metrics.py +237 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/error_events.py +105 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +35 -0
- control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
- control_plane_api/worker/runtimes/agno/__init__.py +34 -0
- control_plane_api/worker/runtimes/agno/config.py +248 -0
- control_plane_api/worker/runtimes/agno/hooks.py +385 -0
- control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
- control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
- control_plane_api/worker/runtimes/agno/utils.py +163 -0
- control_plane_api/worker/runtimes/base.py +979 -0
- control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
- control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
- control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
- control_plane_api/worker/runtimes/claude_code/config.py +829 -0
- control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
- control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
- control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
- control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
- control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
- control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
- control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/model_utils.py +107 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_communication_tools.py +908 -0
- control_plane_api/worker/services/agent_executor.py +485 -0
- control_plane_api/worker/services/agent_executor_v2.py +793 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/code_ingestion_tools.py +465 -0
- control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
- control_plane_api/worker/services/data_visualization.py +834 -0
- control_plane_api/worker/services/event_publisher.py +531 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +365 -0
- control_plane_api/worker/services/skill_context_enhancement.py +181 -0
- control_plane_api/worker/services/skill_factory.py +471 -0
- control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
- control_plane_api/worker/services/team_executor.py +715 -0
- control_plane_api/worker/services/team_executor_v2.py +1866 -0
- control_plane_api/worker/services/tool_enforcement.py +254 -0
- control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
- control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
- control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
- control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
- control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
- control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/models.py +142 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
- control_plane_api/worker/skills/__init__.py +12 -0
- control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
- control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
- control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
- control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
- control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
- control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
- control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
- control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
- control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
- control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
- control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
- control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
- control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
- control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
- control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
- control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
- control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
- control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
- control_plane_api/worker/skills/loaders/__init__.py +5 -0
- control_plane_api/worker/skills/loaders/base.py +23 -0
- control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
- control_plane_api/worker/skills/registry.py +208 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/conftest.py +12 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
- control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
- control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
- control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
- control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
- control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
- control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
- control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
- control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
- control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
- control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
- control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +330 -0
- control_plane_api/worker/utils/environment.py +65 -0
- control_plane_api/worker/utils/error_publisher.py +260 -0
- control_plane_api/worker/utils/event_batcher.py +256 -0
- control_plane_api/worker/utils/logging_config.py +335 -0
- control_plane_api/worker/utils/logging_helper.py +326 -0
- control_plane_api/worker/utils/parameter_validator.py +120 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +665 -0
- control_plane_api/worker/utils/tool_validation.py +332 -0
- control_plane_api/worker/utils/workspace_manager.py +163 -0
- control_plane_api/worker/websocket_client.py +393 -0
- control_plane_api/worker/worker.py +1297 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +909 -0
- control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
- control_plane_api/worker/workflows/team_execution.py +611 -0
- kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
- kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
- kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
- kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
- scripts/__init__.py +1 -0
- scripts/migrations.py +39 -0
- scripts/seed_worker_queues.py +128 -0
- scripts/setup_agent_runtime.py +142 -0
- worker_internal/__init__.py +1 -0
- worker_internal/planner/__init__.py +1 -0
- worker_internal/planner/activities.py +1499 -0
- worker_internal/planner/agent_tools.py +197 -0
- worker_internal/planner/event_models.py +148 -0
- worker_internal/planner/event_publisher.py +67 -0
- worker_internal/planner/models.py +199 -0
- worker_internal/planner/retry_logic.py +134 -0
- worker_internal/planner/worker.py +300 -0
- worker_internal/planner/workflows.py +970 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Policies Router - API endpoints for policy management and enforcement.
|
|
3
|
+
|
|
4
|
+
This router provides:
|
|
5
|
+
- Policy CRUD operations (proxy to enforcer service)
|
|
6
|
+
- Policy association management (linking policies to entities)
|
|
7
|
+
- Policy inheritance resolution
|
|
8
|
+
- Policy evaluation and authorization checks
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
12
|
+
from typing import List, Optional, Dict, Any
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
import structlog
|
|
15
|
+
from sqlalchemy.orm import Session
|
|
16
|
+
|
|
17
|
+
from control_plane_api.app.database import get_db
|
|
18
|
+
from control_plane_api.app.middleware.auth import get_current_organization
|
|
19
|
+
from control_plane_api.app.lib.policy_enforcer_client import (
|
|
20
|
+
create_policy_enforcer_client,
|
|
21
|
+
PolicyEnforcerClient,
|
|
22
|
+
PolicyCreate,
|
|
23
|
+
PolicyUpdate,
|
|
24
|
+
Policy,
|
|
25
|
+
PolicyValidationError,
|
|
26
|
+
PolicyNotFoundError,
|
|
27
|
+
EnforcerConnectionError,
|
|
28
|
+
)
|
|
29
|
+
from control_plane_api.app.services.policy_service import (
|
|
30
|
+
PolicyService,
|
|
31
|
+
PolicyAssociationCreate,
|
|
32
|
+
PolicyAssociationUpdate,
|
|
33
|
+
PolicyAssociationResponse,
|
|
34
|
+
ResolvedPolicy,
|
|
35
|
+
EntityType,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = structlog.get_logger()
|
|
39
|
+
|
|
40
|
+
router = APIRouter()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ============================================================================
|
|
44
|
+
# Dependency Injection
|
|
45
|
+
# ============================================================================
|
|
46
|
+
|
|
47
|
+
async def get_policy_service(
|
|
48
|
+
request: Request,
|
|
49
|
+
organization: dict = Depends(get_current_organization),
|
|
50
|
+
db: Session = Depends(get_db)
|
|
51
|
+
) -> PolicyService:
|
|
52
|
+
"""
|
|
53
|
+
Dependency to get PolicyService with enforcer client.
|
|
54
|
+
|
|
55
|
+
Note: If ENFORCER_SERVICE_URL is not set, returns service with disabled enforcer.
|
|
56
|
+
The enforcer client uses the same authorization token from the incoming request.
|
|
57
|
+
"""
|
|
58
|
+
# Extract the authorization token and auth type from the request state (set by auth middleware)
|
|
59
|
+
auth_token = getattr(request.state, "kubiya_token", None)
|
|
60
|
+
auth_type = getattr(request.state, "kubiya_auth_type", "UserKey")
|
|
61
|
+
|
|
62
|
+
async with create_policy_enforcer_client(api_key=auth_token, auth_type=auth_type) as enforcer_client:
|
|
63
|
+
service = PolicyService(
|
|
64
|
+
organization_id=organization["id"],
|
|
65
|
+
enforcer_client=enforcer_client,
|
|
66
|
+
db=db
|
|
67
|
+
)
|
|
68
|
+
yield service
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ============================================================================
|
|
72
|
+
# Request/Response Models
|
|
73
|
+
# ============================================================================
|
|
74
|
+
|
|
75
|
+
class PolicyResponse(BaseModel):
|
|
76
|
+
"""Extended policy response with association count"""
|
|
77
|
+
id: str
|
|
78
|
+
name: str
|
|
79
|
+
description: Optional[str]
|
|
80
|
+
policy_content: Optional[str] = "" # May be None or empty in some responses
|
|
81
|
+
organization_id: str
|
|
82
|
+
enabled: bool
|
|
83
|
+
tags: List[str]
|
|
84
|
+
version: int
|
|
85
|
+
created_at: Optional[str]
|
|
86
|
+
updated_at: Optional[str]
|
|
87
|
+
created_by: Optional[str] = None
|
|
88
|
+
updated_by: Optional[str] = None
|
|
89
|
+
policy_type: str = "rego"
|
|
90
|
+
association_count: int = 0 # Number of entities using this policy
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class EvaluationRequest(BaseModel):
|
|
94
|
+
"""Request model for policy evaluation"""
|
|
95
|
+
input_data: Dict[str, Any] = Field(..., description="Input data for evaluation")
|
|
96
|
+
policy_ids: Optional[List[str]] = Field(None, description="Specific policy IDs to evaluate")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class EvaluationResponse(BaseModel):
|
|
100
|
+
"""Response model for policy evaluation"""
|
|
101
|
+
allowed: bool
|
|
102
|
+
violations: List[str]
|
|
103
|
+
policy_results: Dict[str, Dict[str, Any]] # policy_id -> result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class AuthorizationCheckRequest(BaseModel):
|
|
107
|
+
"""Request model for authorization check"""
|
|
108
|
+
action: str = Field(..., description="Action to check")
|
|
109
|
+
resource: Optional[str] = Field(None, description="Resource identifier")
|
|
110
|
+
context: Optional[Dict[str, Any]] = Field(None, description="Additional context")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class AuthorizationCheckResponse(BaseModel):
|
|
114
|
+
"""Response model for authorization check"""
|
|
115
|
+
authorized: bool
|
|
116
|
+
violations: List[str]
|
|
117
|
+
policies_evaluated: int
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class PolicyListResponse(BaseModel):
|
|
121
|
+
"""Paginated response for list policies"""
|
|
122
|
+
policies: List[PolicyResponse]
|
|
123
|
+
total: int
|
|
124
|
+
page: int
|
|
125
|
+
limit: int
|
|
126
|
+
has_more: bool
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ValidationResultResponse(BaseModel):
|
|
130
|
+
"""Response for policy validation"""
|
|
131
|
+
valid: bool
|
|
132
|
+
errors: List[str] = []
|
|
133
|
+
warnings: List[str] = []
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ============================================================================
|
|
137
|
+
# Health Check (Must be before parameterized routes)
|
|
138
|
+
# ============================================================================
|
|
139
|
+
|
|
140
|
+
@router.get("/health")
|
|
141
|
+
async def check_policy_enforcer_health(
|
|
142
|
+
service: PolicyService = Depends(get_policy_service),
|
|
143
|
+
):
|
|
144
|
+
"""
|
|
145
|
+
Check health of the policy enforcer service.
|
|
146
|
+
|
|
147
|
+
Returns connection status and configuration information.
|
|
148
|
+
"""
|
|
149
|
+
if not service.is_enabled:
|
|
150
|
+
return {
|
|
151
|
+
"enabled": False,
|
|
152
|
+
"healthy": False,
|
|
153
|
+
"message": "Policy enforcer is not configured",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
healthy = await service.enforcer_client.health_check()
|
|
158
|
+
return {
|
|
159
|
+
"enabled": True,
|
|
160
|
+
"healthy": healthy,
|
|
161
|
+
"enforcer_url": service.enforcer_client._base_url,
|
|
162
|
+
}
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error("health_check_failed", error=str(e), error_type=type(e).__name__)
|
|
165
|
+
return {
|
|
166
|
+
"enabled": True,
|
|
167
|
+
"healthy": False,
|
|
168
|
+
"error": str(e),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ============================================================================
|
|
173
|
+
# Policy CRUD Endpoints (Proxy to Enforcer Service)
|
|
174
|
+
# ============================================================================
|
|
175
|
+
|
|
176
|
+
@router.post("", response_model=PolicyResponse, status_code=status.HTTP_201_CREATED)
|
|
177
|
+
async def create_policy(
|
|
178
|
+
policy: PolicyCreate,
|
|
179
|
+
service: PolicyService = Depends(get_policy_service),
|
|
180
|
+
):
|
|
181
|
+
"""
|
|
182
|
+
Create a new OPA policy in the enforcer service.
|
|
183
|
+
|
|
184
|
+
The policy will be stored in the enforcer service and can then be
|
|
185
|
+
associated with entities (agents, teams, environments).
|
|
186
|
+
"""
|
|
187
|
+
if not service.is_enabled:
|
|
188
|
+
raise HTTPException(
|
|
189
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
190
|
+
detail="Policy enforcer is not configured. Set ENFORCER_SERVICE_URL environment variable.",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
created_policy = await service.create_policy(policy)
|
|
195
|
+
policy_dict = created_policy.model_dump()
|
|
196
|
+
# Convert datetime objects to ISO strings
|
|
197
|
+
if policy_dict.get("created_at"):
|
|
198
|
+
policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
|
|
199
|
+
if policy_dict.get("updated_at"):
|
|
200
|
+
policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
|
|
201
|
+
return PolicyResponse(
|
|
202
|
+
**policy_dict,
|
|
203
|
+
organization_id=service.organization_id,
|
|
204
|
+
association_count=0,
|
|
205
|
+
)
|
|
206
|
+
except PolicyValidationError as e:
|
|
207
|
+
error_detail = {
|
|
208
|
+
"error": str(e),
|
|
209
|
+
"code": "VALIDATION_ERROR",
|
|
210
|
+
"errors": getattr(e, 'errors', []),
|
|
211
|
+
}
|
|
212
|
+
# Include details if available
|
|
213
|
+
if hasattr(e, 'details') and e.details:
|
|
214
|
+
error_detail["details"] = e.details
|
|
215
|
+
|
|
216
|
+
logger.error(
|
|
217
|
+
"policy_creation_validation_error",
|
|
218
|
+
error=str(e),
|
|
219
|
+
errors=error_detail["errors"],
|
|
220
|
+
details=error_detail.get("details")
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
raise HTTPException(
|
|
224
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
225
|
+
detail=error_detail
|
|
226
|
+
)
|
|
227
|
+
except EnforcerConnectionError as e:
|
|
228
|
+
raise HTTPException(
|
|
229
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
230
|
+
detail={"error": str(e), "code": "SERVICE_UNAVAILABLE"}
|
|
231
|
+
)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error("create_policy_failed", error=str(e))
|
|
234
|
+
raise HTTPException(
|
|
235
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
236
|
+
detail={"error": str(e), "code": "INTERNAL_ERROR"}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@router.get("/{policy_id}", response_model=PolicyResponse)
|
|
241
|
+
async def get_policy(
|
|
242
|
+
policy_id: str,
|
|
243
|
+
service: PolicyService = Depends(get_policy_service),
|
|
244
|
+
):
|
|
245
|
+
"""Get a specific policy by ID"""
|
|
246
|
+
if not service.is_enabled:
|
|
247
|
+
raise HTTPException(
|
|
248
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
249
|
+
detail="Policy enforcer is not configured",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
policy = await service.get_policy(policy_id)
|
|
254
|
+
|
|
255
|
+
# Count associations
|
|
256
|
+
associations = service.list_associations(policy_id=policy_id)
|
|
257
|
+
|
|
258
|
+
policy_dict = policy.model_dump()
|
|
259
|
+
# Convert datetime objects to ISO strings
|
|
260
|
+
if policy_dict.get("created_at"):
|
|
261
|
+
policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
|
|
262
|
+
if policy_dict.get("updated_at"):
|
|
263
|
+
policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
|
|
264
|
+
|
|
265
|
+
return PolicyResponse(
|
|
266
|
+
**policy_dict,
|
|
267
|
+
organization_id=service.organization_id,
|
|
268
|
+
association_count=len(associations),
|
|
269
|
+
)
|
|
270
|
+
except PolicyNotFoundError:
|
|
271
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error("get_policy_failed", policy_id=policy_id, error=str(e), error_type=type(e).__name__)
|
|
274
|
+
raise HTTPException(
|
|
275
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
276
|
+
detail={"error": str(e), "code": "POLICY_FETCH_ERROR"}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@router.get("", response_model=PolicyListResponse)
|
|
281
|
+
async def list_policies(
|
|
282
|
+
page: int = 1,
|
|
283
|
+
limit: int = 20,
|
|
284
|
+
enabled: Optional[bool] = None,
|
|
285
|
+
search: Optional[str] = None,
|
|
286
|
+
service: PolicyService = Depends(get_policy_service),
|
|
287
|
+
):
|
|
288
|
+
"""
|
|
289
|
+
List all policies from the enforcer service.
|
|
290
|
+
|
|
291
|
+
Supports pagination, filtering by enabled status, and search.
|
|
292
|
+
"""
|
|
293
|
+
if not service.is_enabled:
|
|
294
|
+
return PolicyListResponse(
|
|
295
|
+
policies=[],
|
|
296
|
+
total=0,
|
|
297
|
+
page=page,
|
|
298
|
+
limit=limit,
|
|
299
|
+
has_more=False,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
policies = await service.list_policies(
|
|
303
|
+
page=page,
|
|
304
|
+
limit=limit,
|
|
305
|
+
enabled=enabled,
|
|
306
|
+
search=search,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Enhance with association counts
|
|
310
|
+
responses = []
|
|
311
|
+
for policy in policies:
|
|
312
|
+
associations = service.list_associations(policy_id=policy.id)
|
|
313
|
+
policy_dict = policy.model_dump()
|
|
314
|
+
# Ensure policy_content exists (list endpoint may not return it)
|
|
315
|
+
if not policy_dict.get("policy_content"):
|
|
316
|
+
policy_dict["policy_content"] = ""
|
|
317
|
+
# Convert datetime objects to ISO strings
|
|
318
|
+
if policy_dict.get("created_at"):
|
|
319
|
+
policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
|
|
320
|
+
if policy_dict.get("updated_at"):
|
|
321
|
+
policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
|
|
322
|
+
responses.append(
|
|
323
|
+
PolicyResponse(
|
|
324
|
+
**policy_dict,
|
|
325
|
+
organization_id=service.organization_id,
|
|
326
|
+
association_count=len(associations),
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Calculate total and has_more
|
|
331
|
+
# Note: The enforcer service list_policies returns all policies for now
|
|
332
|
+
# We'll implement proper pagination when needed
|
|
333
|
+
total = len(responses)
|
|
334
|
+
has_more = False # Since we're returning all results for now
|
|
335
|
+
|
|
336
|
+
return PolicyListResponse(
|
|
337
|
+
policies=responses,
|
|
338
|
+
total=total,
|
|
339
|
+
page=page,
|
|
340
|
+
limit=limit,
|
|
341
|
+
has_more=has_more,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@router.put("/{policy_id}", response_model=PolicyResponse)
|
|
346
|
+
async def update_policy(
|
|
347
|
+
policy_id: str,
|
|
348
|
+
update: PolicyUpdate,
|
|
349
|
+
service: PolicyService = Depends(get_policy_service),
|
|
350
|
+
):
|
|
351
|
+
"""Update an existing policy"""
|
|
352
|
+
if not service.is_enabled:
|
|
353
|
+
raise HTTPException(
|
|
354
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
355
|
+
detail="Policy enforcer is not configured",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
updated_policy = await service.update_policy(policy_id, update)
|
|
360
|
+
|
|
361
|
+
# Count associations
|
|
362
|
+
associations = service.list_associations(policy_id=policy_id)
|
|
363
|
+
|
|
364
|
+
policy_dict = updated_policy.model_dump()
|
|
365
|
+
# Convert datetime objects to ISO strings
|
|
366
|
+
if policy_dict.get("created_at"):
|
|
367
|
+
policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
|
|
368
|
+
if policy_dict.get("updated_at"):
|
|
369
|
+
policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
|
|
370
|
+
|
|
371
|
+
return PolicyResponse(
|
|
372
|
+
**policy_dict,
|
|
373
|
+
organization_id=service.organization_id,
|
|
374
|
+
association_count=len(associations),
|
|
375
|
+
)
|
|
376
|
+
except PolicyNotFoundError:
|
|
377
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
|
|
378
|
+
except PolicyValidationError as e:
|
|
379
|
+
error_detail = {
|
|
380
|
+
"error": str(e),
|
|
381
|
+
"code": "VALIDATION_ERROR",
|
|
382
|
+
"errors": getattr(e, 'errors', []),
|
|
383
|
+
}
|
|
384
|
+
# Include details if available
|
|
385
|
+
if hasattr(e, 'details') and e.details:
|
|
386
|
+
error_detail["details"] = e.details
|
|
387
|
+
|
|
388
|
+
logger.error(
|
|
389
|
+
"policy_update_validation_error",
|
|
390
|
+
policy_id=policy_id,
|
|
391
|
+
error=str(e),
|
|
392
|
+
errors=error_detail["errors"],
|
|
393
|
+
details=error_detail.get("details")
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
raise HTTPException(
|
|
397
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
398
|
+
detail=error_detail
|
|
399
|
+
)
|
|
400
|
+
except Exception as e:
|
|
401
|
+
logger.error("update_policy_failed", policy_id=policy_id, error=str(e))
|
|
402
|
+
raise HTTPException(
|
|
403
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
404
|
+
detail={"error": str(e), "code": "INTERNAL_ERROR"}
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@router.delete("/{policy_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
409
|
+
async def delete_policy(
|
|
410
|
+
policy_id: str,
|
|
411
|
+
service: PolicyService = Depends(get_policy_service),
|
|
412
|
+
):
|
|
413
|
+
"""
|
|
414
|
+
Delete a policy from the enforcer service.
|
|
415
|
+
|
|
416
|
+
This will also remove all associations with entities.
|
|
417
|
+
"""
|
|
418
|
+
if not service.is_enabled:
|
|
419
|
+
raise HTTPException(
|
|
420
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
421
|
+
detail="Policy enforcer is not configured",
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
await service.delete_policy(policy_id)
|
|
426
|
+
except PolicyNotFoundError:
|
|
427
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@router.post("/{policy_id}/validate", response_model=ValidationResultResponse)
|
|
431
|
+
async def validate_policy(
|
|
432
|
+
policy_id: str,
|
|
433
|
+
service: PolicyService = Depends(get_policy_service),
|
|
434
|
+
):
|
|
435
|
+
"""
|
|
436
|
+
Validate a policy's Rego syntax and structure.
|
|
437
|
+
|
|
438
|
+
Returns validation results with errors and warnings.
|
|
439
|
+
"""
|
|
440
|
+
if not service.is_enabled:
|
|
441
|
+
raise HTTPException(
|
|
442
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
443
|
+
detail="Policy enforcer is not configured",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
result = await service.validate_policy(policy_id)
|
|
448
|
+
return {
|
|
449
|
+
"valid": result.valid,
|
|
450
|
+
"errors": result.errors,
|
|
451
|
+
"warnings": result.warnings,
|
|
452
|
+
}
|
|
453
|
+
except PolicyNotFoundError:
|
|
454
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
|
|
455
|
+
except Exception as e:
|
|
456
|
+
logger.error("validate_policy_failed", policy_id=policy_id, error=str(e))
|
|
457
|
+
raise HTTPException(
|
|
458
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
459
|
+
detail={"error": str(e), "code": "INTERNAL_ERROR"}
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# ============================================================================
|
|
464
|
+
# Policy Association Endpoints
|
|
465
|
+
# ============================================================================
|
|
466
|
+
|
|
467
|
+
@router.post("/associations", response_model=PolicyAssociationResponse, status_code=status.HTTP_201_CREATED)
|
|
468
|
+
async def create_policy_association(
|
|
469
|
+
association: PolicyAssociationCreate,
|
|
470
|
+
request: Request,
|
|
471
|
+
service: PolicyService = Depends(get_policy_service),
|
|
472
|
+
organization: dict = Depends(get_current_organization),
|
|
473
|
+
):
|
|
474
|
+
"""
|
|
475
|
+
Create a policy association (link a policy to an entity).
|
|
476
|
+
|
|
477
|
+
Entities can be agents, teams, or environments.
|
|
478
|
+
Priority determines which policy wins in case of conflicts (higher wins).
|
|
479
|
+
"""
|
|
480
|
+
if not service.is_enabled:
|
|
481
|
+
raise HTTPException(
|
|
482
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
483
|
+
detail="Policy enforcer is not configured",
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# Extract user email from request if available
|
|
487
|
+
created_by = None
|
|
488
|
+
if hasattr(request.state, "user_email"):
|
|
489
|
+
created_by = request.state.user_email
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
return await service.create_association(association, created_by=created_by)
|
|
493
|
+
except PolicyNotFoundError:
|
|
494
|
+
raise HTTPException(
|
|
495
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
496
|
+
detail=f"Policy {association.policy_id} not found",
|
|
497
|
+
)
|
|
498
|
+
except Exception as e:
|
|
499
|
+
logger.error("create_association_failed", error=str(e))
|
|
500
|
+
raise HTTPException(
|
|
501
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
502
|
+
detail=str(e),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@router.get("/associations", response_model=List[PolicyAssociationResponse])
|
|
507
|
+
def list_policy_associations(
|
|
508
|
+
entity_type: Optional[EntityType] = None,
|
|
509
|
+
entity_id: Optional[str] = None,
|
|
510
|
+
policy_id: Optional[str] = None,
|
|
511
|
+
enabled: Optional[bool] = None,
|
|
512
|
+
service: PolicyService = Depends(get_policy_service),
|
|
513
|
+
):
|
|
514
|
+
"""
|
|
515
|
+
List policy associations with filtering.
|
|
516
|
+
|
|
517
|
+
Can filter by entity type, entity ID, policy ID, and enabled status.
|
|
518
|
+
"""
|
|
519
|
+
return service.list_associations(
|
|
520
|
+
entity_type=entity_type,
|
|
521
|
+
entity_id=entity_id,
|
|
522
|
+
policy_id=policy_id,
|
|
523
|
+
enabled=enabled,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@router.get("/associations/{association_id}", response_model=PolicyAssociationResponse)
|
|
528
|
+
def get_policy_association(
|
|
529
|
+
association_id: str,
|
|
530
|
+
service: PolicyService = Depends(get_policy_service),
|
|
531
|
+
):
|
|
532
|
+
"""Get a specific policy association by ID"""
|
|
533
|
+
association = service.get_association(association_id)
|
|
534
|
+
if not association:
|
|
535
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Association not found")
|
|
536
|
+
return association
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
@router.patch("/associations/{association_id}", response_model=PolicyAssociationResponse)
|
|
540
|
+
def update_policy_association(
|
|
541
|
+
association_id: str,
|
|
542
|
+
update: PolicyAssociationUpdate,
|
|
543
|
+
service: PolicyService = Depends(get_policy_service),
|
|
544
|
+
):
|
|
545
|
+
"""Update a policy association (e.g., enable/disable, change priority)"""
|
|
546
|
+
association = service.update_association(association_id, update)
|
|
547
|
+
if not association:
|
|
548
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Association not found")
|
|
549
|
+
return association
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@router.delete("/associations/{association_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
553
|
+
def delete_policy_association(
|
|
554
|
+
association_id: str,
|
|
555
|
+
service: PolicyService = Depends(get_policy_service),
|
|
556
|
+
):
|
|
557
|
+
"""Delete a policy association"""
|
|
558
|
+
if not service.delete_association(association_id):
|
|
559
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Association not found")
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
# ============================================================================
|
|
563
|
+
# Policy Resolution and Evaluation Endpoints
|
|
564
|
+
# ============================================================================
|
|
565
|
+
|
|
566
|
+
@router.get("/resolved/{entity_type}/{entity_id}", response_model=List[ResolvedPolicy])
|
|
567
|
+
async def resolve_entity_policies(
|
|
568
|
+
entity_type: EntityType,
|
|
569
|
+
entity_id: str,
|
|
570
|
+
include_details: bool = False,
|
|
571
|
+
service: PolicyService = Depends(get_policy_service),
|
|
572
|
+
):
|
|
573
|
+
"""
|
|
574
|
+
Resolve all policies applicable to an entity considering inheritance.
|
|
575
|
+
|
|
576
|
+
Inheritance order: environment > team > agent
|
|
577
|
+
Returns policies with source information showing where each policy comes from.
|
|
578
|
+
|
|
579
|
+
Set include_details=true to fetch full policy content from enforcer service.
|
|
580
|
+
"""
|
|
581
|
+
if not service.is_enabled:
|
|
582
|
+
return []
|
|
583
|
+
|
|
584
|
+
return await service.resolve_entity_policies(
|
|
585
|
+
entity_type=entity_type,
|
|
586
|
+
entity_id=entity_id,
|
|
587
|
+
include_details=include_details,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@router.post("/evaluate/{entity_type}/{entity_id}", response_model=EvaluationResponse)
|
|
592
|
+
async def evaluate_entity_policies(
|
|
593
|
+
entity_type: EntityType,
|
|
594
|
+
entity_id: str,
|
|
595
|
+
request: EvaluationRequest,
|
|
596
|
+
service: PolicyService = Depends(get_policy_service),
|
|
597
|
+
):
|
|
598
|
+
"""
|
|
599
|
+
Evaluate all policies for an entity against input data.
|
|
600
|
+
|
|
601
|
+
This evaluates all inherited policies and returns aggregated results.
|
|
602
|
+
"""
|
|
603
|
+
if not service.is_enabled:
|
|
604
|
+
return EvaluationResponse(
|
|
605
|
+
allowed=True,
|
|
606
|
+
violations=[],
|
|
607
|
+
policy_results={},
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
results = await service.evaluate_policies(
|
|
611
|
+
entity_type=entity_type,
|
|
612
|
+
entity_id=entity_id,
|
|
613
|
+
input_data=request.input_data,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# Aggregate results
|
|
617
|
+
allowed = all(result.allow for result in results.values())
|
|
618
|
+
all_violations = []
|
|
619
|
+
for result in results.values():
|
|
620
|
+
all_violations.extend(result.violations)
|
|
621
|
+
|
|
622
|
+
policy_results = {
|
|
623
|
+
policy_id: result.model_dump()
|
|
624
|
+
for policy_id, result in results.items()
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return EvaluationResponse(
|
|
628
|
+
allowed=allowed,
|
|
629
|
+
violations=all_violations,
|
|
630
|
+
policy_results=policy_results,
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@router.post("/check-authorization/{entity_type}/{entity_id}", response_model=AuthorizationCheckResponse)
|
|
635
|
+
async def check_entity_authorization(
|
|
636
|
+
entity_type: EntityType,
|
|
637
|
+
entity_id: str,
|
|
638
|
+
request: AuthorizationCheckRequest,
|
|
639
|
+
service: PolicyService = Depends(get_policy_service),
|
|
640
|
+
):
|
|
641
|
+
"""
|
|
642
|
+
Check if an entity is authorized to perform an action.
|
|
643
|
+
|
|
644
|
+
This is a convenience endpoint for common authorization checks.
|
|
645
|
+
It evaluates all policies and returns a simple authorized/denied response.
|
|
646
|
+
"""
|
|
647
|
+
if not service.is_enabled:
|
|
648
|
+
return AuthorizationCheckResponse(
|
|
649
|
+
authorized=True,
|
|
650
|
+
violations=[],
|
|
651
|
+
policies_evaluated=0,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
authorized, violations = await service.check_entity_authorization(
|
|
655
|
+
entity_type=entity_type,
|
|
656
|
+
entity_id=entity_id,
|
|
657
|
+
action=request.action,
|
|
658
|
+
resource=request.resource,
|
|
659
|
+
context=request.context,
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# Count policies
|
|
663
|
+
resolved = await service.resolve_entity_policies(entity_type, entity_id)
|
|
664
|
+
|
|
665
|
+
return AuthorizationCheckResponse(
|
|
666
|
+
authorized=authorized,
|
|
667
|
+
violations=violations,
|
|
668
|
+
policies_evaluated=len(resolved),
|
|
669
|
+
)
|