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,579 @@
|
|
|
1
|
+
"""Integration tests for hook enforcement."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import asyncio
|
|
5
|
+
from unittest.mock import AsyncMock, Mock, MagicMock
|
|
6
|
+
from control_plane_api.worker.runtimes.agno.hooks import (
|
|
7
|
+
create_tool_hook_for_streaming,
|
|
8
|
+
create_tool_hook_with_callback,
|
|
9
|
+
)
|
|
10
|
+
from control_plane_api.worker.runtimes.claude_code.hooks import build_hooks
|
|
11
|
+
from control_plane_api.worker.services.tool_enforcement import ToolEnforcementService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def mock_control_plane():
|
|
16
|
+
"""Mock control plane client."""
|
|
17
|
+
client = Mock()
|
|
18
|
+
client.publish_event = Mock()
|
|
19
|
+
return client
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def mock_enforcer_client():
|
|
24
|
+
"""Mock enforcer client for integration tests."""
|
|
25
|
+
client = AsyncMock()
|
|
26
|
+
client.evaluation = AsyncMock()
|
|
27
|
+
return client
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def enforcement_service(mock_enforcer_client):
|
|
32
|
+
"""Create enforcement service."""
|
|
33
|
+
return ToolEnforcementService(mock_enforcer_client)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def enforcement_context():
|
|
38
|
+
"""Sample enforcement context."""
|
|
39
|
+
return {
|
|
40
|
+
"organization_id": "org-123",
|
|
41
|
+
"user_email": "test@example.com",
|
|
42
|
+
"user_id": "user-456",
|
|
43
|
+
"user_roles": ["developer"],
|
|
44
|
+
"team_id": "team-789",
|
|
45
|
+
"agent_id": "agent-abc",
|
|
46
|
+
"environment": "test",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestAgnoHookEnforcement:
|
|
51
|
+
"""Test Agno hook with enforcement integration."""
|
|
52
|
+
|
|
53
|
+
def test_streaming_hook_with_allowed_tool(
|
|
54
|
+
self, mock_control_plane, enforcement_service, enforcement_context, mock_enforcer_client
|
|
55
|
+
):
|
|
56
|
+
"""Test streaming hook when enforcement allows tool."""
|
|
57
|
+
# Mock allowed response
|
|
58
|
+
mock_enforcer_client.evaluation.enforce.return_value = {
|
|
59
|
+
"allow": True,
|
|
60
|
+
"id": "enf-123",
|
|
61
|
+
"policies": ["test_policy"],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Create hook
|
|
65
|
+
hook = create_tool_hook_for_streaming(
|
|
66
|
+
control_plane=mock_control_plane,
|
|
67
|
+
execution_id="exec-123",
|
|
68
|
+
message_id="msg-456",
|
|
69
|
+
enforcement_context=enforcement_context,
|
|
70
|
+
enforcement_service=enforcement_service,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Mock tool function
|
|
74
|
+
mock_tool = Mock(return_value="tool result")
|
|
75
|
+
|
|
76
|
+
# Execute hook
|
|
77
|
+
result = hook(name="Read", function=mock_tool, arguments={"file_path": "/tmp/test.txt"})
|
|
78
|
+
|
|
79
|
+
# Verify tool was executed
|
|
80
|
+
mock_tool.assert_called_once_with(file_path="/tmp/test.txt")
|
|
81
|
+
assert result == "tool result"
|
|
82
|
+
|
|
83
|
+
# Verify enforcement was called
|
|
84
|
+
assert mock_enforcer_client.evaluation.enforce.called
|
|
85
|
+
|
|
86
|
+
# Verify events were published
|
|
87
|
+
assert mock_control_plane.publish_event.call_count == 2 # start + complete
|
|
88
|
+
|
|
89
|
+
# Check that enforcement metadata is in events
|
|
90
|
+
calls = mock_control_plane.publish_event.call_args_list
|
|
91
|
+
start_event = calls[0][1]["data"]
|
|
92
|
+
complete_event = calls[1][1]["data"]
|
|
93
|
+
|
|
94
|
+
assert "enforcement" in start_event
|
|
95
|
+
assert "enforcement" in complete_event
|
|
96
|
+
assert complete_event["enforcement"]["enforcer"] == "allowed"
|
|
97
|
+
|
|
98
|
+
def test_streaming_hook_with_blocked_tool(
|
|
99
|
+
self, mock_control_plane, enforcement_service, enforcement_context, mock_enforcer_client
|
|
100
|
+
):
|
|
101
|
+
"""Test streaming hook when enforcement blocks tool."""
|
|
102
|
+
# Mock blocked response
|
|
103
|
+
mock_enforcer_client.evaluation.enforce.return_value = {
|
|
104
|
+
"allow": False,
|
|
105
|
+
"id": "enf-456",
|
|
106
|
+
"policies": ["production_safeguards"],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Create hook
|
|
110
|
+
hook = create_tool_hook_for_streaming(
|
|
111
|
+
control_plane=mock_control_plane,
|
|
112
|
+
execution_id="exec-123",
|
|
113
|
+
message_id="msg-456",
|
|
114
|
+
enforcement_context=enforcement_context,
|
|
115
|
+
enforcement_service=enforcement_service,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Mock tool function
|
|
119
|
+
mock_tool = Mock(return_value="original result")
|
|
120
|
+
|
|
121
|
+
# Execute hook
|
|
122
|
+
result = hook(name="Bash", function=mock_tool, arguments={"command": "rm -rf /"})
|
|
123
|
+
|
|
124
|
+
# Verify tool was STILL executed (non-blocking)
|
|
125
|
+
mock_tool.assert_called_once()
|
|
126
|
+
|
|
127
|
+
# Verify result contains violation message
|
|
128
|
+
assert "POLICY VIOLATION" in result
|
|
129
|
+
assert "Bash" in result
|
|
130
|
+
assert "original result" in result
|
|
131
|
+
|
|
132
|
+
# Verify enforcement metadata shows blocked
|
|
133
|
+
calls = mock_control_plane.publish_event.call_args_list
|
|
134
|
+
complete_event = calls[1][1]["data"]
|
|
135
|
+
assert complete_event["enforcement"]["enforcer"] == "blocked"
|
|
136
|
+
|
|
137
|
+
def test_callback_hook_with_enforcement(
|
|
138
|
+
self, enforcement_service, enforcement_context, mock_enforcer_client
|
|
139
|
+
):
|
|
140
|
+
"""Test callback hook with enforcement."""
|
|
141
|
+
# Mock allowed response
|
|
142
|
+
mock_enforcer_client.evaluation.enforce.return_value = {
|
|
143
|
+
"allow": True,
|
|
144
|
+
"id": "enf-789",
|
|
145
|
+
"policies": [],
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Mock callback
|
|
149
|
+
callback_events = []
|
|
150
|
+
def event_callback(event):
|
|
151
|
+
callback_events.append(event)
|
|
152
|
+
|
|
153
|
+
# Create hook
|
|
154
|
+
hook = create_tool_hook_with_callback(
|
|
155
|
+
execution_id="exec-123",
|
|
156
|
+
message_id="msg-456",
|
|
157
|
+
event_callback=event_callback,
|
|
158
|
+
enforcement_context=enforcement_context,
|
|
159
|
+
enforcement_service=enforcement_service,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Mock tool function
|
|
163
|
+
mock_tool = Mock(return_value="result")
|
|
164
|
+
|
|
165
|
+
# Execute hook
|
|
166
|
+
result = hook(name="Read", function=mock_tool, arguments={"file_path": "/tmp/test"})
|
|
167
|
+
|
|
168
|
+
# Verify events contain enforcement metadata
|
|
169
|
+
assert len(callback_events) == 2 # start + complete
|
|
170
|
+
assert "enforcement" in callback_events[0]
|
|
171
|
+
assert "enforcement" in callback_events[1]
|
|
172
|
+
|
|
173
|
+
def test_hook_without_enforcement_service(self, mock_control_plane):
|
|
174
|
+
"""Test hook works without enforcement service (disabled)."""
|
|
175
|
+
# Create hook WITHOUT enforcement
|
|
176
|
+
hook = create_tool_hook_for_streaming(
|
|
177
|
+
control_plane=mock_control_plane,
|
|
178
|
+
execution_id="exec-123",
|
|
179
|
+
message_id="msg-456",
|
|
180
|
+
enforcement_context=None,
|
|
181
|
+
enforcement_service=None,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Mock tool function
|
|
185
|
+
mock_tool = Mock(return_value="result")
|
|
186
|
+
|
|
187
|
+
# Execute hook
|
|
188
|
+
result = hook(name="Read", function=mock_tool, arguments={"file_path": "/tmp/test"})
|
|
189
|
+
|
|
190
|
+
# Should work normally
|
|
191
|
+
assert result == "result"
|
|
192
|
+
mock_tool.assert_called_once()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestClaudeCodeHookEnforcement:
|
|
196
|
+
"""Test Claude Code hooks with enforcement integration."""
|
|
197
|
+
|
|
198
|
+
@pytest.mark.asyncio
|
|
199
|
+
async def test_claude_hooks_with_allowed_tool(
|
|
200
|
+
self, enforcement_service, enforcement_context, mock_enforcer_client
|
|
201
|
+
):
|
|
202
|
+
"""Test Claude Code hooks when enforcement allows tool."""
|
|
203
|
+
# Mock allowed response
|
|
204
|
+
mock_enforcer_client.evaluation.enforce.return_value = {
|
|
205
|
+
"allow": True,
|
|
206
|
+
"id": "enf-123",
|
|
207
|
+
"policies": ["test_policy"],
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Mock callback
|
|
211
|
+
callback_events = []
|
|
212
|
+
def event_callback(event):
|
|
213
|
+
callback_events.append(event)
|
|
214
|
+
|
|
215
|
+
# Build hooks
|
|
216
|
+
active_tools = {}
|
|
217
|
+
started_tools = set()
|
|
218
|
+
completed_tools = set()
|
|
219
|
+
hooks = build_hooks(
|
|
220
|
+
execution_id="exec-123",
|
|
221
|
+
event_callback=event_callback,
|
|
222
|
+
active_tools=active_tools,
|
|
223
|
+
completed_tools=completed_tools,
|
|
224
|
+
started_tools=started_tools,
|
|
225
|
+
enforcement_context=enforcement_context,
|
|
226
|
+
enforcement_service=enforcement_service,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Get hooks
|
|
230
|
+
pre_hook = hooks["PreToolUse"][0].hooks[0]
|
|
231
|
+
post_hook = hooks["PostToolUse"][0].hooks[0]
|
|
232
|
+
|
|
233
|
+
# Simulate tool execution
|
|
234
|
+
input_data = {"tool_name": "Read", "tool_input": {"file_path": "/tmp/test"}}
|
|
235
|
+
tool_use_id = "tool-123"
|
|
236
|
+
tool_context = {}
|
|
237
|
+
|
|
238
|
+
# Call pre-hook
|
|
239
|
+
await pre_hook(input_data, tool_use_id, tool_context)
|
|
240
|
+
|
|
241
|
+
# Verify enforcement was called
|
|
242
|
+
assert mock_enforcer_client.evaluation.enforce.called
|
|
243
|
+
|
|
244
|
+
# Verify no violation stored in context
|
|
245
|
+
assert "enforcement_violation" not in tool_context
|
|
246
|
+
|
|
247
|
+
# Simulate tool output
|
|
248
|
+
output_data = {"tool_name": "Read", "output": "file contents"}
|
|
249
|
+
|
|
250
|
+
# Call post-hook
|
|
251
|
+
await post_hook(output_data, tool_use_id, tool_context)
|
|
252
|
+
|
|
253
|
+
# Verify output is not modified
|
|
254
|
+
assert output_data["output"] == "file contents"
|
|
255
|
+
assert "enforcement_violated" not in output_data
|
|
256
|
+
|
|
257
|
+
@pytest.mark.asyncio
|
|
258
|
+
async def test_claude_hooks_with_blocked_tool(
|
|
259
|
+
self, enforcement_service, enforcement_context, mock_enforcer_client
|
|
260
|
+
):
|
|
261
|
+
"""Test Claude Code hooks when enforcement blocks tool."""
|
|
262
|
+
# Mock blocked response
|
|
263
|
+
mock_enforcer_client.evaluation.enforce.return_value = {
|
|
264
|
+
"allow": False,
|
|
265
|
+
"id": "enf-456",
|
|
266
|
+
"policies": ["bash_validation"],
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Mock callback
|
|
270
|
+
callback_events = []
|
|
271
|
+
def event_callback(event):
|
|
272
|
+
callback_events.append(event)
|
|
273
|
+
|
|
274
|
+
# Build hooks
|
|
275
|
+
active_tools = {}
|
|
276
|
+
started_tools = set()
|
|
277
|
+
completed_tools = set()
|
|
278
|
+
hooks = build_hooks(
|
|
279
|
+
execution_id="exec-123",
|
|
280
|
+
event_callback=event_callback,
|
|
281
|
+
active_tools=active_tools,
|
|
282
|
+
completed_tools=completed_tools,
|
|
283
|
+
started_tools=started_tools,
|
|
284
|
+
enforcement_context=enforcement_context,
|
|
285
|
+
enforcement_service=enforcement_service,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Get hooks
|
|
289
|
+
pre_hook = hooks["PreToolUse"][0].hooks[0]
|
|
290
|
+
post_hook = hooks["PostToolUse"][0].hooks[0]
|
|
291
|
+
|
|
292
|
+
# Simulate tool execution
|
|
293
|
+
input_data = {"tool_name": "Bash", "tool_input": {"command": "rm -rf /"}}
|
|
294
|
+
tool_use_id = "tool-456"
|
|
295
|
+
tool_context = {}
|
|
296
|
+
|
|
297
|
+
# Call pre-hook
|
|
298
|
+
await pre_hook(input_data, tool_use_id, tool_context)
|
|
299
|
+
|
|
300
|
+
# Verify violation stored in context
|
|
301
|
+
assert "enforcement_violation" in tool_context
|
|
302
|
+
assert "enforcement_metadata" in tool_context
|
|
303
|
+
|
|
304
|
+
# Simulate tool output
|
|
305
|
+
output_data = {"tool_name": "Bash", "output": "command executed"}
|
|
306
|
+
|
|
307
|
+
# Call post-hook
|
|
308
|
+
await post_hook(output_data, tool_use_id, tool_context)
|
|
309
|
+
|
|
310
|
+
# Verify violation injected into output
|
|
311
|
+
assert "POLICY VIOLATION" in output_data["output"]
|
|
312
|
+
assert "enforcement_violated" in output_data
|
|
313
|
+
assert output_data["enforcement_violated"] is True
|
|
314
|
+
assert "command executed" in output_data["output"] # Original output preserved
|
|
315
|
+
|
|
316
|
+
@pytest.mark.asyncio
|
|
317
|
+
async def test_claude_hooks_without_enforcement(self):
|
|
318
|
+
"""Test Claude Code hooks work without enforcement service."""
|
|
319
|
+
# Mock callback
|
|
320
|
+
callback_events = []
|
|
321
|
+
def event_callback(event):
|
|
322
|
+
callback_events.append(event)
|
|
323
|
+
|
|
324
|
+
# Build hooks WITHOUT enforcement
|
|
325
|
+
active_tools = {}
|
|
326
|
+
started_tools = set()
|
|
327
|
+
completed_tools = set()
|
|
328
|
+
hooks = build_hooks(
|
|
329
|
+
execution_id="exec-123",
|
|
330
|
+
event_callback=event_callback,
|
|
331
|
+
active_tools=active_tools,
|
|
332
|
+
completed_tools=completed_tools,
|
|
333
|
+
started_tools=started_tools,
|
|
334
|
+
enforcement_context=None,
|
|
335
|
+
enforcement_service=None,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Get hooks
|
|
339
|
+
pre_hook = hooks["PreToolUse"][0].hooks[0]
|
|
340
|
+
post_hook = hooks["PostToolUse"][0].hooks[0]
|
|
341
|
+
|
|
342
|
+
# Simulate tool execution
|
|
343
|
+
input_data = {"tool_name": "Read", "tool_input": {"file_path": "/tmp/test"}}
|
|
344
|
+
tool_use_id = "tool-123"
|
|
345
|
+
tool_context = {}
|
|
346
|
+
|
|
347
|
+
# Call hooks
|
|
348
|
+
await pre_hook(input_data, tool_use_id, tool_context)
|
|
349
|
+
|
|
350
|
+
output_data = {"tool_name": "Read", "output": "file contents"}
|
|
351
|
+
await post_hook(output_data, tool_use_id, tool_context)
|
|
352
|
+
|
|
353
|
+
# Should work normally without violations
|
|
354
|
+
assert output_data["output"] == "file contents"
|
|
355
|
+
assert "enforcement_violated" not in output_data
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class TestEnforcementFailOpen:
|
|
359
|
+
"""Test fail-open behavior on errors."""
|
|
360
|
+
|
|
361
|
+
def test_agno_hook_fails_open_on_enforcer_error(
|
|
362
|
+
self, mock_control_plane, enforcement_service, enforcement_context, mock_enforcer_client
|
|
363
|
+
):
|
|
364
|
+
"""Test Agno hook fails open when enforcer has error."""
|
|
365
|
+
# Mock enforcer error
|
|
366
|
+
mock_enforcer_client.evaluation.enforce.side_effect = Exception("Enforcer down")
|
|
367
|
+
|
|
368
|
+
# Create hook
|
|
369
|
+
hook = create_tool_hook_for_streaming(
|
|
370
|
+
control_plane=mock_control_plane,
|
|
371
|
+
execution_id="exec-123",
|
|
372
|
+
message_id="msg-456",
|
|
373
|
+
enforcement_context=enforcement_context,
|
|
374
|
+
enforcement_service=enforcement_service,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Mock tool function
|
|
378
|
+
mock_tool = Mock(return_value="result")
|
|
379
|
+
|
|
380
|
+
# Execute hook - should NOT raise exception
|
|
381
|
+
result = hook(name="Read", function=mock_tool, arguments={"file_path": "/tmp/test"})
|
|
382
|
+
|
|
383
|
+
# Verify tool was executed (fail open)
|
|
384
|
+
mock_tool.assert_called_once()
|
|
385
|
+
assert result == "result"
|
|
386
|
+
|
|
387
|
+
# Verify enforcement metadata shows error
|
|
388
|
+
calls = mock_control_plane.publish_event.call_args_list
|
|
389
|
+
complete_event = calls[1][1]["data"]
|
|
390
|
+
assert "enforcement" in complete_event
|
|
391
|
+
assert complete_event["enforcement"]["enforcer"] == "error"
|
|
392
|
+
|
|
393
|
+
@pytest.mark.asyncio
|
|
394
|
+
async def test_claude_hook_fails_open_on_enforcer_error(
|
|
395
|
+
self, enforcement_service, enforcement_context, mock_enforcer_client
|
|
396
|
+
):
|
|
397
|
+
"""Test Claude Code hook fails open when enforcer has error."""
|
|
398
|
+
# Mock enforcer error
|
|
399
|
+
mock_enforcer_client.evaluation.enforce.side_effect = Exception("Enforcer down")
|
|
400
|
+
|
|
401
|
+
# Build hooks
|
|
402
|
+
active_tools = {}
|
|
403
|
+
started_tools = set()
|
|
404
|
+
completed_tools = set()
|
|
405
|
+
hooks = build_hooks(
|
|
406
|
+
execution_id="exec-123",
|
|
407
|
+
event_callback=lambda x: None,
|
|
408
|
+
active_tools=active_tools,
|
|
409
|
+
completed_tools=completed_tools,
|
|
410
|
+
started_tools=started_tools,
|
|
411
|
+
enforcement_context=enforcement_context,
|
|
412
|
+
enforcement_service=enforcement_service,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Get pre-hook
|
|
416
|
+
pre_hook = hooks["PreToolUse"][0].hooks[0]
|
|
417
|
+
|
|
418
|
+
# Simulate tool execution
|
|
419
|
+
input_data = {"tool_name": "Bash", "tool_input": {"command": "ls"}}
|
|
420
|
+
tool_use_id = "tool-123"
|
|
421
|
+
tool_context = {}
|
|
422
|
+
|
|
423
|
+
# Call pre-hook - should NOT raise exception
|
|
424
|
+
await pre_hook(input_data, tool_use_id, tool_context)
|
|
425
|
+
|
|
426
|
+
# Verify no violation stored (fail open)
|
|
427
|
+
assert "enforcement_violation" not in tool_context
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class TestToolEventDeduplication:
|
|
431
|
+
"""Test deduplication of tool start and complete events."""
|
|
432
|
+
|
|
433
|
+
@pytest.mark.asyncio
|
|
434
|
+
async def test_tool_start_deduplication(self):
|
|
435
|
+
"""Test that duplicate tool_start events are prevented."""
|
|
436
|
+
# Track events
|
|
437
|
+
callback_events = []
|
|
438
|
+
def event_callback(event):
|
|
439
|
+
callback_events.append(event)
|
|
440
|
+
|
|
441
|
+
# Build hooks
|
|
442
|
+
active_tools = {}
|
|
443
|
+
started_tools = set()
|
|
444
|
+
completed_tools = set()
|
|
445
|
+
hooks = build_hooks(
|
|
446
|
+
execution_id="exec-123",
|
|
447
|
+
event_callback=event_callback,
|
|
448
|
+
active_tools=active_tools,
|
|
449
|
+
completed_tools=completed_tools,
|
|
450
|
+
started_tools=started_tools,
|
|
451
|
+
enforcement_context=None,
|
|
452
|
+
enforcement_service=None,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Get pre-hook
|
|
456
|
+
pre_hook = hooks["PreToolUse"][0].hooks[0]
|
|
457
|
+
|
|
458
|
+
# Simulate tool execution
|
|
459
|
+
input_data = {"tool_name": "Bash", "tool_input": {"command": "echo 'hello world'"}}
|
|
460
|
+
tool_use_id = "tool-123"
|
|
461
|
+
tool_context = {}
|
|
462
|
+
|
|
463
|
+
# Call pre-hook FIRST time
|
|
464
|
+
await pre_hook(input_data, tool_use_id, tool_context)
|
|
465
|
+
|
|
466
|
+
# Verify tool_start event was published
|
|
467
|
+
assert len(callback_events) == 1
|
|
468
|
+
assert callback_events[0]["type"] == "tool_start"
|
|
469
|
+
assert callback_events[0]["tool_name"] == "Bash"
|
|
470
|
+
assert callback_events[0]["tool_execution_id"] == tool_use_id
|
|
471
|
+
|
|
472
|
+
# Verify tool_use_id added to started_tools
|
|
473
|
+
assert tool_use_id in started_tools
|
|
474
|
+
|
|
475
|
+
# Call pre-hook SECOND time with SAME tool_use_id (simulating duplicate)
|
|
476
|
+
await pre_hook(input_data, tool_use_id, tool_context)
|
|
477
|
+
|
|
478
|
+
# Verify NO additional event was published (deduplication works!)
|
|
479
|
+
assert len(callback_events) == 1, "Duplicate tool_start event was published!"
|
|
480
|
+
|
|
481
|
+
@pytest.mark.asyncio
|
|
482
|
+
async def test_tool_complete_deduplication(self):
|
|
483
|
+
"""Test that duplicate tool_complete events are prevented."""
|
|
484
|
+
# Track events
|
|
485
|
+
callback_events = []
|
|
486
|
+
def event_callback(event):
|
|
487
|
+
callback_events.append(event)
|
|
488
|
+
|
|
489
|
+
# Build hooks
|
|
490
|
+
active_tools = {}
|
|
491
|
+
started_tools = set()
|
|
492
|
+
completed_tools = set()
|
|
493
|
+
hooks = build_hooks(
|
|
494
|
+
execution_id="exec-123",
|
|
495
|
+
event_callback=event_callback,
|
|
496
|
+
active_tools=active_tools,
|
|
497
|
+
completed_tools=completed_tools,
|
|
498
|
+
started_tools=started_tools,
|
|
499
|
+
enforcement_context=None,
|
|
500
|
+
enforcement_service=None,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# Get post-hook
|
|
504
|
+
post_hook = hooks["PostToolUse"][0].hooks[0]
|
|
505
|
+
|
|
506
|
+
# Simulate tool output
|
|
507
|
+
output_data = {"tool_name": "Bash", "output": "hello world"}
|
|
508
|
+
tool_use_id = "tool-456"
|
|
509
|
+
tool_context = {}
|
|
510
|
+
|
|
511
|
+
# Call post-hook FIRST time
|
|
512
|
+
await post_hook(output_data, tool_use_id, tool_context)
|
|
513
|
+
|
|
514
|
+
# Verify tool_complete event was published
|
|
515
|
+
assert len(callback_events) == 1
|
|
516
|
+
assert callback_events[0]["type"] == "tool_complete"
|
|
517
|
+
assert callback_events[0]["tool_name"] == "Bash"
|
|
518
|
+
assert callback_events[0]["tool_execution_id"] == tool_use_id
|
|
519
|
+
|
|
520
|
+
# Verify tool_use_id added to completed_tools
|
|
521
|
+
assert tool_use_id in completed_tools
|
|
522
|
+
|
|
523
|
+
# Call post-hook SECOND time with SAME tool_use_id (simulating duplicate)
|
|
524
|
+
await post_hook(output_data, tool_use_id, tool_context)
|
|
525
|
+
|
|
526
|
+
# Verify NO additional event was published (deduplication works!)
|
|
527
|
+
assert len(callback_events) == 1, "Duplicate tool_complete event was published!"
|
|
528
|
+
|
|
529
|
+
@pytest.mark.asyncio
|
|
530
|
+
async def test_multiple_different_tools(self):
|
|
531
|
+
"""Test that different tools each get their own events."""
|
|
532
|
+
# Track events
|
|
533
|
+
callback_events = []
|
|
534
|
+
def event_callback(event):
|
|
535
|
+
callback_events.append(event)
|
|
536
|
+
|
|
537
|
+
# Build hooks
|
|
538
|
+
active_tools = {}
|
|
539
|
+
started_tools = set()
|
|
540
|
+
completed_tools = set()
|
|
541
|
+
hooks = build_hooks(
|
|
542
|
+
execution_id="exec-123",
|
|
543
|
+
event_callback=event_callback,
|
|
544
|
+
active_tools=active_tools,
|
|
545
|
+
completed_tools=completed_tools,
|
|
546
|
+
started_tools=started_tools,
|
|
547
|
+
enforcement_context=None,
|
|
548
|
+
enforcement_service=None,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Get pre-hook
|
|
552
|
+
pre_hook = hooks["PreToolUse"][0].hooks[0]
|
|
553
|
+
|
|
554
|
+
# Execute tool 1
|
|
555
|
+
input_data_1 = {"tool_name": "Bash", "tool_input": {"command": "echo 'hello'"}}
|
|
556
|
+
await pre_hook(input_data_1, "tool-1", {})
|
|
557
|
+
|
|
558
|
+
# Execute tool 2
|
|
559
|
+
input_data_2 = {"tool_name": "Read", "tool_input": {"file_path": "/tmp/test"}}
|
|
560
|
+
await pre_hook(input_data_2, "tool-2", {})
|
|
561
|
+
|
|
562
|
+
# Execute tool 3
|
|
563
|
+
input_data_3 = {"tool_name": "Write", "tool_input": {"file_path": "/tmp/out"}}
|
|
564
|
+
await pre_hook(input_data_3, "tool-3", {})
|
|
565
|
+
|
|
566
|
+
# Verify all 3 events were published
|
|
567
|
+
assert len(callback_events) == 3
|
|
568
|
+
assert callback_events[0]["tool_name"] == "Bash"
|
|
569
|
+
assert callback_events[1]["tool_name"] == "Read"
|
|
570
|
+
assert callback_events[2]["tool_name"] == "Write"
|
|
571
|
+
|
|
572
|
+
# Verify all 3 tool_use_ids are in started_tools
|
|
573
|
+
assert "tool-1" in started_tools
|
|
574
|
+
assert "tool-2" in started_tools
|
|
575
|
+
assert "tool-3" in started_tools
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
if __name__ == "__main__":
|
|
579
|
+
pytest.main([__file__, "-v", "--tb=short"])
|