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.
Files changed (479) hide show
  1. control_plane_api/LICENSE +676 -0
  2. control_plane_api/README.md +350 -0
  3. control_plane_api/__init__.py +4 -0
  4. control_plane_api/__version__.py +8 -0
  5. control_plane_api/alembic/README +1 -0
  6. control_plane_api/alembic/env.py +121 -0
  7. control_plane_api/alembic/script.py.mako +28 -0
  8. control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
  9. control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
  10. control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
  11. control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
  12. control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
  13. control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
  14. control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
  15. control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
  16. control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
  17. control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
  18. control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
  19. control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
  20. control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
  21. control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
  22. control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
  23. control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
  24. control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
  25. control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
  26. control_plane_api/alembic.ini +148 -0
  27. control_plane_api/api/index.py +12 -0
  28. control_plane_api/app/__init__.py +11 -0
  29. control_plane_api/app/activities/__init__.py +20 -0
  30. control_plane_api/app/activities/agent_activities.py +384 -0
  31. control_plane_api/app/activities/plan_generation_activities.py +499 -0
  32. control_plane_api/app/activities/team_activities.py +424 -0
  33. control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
  34. control_plane_api/app/config/__init__.py +35 -0
  35. control_plane_api/app/config/api_config.py +469 -0
  36. control_plane_api/app/config/config_loader.py +224 -0
  37. control_plane_api/app/config/model_pricing.py +323 -0
  38. control_plane_api/app/config/storage_config.py +159 -0
  39. control_plane_api/app/config.py +115 -0
  40. control_plane_api/app/controllers/__init__.py +0 -0
  41. control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
  42. control_plane_api/app/database.py +135 -0
  43. control_plane_api/app/exceptions.py +408 -0
  44. control_plane_api/app/lib/__init__.py +11 -0
  45. control_plane_api/app/lib/environment.py +65 -0
  46. control_plane_api/app/lib/event_bus/__init__.py +17 -0
  47. control_plane_api/app/lib/event_bus/base.py +136 -0
  48. control_plane_api/app/lib/event_bus/manager.py +335 -0
  49. control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
  50. control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
  51. control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
  52. control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
  53. control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
  54. control_plane_api/app/lib/job_executor.py +330 -0
  55. control_plane_api/app/lib/kubiya_client.py +293 -0
  56. control_plane_api/app/lib/litellm_pricing.py +166 -0
  57. control_plane_api/app/lib/mcp_validation.py +163 -0
  58. control_plane_api/app/lib/nats/__init__.py +13 -0
  59. control_plane_api/app/lib/nats/credentials_manager.py +288 -0
  60. control_plane_api/app/lib/nats/listener.py +374 -0
  61. control_plane_api/app/lib/planning_prompt_builder.py +153 -0
  62. control_plane_api/app/lib/planning_tools/__init__.py +41 -0
  63. control_plane_api/app/lib/planning_tools/agents.py +409 -0
  64. control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
  65. control_plane_api/app/lib/planning_tools/base.py +119 -0
  66. control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
  67. control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
  68. control_plane_api/app/lib/planning_tools/environments.py +218 -0
  69. control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
  70. control_plane_api/app/lib/planning_tools/models.py +93 -0
  71. control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
  72. control_plane_api/app/lib/planning_tools/resources.py +242 -0
  73. control_plane_api/app/lib/planning_tools/teams.py +334 -0
  74. control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
  75. control_plane_api/app/lib/redis_client.py +803 -0
  76. control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
  77. control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
  78. control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
  79. control_plane_api/app/lib/storage/__init__.py +20 -0
  80. control_plane_api/app/lib/storage/base_provider.py +274 -0
  81. control_plane_api/app/lib/storage/provider_factory.py +157 -0
  82. control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
  83. control_plane_api/app/lib/supabase.py +71 -0
  84. control_plane_api/app/lib/supabase_utils.py +138 -0
  85. control_plane_api/app/lib/task_planning/__init__.py +138 -0
  86. control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
  87. control_plane_api/app/lib/task_planning/agents.py +389 -0
  88. control_plane_api/app/lib/task_planning/cache.py +218 -0
  89. control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
  90. control_plane_api/app/lib/task_planning/helpers.py +293 -0
  91. control_plane_api/app/lib/task_planning/hooks.py +474 -0
  92. control_plane_api/app/lib/task_planning/models.py +503 -0
  93. control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
  94. control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
  95. control_plane_api/app/lib/task_planning/runner.py +656 -0
  96. control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
  97. control_plane_api/app/lib/task_planning/workflow.py +424 -0
  98. control_plane_api/app/lib/templating/__init__.py +88 -0
  99. control_plane_api/app/lib/templating/compiler.py +278 -0
  100. control_plane_api/app/lib/templating/engine.py +178 -0
  101. control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
  102. control_plane_api/app/lib/templating/parsers/base.py +96 -0
  103. control_plane_api/app/lib/templating/parsers/env.py +85 -0
  104. control_plane_api/app/lib/templating/parsers/graph.py +112 -0
  105. control_plane_api/app/lib/templating/parsers/secret.py +87 -0
  106. control_plane_api/app/lib/templating/parsers/simple.py +81 -0
  107. control_plane_api/app/lib/templating/resolver.py +366 -0
  108. control_plane_api/app/lib/templating/types.py +214 -0
  109. control_plane_api/app/lib/templating/validator.py +201 -0
  110. control_plane_api/app/lib/temporal_client.py +232 -0
  111. control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
  112. control_plane_api/app/lib/temporal_credentials_service.py +203 -0
  113. control_plane_api/app/lib/validation/__init__.py +24 -0
  114. control_plane_api/app/lib/validation/runtime_validation.py +388 -0
  115. control_plane_api/app/main.py +531 -0
  116. control_plane_api/app/middleware/__init__.py +10 -0
  117. control_plane_api/app/middleware/auth.py +645 -0
  118. control_plane_api/app/middleware/exception_handler.py +267 -0
  119. control_plane_api/app/middleware/prometheus_middleware.py +173 -0
  120. control_plane_api/app/middleware/rate_limiting.py +384 -0
  121. control_plane_api/app/middleware/request_id.py +202 -0
  122. control_plane_api/app/models/__init__.py +40 -0
  123. control_plane_api/app/models/agent.py +90 -0
  124. control_plane_api/app/models/analytics.py +206 -0
  125. control_plane_api/app/models/associations.py +107 -0
  126. control_plane_api/app/models/auth_user.py +73 -0
  127. control_plane_api/app/models/context.py +161 -0
  128. control_plane_api/app/models/custom_integration.py +99 -0
  129. control_plane_api/app/models/environment.py +64 -0
  130. control_plane_api/app/models/execution.py +125 -0
  131. control_plane_api/app/models/execution_transition.py +50 -0
  132. control_plane_api/app/models/job.py +159 -0
  133. control_plane_api/app/models/llm_model.py +78 -0
  134. control_plane_api/app/models/orchestration.py +66 -0
  135. control_plane_api/app/models/plan_execution.py +102 -0
  136. control_plane_api/app/models/presence.py +49 -0
  137. control_plane_api/app/models/project.py +61 -0
  138. control_plane_api/app/models/project_management.py +85 -0
  139. control_plane_api/app/models/session.py +29 -0
  140. control_plane_api/app/models/skill.py +155 -0
  141. control_plane_api/app/models/system_tables.py +43 -0
  142. control_plane_api/app/models/task_planning.py +372 -0
  143. control_plane_api/app/models/team.py +86 -0
  144. control_plane_api/app/models/trace.py +257 -0
  145. control_plane_api/app/models/user_profile.py +54 -0
  146. control_plane_api/app/models/worker.py +221 -0
  147. control_plane_api/app/models/workflow.py +161 -0
  148. control_plane_api/app/models/workspace.py +50 -0
  149. control_plane_api/app/observability/__init__.py +177 -0
  150. control_plane_api/app/observability/context_logging.py +475 -0
  151. control_plane_api/app/observability/decorators.py +337 -0
  152. control_plane_api/app/observability/local_span_processor.py +702 -0
  153. control_plane_api/app/observability/metrics.py +303 -0
  154. control_plane_api/app/observability/middleware.py +246 -0
  155. control_plane_api/app/observability/optional.py +115 -0
  156. control_plane_api/app/observability/tracing.py +382 -0
  157. control_plane_api/app/policies/README.md +149 -0
  158. control_plane_api/app/policies/approved_users.rego +62 -0
  159. control_plane_api/app/policies/business_hours.rego +51 -0
  160. control_plane_api/app/policies/rate_limiting.rego +100 -0
  161. control_plane_api/app/policies/tool_enforcement/README.md +336 -0
  162. control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
  163. control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
  164. control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
  165. control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
  166. control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
  167. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  168. control_plane_api/app/routers/__init__.py +4 -0
  169. control_plane_api/app/routers/agents.py +382 -0
  170. control_plane_api/app/routers/agents_v2.py +1598 -0
  171. control_plane_api/app/routers/analytics.py +1310 -0
  172. control_plane_api/app/routers/auth.py +59 -0
  173. control_plane_api/app/routers/client_config.py +57 -0
  174. control_plane_api/app/routers/context_graph.py +561 -0
  175. control_plane_api/app/routers/context_manager.py +577 -0
  176. control_plane_api/app/routers/custom_integrations.py +490 -0
  177. control_plane_api/app/routers/enforcer.py +132 -0
  178. control_plane_api/app/routers/environment_context.py +252 -0
  179. control_plane_api/app/routers/environments.py +761 -0
  180. control_plane_api/app/routers/execution_environment.py +847 -0
  181. control_plane_api/app/routers/executions/__init__.py +28 -0
  182. control_plane_api/app/routers/executions/router.py +286 -0
  183. control_plane_api/app/routers/executions/services/__init__.py +22 -0
  184. control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
  185. control_plane_api/app/routers/executions/services/status_service.py +420 -0
  186. control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
  187. control_plane_api/app/routers/executions/services/worker_health.py +514 -0
  188. control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
  189. control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
  190. control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
  191. control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
  192. control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
  193. control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
  194. control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
  195. control_plane_api/app/routers/executions.py +4888 -0
  196. control_plane_api/app/routers/health.py +165 -0
  197. control_plane_api/app/routers/health_v2.py +394 -0
  198. control_plane_api/app/routers/integration_templates.py +496 -0
  199. control_plane_api/app/routers/integrations.py +287 -0
  200. control_plane_api/app/routers/jobs.py +1809 -0
  201. control_plane_api/app/routers/metrics.py +517 -0
  202. control_plane_api/app/routers/models.py +82 -0
  203. control_plane_api/app/routers/models_v2.py +628 -0
  204. control_plane_api/app/routers/plan_executions.py +1481 -0
  205. control_plane_api/app/routers/plan_generation_async.py +304 -0
  206. control_plane_api/app/routers/policies.py +669 -0
  207. control_plane_api/app/routers/presence.py +234 -0
  208. control_plane_api/app/routers/projects.py +987 -0
  209. control_plane_api/app/routers/runners.py +379 -0
  210. control_plane_api/app/routers/runtimes.py +172 -0
  211. control_plane_api/app/routers/secrets.py +171 -0
  212. control_plane_api/app/routers/skills.py +1010 -0
  213. control_plane_api/app/routers/skills_definitions.py +140 -0
  214. control_plane_api/app/routers/storage.py +456 -0
  215. control_plane_api/app/routers/task_planning.py +611 -0
  216. control_plane_api/app/routers/task_queues.py +650 -0
  217. control_plane_api/app/routers/team_context.py +274 -0
  218. control_plane_api/app/routers/teams.py +1747 -0
  219. control_plane_api/app/routers/templates.py +248 -0
  220. control_plane_api/app/routers/traces.py +571 -0
  221. control_plane_api/app/routers/websocket_client.py +479 -0
  222. control_plane_api/app/routers/websocket_executions_status.py +437 -0
  223. control_plane_api/app/routers/websocket_gateway.py +323 -0
  224. control_plane_api/app/routers/websocket_traces.py +576 -0
  225. control_plane_api/app/routers/worker_queues.py +2555 -0
  226. control_plane_api/app/routers/worker_websocket.py +419 -0
  227. control_plane_api/app/routers/workers.py +1004 -0
  228. control_plane_api/app/routers/workflows.py +204 -0
  229. control_plane_api/app/runtimes/__init__.py +6 -0
  230. control_plane_api/app/runtimes/validation.py +344 -0
  231. control_plane_api/app/schemas/__init__.py +1 -0
  232. control_plane_api/app/schemas/job_schemas.py +302 -0
  233. control_plane_api/app/schemas/mcp_schemas.py +311 -0
  234. control_plane_api/app/schemas/template_schemas.py +133 -0
  235. control_plane_api/app/schemas/trace_schemas.py +168 -0
  236. control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
  237. control_plane_api/app/services/__init__.py +1 -0
  238. control_plane_api/app/services/agno_planning_strategy.py +233 -0
  239. control_plane_api/app/services/agno_service.py +838 -0
  240. control_plane_api/app/services/claude_code_planning_service.py +203 -0
  241. control_plane_api/app/services/context_graph_client.py +224 -0
  242. control_plane_api/app/services/custom_integration_service.py +415 -0
  243. control_plane_api/app/services/integration_resolution_service.py +345 -0
  244. control_plane_api/app/services/litellm_service.py +394 -0
  245. control_plane_api/app/services/plan_generator.py +79 -0
  246. control_plane_api/app/services/planning_strategy.py +66 -0
  247. control_plane_api/app/services/planning_strategy_factory.py +118 -0
  248. control_plane_api/app/services/policy_service.py +615 -0
  249. control_plane_api/app/services/state_transition_service.py +755 -0
  250. control_plane_api/app/services/storage_service.py +593 -0
  251. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  252. control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
  253. control_plane_api/app/services/trace_retention.py +354 -0
  254. control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
  255. control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
  256. control_plane_api/app/services/workflow_operations_service.py +611 -0
  257. control_plane_api/app/skills/__init__.py +100 -0
  258. control_plane_api/app/skills/base.py +239 -0
  259. control_plane_api/app/skills/builtin/__init__.py +37 -0
  260. control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
  261. control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
  262. control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
  263. control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
  264. control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
  265. control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
  266. control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
  267. control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
  268. control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
  269. control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
  270. control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
  271. control_plane_api/app/skills/builtin/docker/skill.py +104 -0
  272. control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
  273. control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
  274. control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
  275. control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
  276. control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
  277. control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
  278. control_plane_api/app/skills/builtin/python/__init__.py +4 -0
  279. control_plane_api/app/skills/builtin/python/skill.py +92 -0
  280. control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
  281. control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
  282. control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
  283. control_plane_api/app/skills/builtin/shell/skill.py +161 -0
  284. control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
  285. control_plane_api/app/skills/builtin/slack/skill.py +302 -0
  286. control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
  287. control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
  288. control_plane_api/app/skills/business_intelligence.py +189 -0
  289. control_plane_api/app/skills/config.py +63 -0
  290. control_plane_api/app/skills/loaders/__init__.py +14 -0
  291. control_plane_api/app/skills/loaders/base.py +73 -0
  292. control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
  293. control_plane_api/app/skills/registry.py +125 -0
  294. control_plane_api/app/utils/helpers.py +12 -0
  295. control_plane_api/app/utils/workflow_executor.py +354 -0
  296. control_plane_api/app/workflows/__init__.py +11 -0
  297. control_plane_api/app/workflows/agent_execution.py +520 -0
  298. control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
  299. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  300. control_plane_api/app/workflows/plan_generation.py +254 -0
  301. control_plane_api/app/workflows/team_execution.py +442 -0
  302. control_plane_api/scripts/seed_models.py +240 -0
  303. control_plane_api/scripts/validate_existing_tool_names.py +492 -0
  304. control_plane_api/shared/__init__.py +8 -0
  305. control_plane_api/shared/version.py +17 -0
  306. control_plane_api/test_deduplication.py +274 -0
  307. control_plane_api/test_executor_deduplication_e2e.py +309 -0
  308. control_plane_api/test_job_execution_e2e.py +283 -0
  309. control_plane_api/test_real_integration.py +193 -0
  310. control_plane_api/version.py +38 -0
  311. control_plane_api/worker/__init__.py +0 -0
  312. control_plane_api/worker/activities/__init__.py +0 -0
  313. control_plane_api/worker/activities/agent_activities.py +1585 -0
  314. control_plane_api/worker/activities/approval_activities.py +234 -0
  315. control_plane_api/worker/activities/job_activities.py +199 -0
  316. control_plane_api/worker/activities/runtime_activities.py +1167 -0
  317. control_plane_api/worker/activities/skill_activities.py +282 -0
  318. control_plane_api/worker/activities/team_activities.py +479 -0
  319. control_plane_api/worker/agent_runtime_server.py +370 -0
  320. control_plane_api/worker/binary_manager.py +333 -0
  321. control_plane_api/worker/config/__init__.py +31 -0
  322. control_plane_api/worker/config/worker_config.py +273 -0
  323. control_plane_api/worker/control_plane_client.py +1491 -0
  324. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  325. control_plane_api/worker/health_monitor.py +159 -0
  326. control_plane_api/worker/metrics.py +237 -0
  327. control_plane_api/worker/models/__init__.py +1 -0
  328. control_plane_api/worker/models/error_events.py +105 -0
  329. control_plane_api/worker/models/inputs.py +89 -0
  330. control_plane_api/worker/runtimes/__init__.py +35 -0
  331. control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
  332. control_plane_api/worker/runtimes/agno/__init__.py +34 -0
  333. control_plane_api/worker/runtimes/agno/config.py +248 -0
  334. control_plane_api/worker/runtimes/agno/hooks.py +385 -0
  335. control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
  336. control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
  337. control_plane_api/worker/runtimes/agno/utils.py +163 -0
  338. control_plane_api/worker/runtimes/base.py +979 -0
  339. control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
  340. control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
  341. control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
  342. control_plane_api/worker/runtimes/claude_code/config.py +829 -0
  343. control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
  344. control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
  345. control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
  346. control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
  347. control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
  348. control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
  349. control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
  350. control_plane_api/worker/runtimes/factory.py +173 -0
  351. control_plane_api/worker/runtimes/model_utils.py +107 -0
  352. control_plane_api/worker/runtimes/validation.py +93 -0
  353. control_plane_api/worker/services/__init__.py +1 -0
  354. control_plane_api/worker/services/agent_communication_tools.py +908 -0
  355. control_plane_api/worker/services/agent_executor.py +485 -0
  356. control_plane_api/worker/services/agent_executor_v2.py +793 -0
  357. control_plane_api/worker/services/analytics_collector.py +457 -0
  358. control_plane_api/worker/services/analytics_service.py +464 -0
  359. control_plane_api/worker/services/approval_tools.py +310 -0
  360. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  361. control_plane_api/worker/services/cancellation_manager.py +177 -0
  362. control_plane_api/worker/services/code_ingestion_tools.py +465 -0
  363. control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
  364. control_plane_api/worker/services/data_visualization.py +834 -0
  365. control_plane_api/worker/services/event_publisher.py +531 -0
  366. control_plane_api/worker/services/jira_tools.py +257 -0
  367. control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
  368. control_plane_api/worker/services/runtime_analytics.py +328 -0
  369. control_plane_api/worker/services/session_service.py +365 -0
  370. control_plane_api/worker/services/skill_context_enhancement.py +181 -0
  371. control_plane_api/worker/services/skill_factory.py +471 -0
  372. control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
  373. control_plane_api/worker/services/team_executor.py +715 -0
  374. control_plane_api/worker/services/team_executor_v2.py +1866 -0
  375. control_plane_api/worker/services/tool_enforcement.py +254 -0
  376. control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
  377. control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
  378. control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
  379. control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
  380. control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
  381. control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
  382. control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
  383. control_plane_api/worker/services/workflow_executor/models.py +142 -0
  384. control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
  385. control_plane_api/worker/skills/__init__.py +12 -0
  386. control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
  387. control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
  388. control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
  389. control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
  390. control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
  391. control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
  392. control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
  393. control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
  394. control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
  395. control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
  396. control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
  397. control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
  398. control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
  399. control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
  400. control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
  401. control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
  402. control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
  403. control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
  404. control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
  405. control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
  406. control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
  407. control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
  408. control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
  409. control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
  410. control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
  411. control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
  412. control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
  413. control_plane_api/worker/skills/loaders/__init__.py +5 -0
  414. control_plane_api/worker/skills/loaders/base.py +23 -0
  415. control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
  416. control_plane_api/worker/skills/registry.py +208 -0
  417. control_plane_api/worker/tests/__init__.py +1 -0
  418. control_plane_api/worker/tests/conftest.py +12 -0
  419. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  420. control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
  421. control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
  422. control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
  423. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  424. control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
  425. control_plane_api/worker/tests/integration/__init__.py +0 -0
  426. control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
  427. control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
  428. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  429. control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
  430. control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
  431. control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
  432. control_plane_api/worker/tests/unit/__init__.py +0 -0
  433. control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
  434. control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
  435. control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
  436. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  437. control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
  438. control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
  439. control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
  440. control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
  441. control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
  442. control_plane_api/worker/utils/__init__.py +1 -0
  443. control_plane_api/worker/utils/chunk_batcher.py +330 -0
  444. control_plane_api/worker/utils/environment.py +65 -0
  445. control_plane_api/worker/utils/error_publisher.py +260 -0
  446. control_plane_api/worker/utils/event_batcher.py +256 -0
  447. control_plane_api/worker/utils/logging_config.py +335 -0
  448. control_plane_api/worker/utils/logging_helper.py +326 -0
  449. control_plane_api/worker/utils/parameter_validator.py +120 -0
  450. control_plane_api/worker/utils/retry_utils.py +60 -0
  451. control_plane_api/worker/utils/streaming_utils.py +665 -0
  452. control_plane_api/worker/utils/tool_validation.py +332 -0
  453. control_plane_api/worker/utils/workspace_manager.py +163 -0
  454. control_plane_api/worker/websocket_client.py +393 -0
  455. control_plane_api/worker/worker.py +1297 -0
  456. control_plane_api/worker/workflows/__init__.py +0 -0
  457. control_plane_api/worker/workflows/agent_execution.py +909 -0
  458. control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
  459. control_plane_api/worker/workflows/team_execution.py +611 -0
  460. kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
  461. kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
  462. kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
  463. kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
  464. kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
  465. kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
  466. scripts/__init__.py +1 -0
  467. scripts/migrations.py +39 -0
  468. scripts/seed_worker_queues.py +128 -0
  469. scripts/setup_agent_runtime.py +142 -0
  470. worker_internal/__init__.py +1 -0
  471. worker_internal/planner/__init__.py +1 -0
  472. worker_internal/planner/activities.py +1499 -0
  473. worker_internal/planner/agent_tools.py +197 -0
  474. worker_internal/planner/event_models.py +148 -0
  475. worker_internal/planner/event_publisher.py +67 -0
  476. worker_internal/planner/models.py +199 -0
  477. worker_internal/planner/retry_logic.py +134 -0
  478. worker_internal/planner/worker.py +300 -0
  479. worker_internal/planner/workflows.py +970 -0
@@ -0,0 +1,615 @@
1
+ """
2
+ Policy Service - Business logic for policy management and enforcement.
3
+
4
+ This service provides:
5
+ - Policy CRUD operations with enforcer service integration
6
+ - Policy association management (linking policies to entities)
7
+ - Policy inheritance resolution (environment > team > agent)
8
+ - Policy evaluation with pre-hook support
9
+ """
10
+
11
+ from typing import List, Optional, Dict, Any, Literal
12
+ from pydantic import BaseModel, Field
13
+ import structlog
14
+ from sqlalchemy.orm import Session
15
+ from control_plane_api.app.models.system_tables import PolicyAssociation
16
+ from control_plane_api.app.lib.policy_enforcer_client import (
17
+ PolicyEnforcerClient,
18
+ Policy,
19
+ PolicyCreate,
20
+ PolicyUpdate,
21
+ EvaluationResult,
22
+ PolicyNotFoundError,
23
+ PolicyValidationError,
24
+ )
25
+
26
+ logger = structlog.get_logger()
27
+
28
+ # Entity types for policy associations
29
+ EntityType = Literal["agent", "team", "environment"]
30
+
31
+ # Priority levels for inheritance
32
+ PRIORITY_LEVELS = {
33
+ "environment": 300,
34
+ "team": 200,
35
+ "agent": 100,
36
+ }
37
+
38
+
39
+ class PolicyAssociationCreate(BaseModel):
40
+ """Schema for creating a policy association"""
41
+ policy_id: str = Field(..., description="Policy UUID from enforcer service")
42
+ policy_name: str = Field(..., description="Policy name (cached)")
43
+ entity_type: EntityType = Field(..., description="Entity type")
44
+ entity_id: str = Field(..., description="Entity UUID")
45
+ enabled: bool = Field(default=True, description="Whether association is active")
46
+ priority: Optional[int] = Field(None, description="Custom priority (overrides default)")
47
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
48
+
49
+
50
+ class PolicyAssociationUpdate(BaseModel):
51
+ """Schema for updating a policy association"""
52
+ enabled: Optional[bool] = None
53
+ priority: Optional[int] = None
54
+ metadata: Optional[Dict[str, Any]] = None
55
+
56
+
57
+ class PolicyAssociationResponse(BaseModel):
58
+ """Response model for policy associations"""
59
+ id: str
60
+ organization_id: str
61
+ policy_id: str
62
+ policy_name: str
63
+ entity_type: str
64
+ entity_id: str
65
+ enabled: bool
66
+ priority: int
67
+ metadata: Dict[str, Any]
68
+ created_at: str
69
+ updated_at: str
70
+ created_by: Optional[str] = None
71
+
72
+
73
+ class ResolvedPolicy(BaseModel):
74
+ """Policy with inheritance information"""
75
+ policy_id: str
76
+ policy_name: str
77
+ source_type: str # Where the policy comes from
78
+ source_id: str
79
+ priority: int
80
+ enabled: bool
81
+ metadata: Dict[str, Any]
82
+ policy_details: Optional[Dict[str, Any]] = None # Full policy from enforcer
83
+
84
+
85
+ class PolicyService:
86
+ """Service for managing policies and their associations"""
87
+
88
+ def __init__(self, organization_id: str, db: Session, enforcer_client: Optional[PolicyEnforcerClient] = None):
89
+ """
90
+ Initialize policy service.
91
+
92
+ Args:
93
+ organization_id: Organization ID for multi-tenancy
94
+ db: Database session
95
+ enforcer_client: Optional policy enforcer client (if None, policy features disabled)
96
+ """
97
+ self.organization_id = organization_id
98
+ self.db = db
99
+ self.enforcer_client = enforcer_client
100
+
101
+ @property
102
+ def is_enabled(self) -> bool:
103
+ """Check if policy enforcement is enabled"""
104
+ return self.enforcer_client is not None
105
+
106
+ # ============================================================================
107
+ # Policy CRUD Operations (Proxy to Enforcer Service)
108
+ # ============================================================================
109
+
110
+ async def create_policy(self, policy: PolicyCreate) -> Policy:
111
+ """
112
+ Create a new policy in the enforcer service.
113
+
114
+ Args:
115
+ policy: Policy creation data
116
+
117
+ Returns:
118
+ Created Policy object
119
+
120
+ Raises:
121
+ RuntimeError: If enforcer client is not configured
122
+ PolicyValidationError: If policy is invalid
123
+ """
124
+ if not self.enforcer_client:
125
+ raise RuntimeError("Policy enforcer is not configured")
126
+
127
+ logger.info(
128
+ "creating_policy",
129
+ organization_id=self.organization_id,
130
+ policy_name=policy.name,
131
+ )
132
+
133
+ return await self.enforcer_client.policies.create(policy)
134
+
135
+ async def get_policy(self, policy_id: str) -> Policy:
136
+ """
137
+ Get a policy from the enforcer service.
138
+
139
+ Args:
140
+ policy_id: Policy UUID
141
+
142
+ Returns:
143
+ Policy object
144
+
145
+ Raises:
146
+ PolicyNotFoundError: If policy doesn't exist
147
+ """
148
+ if not self.enforcer_client:
149
+ raise RuntimeError("Policy enforcer is not configured")
150
+
151
+ return await self.enforcer_client.policies.get(policy_id)
152
+
153
+ async def list_policies(
154
+ self,
155
+ page: int = 1,
156
+ limit: int = 20,
157
+ enabled: Optional[bool] = None,
158
+ search: Optional[str] = None,
159
+ ) -> List[Policy]:
160
+ """
161
+ List policies from the enforcer service.
162
+
163
+ Args:
164
+ page: Page number
165
+ limit: Items per page
166
+ enabled: Filter by enabled status
167
+ search: Search term
168
+
169
+ Returns:
170
+ List of policies
171
+ """
172
+ if not self.enforcer_client:
173
+ return []
174
+
175
+ response = await self.enforcer_client.policies.list(
176
+ page=page,
177
+ limit=limit,
178
+ enabled=enabled,
179
+ search=search,
180
+ )
181
+ return response.policies
182
+
183
+ async def update_policy(self, policy_id: str, update: PolicyUpdate) -> Policy:
184
+ """
185
+ Update a policy in the enforcer service.
186
+
187
+ Args:
188
+ policy_id: Policy UUID
189
+ update: Update data
190
+
191
+ Returns:
192
+ Updated Policy object
193
+ """
194
+ if not self.enforcer_client:
195
+ raise RuntimeError("Policy enforcer is not configured")
196
+
197
+ return await self.enforcer_client.policies.update(policy_id, update)
198
+
199
+ async def delete_policy(self, policy_id: str) -> None:
200
+ """
201
+ Delete a policy from the enforcer service and remove all associations.
202
+
203
+ Args:
204
+ policy_id: Policy UUID
205
+ """
206
+ if not self.enforcer_client:
207
+ raise RuntimeError("Policy enforcer is not configured")
208
+
209
+ # Delete from enforcer service
210
+ await self.enforcer_client.policies.delete(policy_id)
211
+
212
+ # Delete all associations
213
+ self.db.query(PolicyAssociation).filter(
214
+ PolicyAssociation.organization_id == self.organization_id,
215
+ PolicyAssociation.policy_id == policy_id
216
+ ).delete()
217
+ self.db.commit()
218
+
219
+ logger.info(
220
+ "policy_deleted_with_associations",
221
+ policy_id=policy_id,
222
+ organization_id=self.organization_id,
223
+ )
224
+
225
+ # ============================================================================
226
+ # Policy Association Management
227
+ # ============================================================================
228
+
229
+ async def create_association(
230
+ self,
231
+ association: PolicyAssociationCreate,
232
+ created_by: Optional[str] = None,
233
+ ) -> PolicyAssociationResponse:
234
+ """
235
+ Create a policy association (link policy to entity).
236
+
237
+ Args:
238
+ association: Association data
239
+ created_by: Email of creator
240
+
241
+ Returns:
242
+ Created association
243
+
244
+ Raises:
245
+ PolicyNotFoundError: If policy doesn't exist
246
+ """
247
+ if not self.enforcer_client:
248
+ raise RuntimeError("Policy enforcer is not configured")
249
+
250
+ # Verify policy exists
251
+ await self.get_policy(association.policy_id)
252
+
253
+ # Determine priority (use provided or default based on entity type)
254
+ priority = association.priority or PRIORITY_LEVELS.get(association.entity_type, 100)
255
+
256
+ # Create association
257
+ policy_assoc = PolicyAssociation(
258
+ organization_id=self.organization_id,
259
+ policy_id=association.policy_id,
260
+ policy_name=association.policy_name,
261
+ entity_type=association.entity_type,
262
+ entity_id=association.entity_id,
263
+ enabled=association.enabled,
264
+ priority=priority,
265
+ metadata_=association.metadata,
266
+ created_by=created_by,
267
+ )
268
+
269
+ self.db.add(policy_assoc)
270
+ self.db.commit()
271
+ self.db.refresh(policy_assoc)
272
+
273
+ logger.info(
274
+ "policy_association_created",
275
+ policy_id=association.policy_id,
276
+ entity_type=association.entity_type,
277
+ entity_id=association.entity_id[:8],
278
+ )
279
+
280
+ return PolicyAssociationResponse(
281
+ id=str(policy_assoc.id),
282
+ organization_id=policy_assoc.organization_id,
283
+ policy_id=policy_assoc.policy_id,
284
+ policy_name=policy_assoc.policy_name,
285
+ entity_type=policy_assoc.entity_type,
286
+ entity_id=str(policy_assoc.entity_id),
287
+ enabled=policy_assoc.enabled,
288
+ priority=policy_assoc.priority,
289
+ metadata=policy_assoc.metadata_ or {},
290
+ created_at=policy_assoc.created_at.isoformat() if policy_assoc.created_at else "",
291
+ updated_at=policy_assoc.updated_at.isoformat() if policy_assoc.updated_at else "",
292
+ created_by=policy_assoc.created_by,
293
+ )
294
+
295
+ def get_association(self, association_id: str) -> Optional[PolicyAssociationResponse]:
296
+ """Get a policy association by ID"""
297
+ assoc = (
298
+ self.db.query(PolicyAssociation)
299
+ .filter(
300
+ PolicyAssociation.organization_id == self.organization_id,
301
+ PolicyAssociation.id == association_id
302
+ )
303
+ .first()
304
+ )
305
+
306
+ if assoc:
307
+ return PolicyAssociationResponse(
308
+ id=str(assoc.id),
309
+ organization_id=assoc.organization_id,
310
+ policy_id=assoc.policy_id,
311
+ policy_name=assoc.policy_name,
312
+ entity_type=assoc.entity_type,
313
+ entity_id=str(assoc.entity_id),
314
+ enabled=assoc.enabled,
315
+ priority=assoc.priority,
316
+ metadata=assoc.metadata_ or {},
317
+ created_at=assoc.created_at.isoformat() if assoc.created_at else "",
318
+ updated_at=assoc.updated_at.isoformat() if assoc.updated_at else "",
319
+ created_by=assoc.created_by,
320
+ )
321
+ return None
322
+
323
+ def list_associations(
324
+ self,
325
+ entity_type: Optional[EntityType] = None,
326
+ entity_id: Optional[str] = None,
327
+ policy_id: Optional[str] = None,
328
+ enabled: Optional[bool] = None,
329
+ ) -> List[PolicyAssociationResponse]:
330
+ """
331
+ List policy associations with filtering.
332
+
333
+ Args:
334
+ entity_type: Filter by entity type
335
+ entity_id: Filter by entity ID
336
+ policy_id: Filter by policy ID
337
+ enabled: Filter by enabled status
338
+
339
+ Returns:
340
+ List of associations
341
+ """
342
+ from sqlalchemy import desc as sqlalchemy_desc
343
+
344
+ query = self.db.query(PolicyAssociation).filter(
345
+ PolicyAssociation.organization_id == self.organization_id
346
+ )
347
+
348
+ if entity_type:
349
+ query = query.filter(PolicyAssociation.entity_type == entity_type)
350
+ if entity_id:
351
+ query = query.filter(PolicyAssociation.entity_id == entity_id)
352
+ if policy_id:
353
+ query = query.filter(PolicyAssociation.policy_id == policy_id)
354
+ if enabled is not None:
355
+ query = query.filter(PolicyAssociation.enabled == enabled)
356
+
357
+ assocs = query.order_by(sqlalchemy_desc(PolicyAssociation.priority)).all()
358
+
359
+ return [
360
+ PolicyAssociationResponse(
361
+ id=str(assoc.id),
362
+ organization_id=assoc.organization_id,
363
+ policy_id=assoc.policy_id,
364
+ policy_name=assoc.policy_name,
365
+ entity_type=assoc.entity_type,
366
+ entity_id=str(assoc.entity_id),
367
+ enabled=assoc.enabled,
368
+ priority=assoc.priority,
369
+ metadata=assoc.metadata_ or {},
370
+ created_at=assoc.created_at.isoformat() if assoc.created_at else "",
371
+ updated_at=assoc.updated_at.isoformat() if assoc.updated_at else "",
372
+ created_by=assoc.created_by,
373
+ )
374
+ for assoc in assocs
375
+ ]
376
+
377
+ def update_association(
378
+ self,
379
+ association_id: str,
380
+ update: PolicyAssociationUpdate,
381
+ ) -> Optional[PolicyAssociationResponse]:
382
+ """Update a policy association"""
383
+ assoc = (
384
+ self.db.query(PolicyAssociation)
385
+ .filter(
386
+ PolicyAssociation.organization_id == self.organization_id,
387
+ PolicyAssociation.id == association_id
388
+ )
389
+ .first()
390
+ )
391
+
392
+ if not assoc:
393
+ return None
394
+
395
+ # Apply updates
396
+ update_data = update.model_dump(exclude_none=True)
397
+ for key, value in update_data.items():
398
+ # Handle metadata field name mapping
399
+ if key == "metadata":
400
+ setattr(assoc, "metadata_", value)
401
+ else:
402
+ setattr(assoc, key, value)
403
+
404
+ self.db.commit()
405
+ self.db.refresh(assoc)
406
+
407
+ logger.info("policy_association_updated", association_id=association_id)
408
+
409
+ return PolicyAssociationResponse(
410
+ id=str(assoc.id),
411
+ organization_id=assoc.organization_id,
412
+ policy_id=assoc.policy_id,
413
+ policy_name=assoc.policy_name,
414
+ entity_type=assoc.entity_type,
415
+ entity_id=str(assoc.entity_id),
416
+ enabled=assoc.enabled,
417
+ priority=assoc.priority,
418
+ metadata=assoc.metadata_ or {},
419
+ created_at=assoc.created_at.isoformat() if assoc.created_at else "",
420
+ updated_at=assoc.updated_at.isoformat() if assoc.updated_at else "",
421
+ created_by=assoc.created_by,
422
+ )
423
+
424
+ def delete_association(self, association_id: str) -> bool:
425
+ """Delete a policy association"""
426
+ deleted_count = (
427
+ self.db.query(PolicyAssociation)
428
+ .filter(
429
+ PolicyAssociation.organization_id == self.organization_id,
430
+ PolicyAssociation.id == association_id
431
+ )
432
+ .delete()
433
+ )
434
+ self.db.commit()
435
+
436
+ if deleted_count > 0:
437
+ logger.info("policy_association_deleted", association_id=association_id)
438
+ return True
439
+ return False
440
+
441
+ # ============================================================================
442
+ # Policy Inheritance Resolution
443
+ # ============================================================================
444
+
445
+ async def resolve_entity_policies(
446
+ self,
447
+ entity_type: EntityType,
448
+ entity_id: str,
449
+ include_details: bool = False,
450
+ ) -> List[ResolvedPolicy]:
451
+ """
452
+ Resolve all policies applicable to an entity considering inheritance.
453
+
454
+ Inheritance order: environment > team > agent
455
+ Higher priority wins when same policy is defined at multiple levels.
456
+
457
+ Args:
458
+ entity_type: Entity type
459
+ entity_id: Entity UUID
460
+ include_details: Whether to fetch full policy details from enforcer
461
+
462
+ Returns:
463
+ List of resolved policies with inheritance information
464
+ """
465
+ from sqlalchemy import text
466
+
467
+ # Call PostgreSQL function for efficient resolution
468
+ result = self.db.execute(
469
+ text("SELECT * FROM resolve_entity_policies(:p_entity_type, :p_entity_id, :p_organization_id)"),
470
+ {
471
+ "p_entity_type": entity_type,
472
+ "p_entity_id": entity_id,
473
+ "p_organization_id": self.organization_id,
474
+ }
475
+ )
476
+
477
+ # Convert rows to ResolvedPolicy objects
478
+ policies = []
479
+ for row in result:
480
+ policies.append(ResolvedPolicy(
481
+ policy_id=row.policy_id,
482
+ policy_name=row.policy_name,
483
+ source_type=row.source_type,
484
+ source_id=row.source_id,
485
+ priority=row.priority,
486
+ enabled=row.enabled,
487
+ metadata=row.metadata or {},
488
+ ))
489
+
490
+ # Optionally fetch full policy details from enforcer
491
+ if include_details and self.enforcer_client:
492
+ for policy in policies:
493
+ try:
494
+ details = await self.enforcer_client.policies.get(policy.policy_id)
495
+ policy.policy_details = details.model_dump()
496
+ except PolicyNotFoundError:
497
+ logger.warning(
498
+ "policy_not_found_in_enforcer",
499
+ policy_id=policy.policy_id,
500
+ )
501
+
502
+ logger.info(
503
+ "policies_resolved",
504
+ entity_type=entity_type,
505
+ entity_id=entity_id[:8],
506
+ policy_count=len(policies),
507
+ )
508
+
509
+ return policies
510
+
511
+ # ============================================================================
512
+ # Policy Evaluation
513
+ # ============================================================================
514
+
515
+ async def evaluate_policies(
516
+ self,
517
+ entity_type: EntityType,
518
+ entity_id: str,
519
+ input_data: Dict[str, Any],
520
+ ) -> Dict[str, EvaluationResult]:
521
+ """
522
+ Evaluate all policies for an entity against input data.
523
+
524
+ Args:
525
+ entity_type: Entity type
526
+ entity_id: Entity UUID
527
+ input_data: Input data for evaluation
528
+
529
+ Returns:
530
+ Dict mapping policy_id to EvaluationResult
531
+ """
532
+ if not self.enforcer_client:
533
+ return {}
534
+
535
+ # Resolve policies
536
+ policies = await self.resolve_entity_policies(entity_type, entity_id)
537
+
538
+ # Evaluate each policy
539
+ results = {}
540
+ for policy in policies:
541
+ try:
542
+ result = await self.enforcer_client.evaluation.evaluate(
543
+ input_data=input_data,
544
+ policy_id=policy.policy_id,
545
+ )
546
+ results[policy.policy_id] = result
547
+ except Exception as e:
548
+ logger.warning(
549
+ "policy_evaluation_failed",
550
+ policy_id=policy.policy_id,
551
+ error=str(e),
552
+ )
553
+
554
+ return results
555
+
556
+ async def check_entity_authorization(
557
+ self,
558
+ entity_type: EntityType,
559
+ entity_id: str,
560
+ action: str,
561
+ resource: Optional[str] = None,
562
+ context: Optional[Dict[str, Any]] = None,
563
+ ) -> tuple[bool, List[str]]:
564
+ """
565
+ Check if an entity is authorized to perform an action.
566
+
567
+ Args:
568
+ entity_type: Entity type
569
+ entity_id: Entity UUID
570
+ action: Action to check (e.g., "execute", "create", "delete")
571
+ resource: Optional resource identifier
572
+ context: Additional context for evaluation
573
+
574
+ Returns:
575
+ Tuple of (is_authorized, violations)
576
+ """
577
+ if not self.enforcer_client:
578
+ # If enforcer is disabled, allow by default
579
+ return True, []
580
+
581
+ # Construct input for policy evaluation
582
+ input_data = {
583
+ "action": action,
584
+ "entity_type": entity_type,
585
+ "entity_id": entity_id,
586
+ "organization_id": self.organization_id,
587
+ }
588
+
589
+ if resource:
590
+ input_data["resource"] = resource
591
+ if context:
592
+ input_data.update(context)
593
+
594
+ # Evaluate all policies
595
+ eval_results = await self.evaluate_policies(entity_type, entity_id, input_data)
596
+
597
+ # Check if all policies allow the action
598
+ is_authorized = True
599
+ all_violations = []
600
+
601
+ for policy_id, result in eval_results.items():
602
+ if not result.allow:
603
+ is_authorized = False
604
+ all_violations.extend(result.violations)
605
+
606
+ logger.info(
607
+ "authorization_check",
608
+ entity_type=entity_type,
609
+ entity_id=entity_id[:8],
610
+ action=action,
611
+ authorized=is_authorized,
612
+ violation_count=len(all_violations),
613
+ )
614
+
615
+ return is_authorized, all_violations