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,669 @@
1
+ """
2
+ Policies Router - API endpoints for policy management and enforcement.
3
+
4
+ This router provides:
5
+ - Policy CRUD operations (proxy to enforcer service)
6
+ - Policy association management (linking policies to entities)
7
+ - Policy inheritance resolution
8
+ - Policy evaluation and authorization checks
9
+ """
10
+
11
+ from fastapi import APIRouter, Depends, HTTPException, status, Request
12
+ from typing import List, Optional, Dict, Any
13
+ from pydantic import BaseModel, Field
14
+ import structlog
15
+ from sqlalchemy.orm import Session
16
+
17
+ from control_plane_api.app.database import get_db
18
+ from control_plane_api.app.middleware.auth import get_current_organization
19
+ from control_plane_api.app.lib.policy_enforcer_client import (
20
+ create_policy_enforcer_client,
21
+ PolicyEnforcerClient,
22
+ PolicyCreate,
23
+ PolicyUpdate,
24
+ Policy,
25
+ PolicyValidationError,
26
+ PolicyNotFoundError,
27
+ EnforcerConnectionError,
28
+ )
29
+ from control_plane_api.app.services.policy_service import (
30
+ PolicyService,
31
+ PolicyAssociationCreate,
32
+ PolicyAssociationUpdate,
33
+ PolicyAssociationResponse,
34
+ ResolvedPolicy,
35
+ EntityType,
36
+ )
37
+
38
+ logger = structlog.get_logger()
39
+
40
+ router = APIRouter()
41
+
42
+
43
+ # ============================================================================
44
+ # Dependency Injection
45
+ # ============================================================================
46
+
47
+ async def get_policy_service(
48
+ request: Request,
49
+ organization: dict = Depends(get_current_organization),
50
+ db: Session = Depends(get_db)
51
+ ) -> PolicyService:
52
+ """
53
+ Dependency to get PolicyService with enforcer client.
54
+
55
+ Note: If ENFORCER_SERVICE_URL is not set, returns service with disabled enforcer.
56
+ The enforcer client uses the same authorization token from the incoming request.
57
+ """
58
+ # Extract the authorization token and auth type from the request state (set by auth middleware)
59
+ auth_token = getattr(request.state, "kubiya_token", None)
60
+ auth_type = getattr(request.state, "kubiya_auth_type", "UserKey")
61
+
62
+ async with create_policy_enforcer_client(api_key=auth_token, auth_type=auth_type) as enforcer_client:
63
+ service = PolicyService(
64
+ organization_id=organization["id"],
65
+ enforcer_client=enforcer_client,
66
+ db=db
67
+ )
68
+ yield service
69
+
70
+
71
+ # ============================================================================
72
+ # Request/Response Models
73
+ # ============================================================================
74
+
75
+ class PolicyResponse(BaseModel):
76
+ """Extended policy response with association count"""
77
+ id: str
78
+ name: str
79
+ description: Optional[str]
80
+ policy_content: Optional[str] = "" # May be None or empty in some responses
81
+ organization_id: str
82
+ enabled: bool
83
+ tags: List[str]
84
+ version: int
85
+ created_at: Optional[str]
86
+ updated_at: Optional[str]
87
+ created_by: Optional[str] = None
88
+ updated_by: Optional[str] = None
89
+ policy_type: str = "rego"
90
+ association_count: int = 0 # Number of entities using this policy
91
+
92
+
93
+ class EvaluationRequest(BaseModel):
94
+ """Request model for policy evaluation"""
95
+ input_data: Dict[str, Any] = Field(..., description="Input data for evaluation")
96
+ policy_ids: Optional[List[str]] = Field(None, description="Specific policy IDs to evaluate")
97
+
98
+
99
+ class EvaluationResponse(BaseModel):
100
+ """Response model for policy evaluation"""
101
+ allowed: bool
102
+ violations: List[str]
103
+ policy_results: Dict[str, Dict[str, Any]] # policy_id -> result
104
+
105
+
106
+ class AuthorizationCheckRequest(BaseModel):
107
+ """Request model for authorization check"""
108
+ action: str = Field(..., description="Action to check")
109
+ resource: Optional[str] = Field(None, description="Resource identifier")
110
+ context: Optional[Dict[str, Any]] = Field(None, description="Additional context")
111
+
112
+
113
+ class AuthorizationCheckResponse(BaseModel):
114
+ """Response model for authorization check"""
115
+ authorized: bool
116
+ violations: List[str]
117
+ policies_evaluated: int
118
+
119
+
120
+ class PolicyListResponse(BaseModel):
121
+ """Paginated response for list policies"""
122
+ policies: List[PolicyResponse]
123
+ total: int
124
+ page: int
125
+ limit: int
126
+ has_more: bool
127
+
128
+
129
+ class ValidationResultResponse(BaseModel):
130
+ """Response for policy validation"""
131
+ valid: bool
132
+ errors: List[str] = []
133
+ warnings: List[str] = []
134
+
135
+
136
+ # ============================================================================
137
+ # Health Check (Must be before parameterized routes)
138
+ # ============================================================================
139
+
140
+ @router.get("/health")
141
+ async def check_policy_enforcer_health(
142
+ service: PolicyService = Depends(get_policy_service),
143
+ ):
144
+ """
145
+ Check health of the policy enforcer service.
146
+
147
+ Returns connection status and configuration information.
148
+ """
149
+ if not service.is_enabled:
150
+ return {
151
+ "enabled": False,
152
+ "healthy": False,
153
+ "message": "Policy enforcer is not configured",
154
+ }
155
+
156
+ try:
157
+ healthy = await service.enforcer_client.health_check()
158
+ return {
159
+ "enabled": True,
160
+ "healthy": healthy,
161
+ "enforcer_url": service.enforcer_client._base_url,
162
+ }
163
+ except Exception as e:
164
+ logger.error("health_check_failed", error=str(e), error_type=type(e).__name__)
165
+ return {
166
+ "enabled": True,
167
+ "healthy": False,
168
+ "error": str(e),
169
+ }
170
+
171
+
172
+ # ============================================================================
173
+ # Policy CRUD Endpoints (Proxy to Enforcer Service)
174
+ # ============================================================================
175
+
176
+ @router.post("", response_model=PolicyResponse, status_code=status.HTTP_201_CREATED)
177
+ async def create_policy(
178
+ policy: PolicyCreate,
179
+ service: PolicyService = Depends(get_policy_service),
180
+ ):
181
+ """
182
+ Create a new OPA policy in the enforcer service.
183
+
184
+ The policy will be stored in the enforcer service and can then be
185
+ associated with entities (agents, teams, environments).
186
+ """
187
+ if not service.is_enabled:
188
+ raise HTTPException(
189
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
190
+ detail="Policy enforcer is not configured. Set ENFORCER_SERVICE_URL environment variable.",
191
+ )
192
+
193
+ try:
194
+ created_policy = await service.create_policy(policy)
195
+ policy_dict = created_policy.model_dump()
196
+ # Convert datetime objects to ISO strings
197
+ if policy_dict.get("created_at"):
198
+ policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
199
+ if policy_dict.get("updated_at"):
200
+ policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
201
+ return PolicyResponse(
202
+ **policy_dict,
203
+ organization_id=service.organization_id,
204
+ association_count=0,
205
+ )
206
+ except PolicyValidationError as e:
207
+ error_detail = {
208
+ "error": str(e),
209
+ "code": "VALIDATION_ERROR",
210
+ "errors": getattr(e, 'errors', []),
211
+ }
212
+ # Include details if available
213
+ if hasattr(e, 'details') and e.details:
214
+ error_detail["details"] = e.details
215
+
216
+ logger.error(
217
+ "policy_creation_validation_error",
218
+ error=str(e),
219
+ errors=error_detail["errors"],
220
+ details=error_detail.get("details")
221
+ )
222
+
223
+ raise HTTPException(
224
+ status_code=status.HTTP_400_BAD_REQUEST,
225
+ detail=error_detail
226
+ )
227
+ except EnforcerConnectionError as e:
228
+ raise HTTPException(
229
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
230
+ detail={"error": str(e), "code": "SERVICE_UNAVAILABLE"}
231
+ )
232
+ except Exception as e:
233
+ logger.error("create_policy_failed", error=str(e))
234
+ raise HTTPException(
235
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
236
+ detail={"error": str(e), "code": "INTERNAL_ERROR"}
237
+ )
238
+
239
+
240
+ @router.get("/{policy_id}", response_model=PolicyResponse)
241
+ async def get_policy(
242
+ policy_id: str,
243
+ service: PolicyService = Depends(get_policy_service),
244
+ ):
245
+ """Get a specific policy by ID"""
246
+ if not service.is_enabled:
247
+ raise HTTPException(
248
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
249
+ detail="Policy enforcer is not configured",
250
+ )
251
+
252
+ try:
253
+ policy = await service.get_policy(policy_id)
254
+
255
+ # Count associations
256
+ associations = service.list_associations(policy_id=policy_id)
257
+
258
+ policy_dict = policy.model_dump()
259
+ # Convert datetime objects to ISO strings
260
+ if policy_dict.get("created_at"):
261
+ policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
262
+ if policy_dict.get("updated_at"):
263
+ policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
264
+
265
+ return PolicyResponse(
266
+ **policy_dict,
267
+ organization_id=service.organization_id,
268
+ association_count=len(associations),
269
+ )
270
+ except PolicyNotFoundError:
271
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
272
+ except Exception as e:
273
+ logger.error("get_policy_failed", policy_id=policy_id, error=str(e), error_type=type(e).__name__)
274
+ raise HTTPException(
275
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
276
+ detail={"error": str(e), "code": "POLICY_FETCH_ERROR"}
277
+ )
278
+
279
+
280
+ @router.get("", response_model=PolicyListResponse)
281
+ async def list_policies(
282
+ page: int = 1,
283
+ limit: int = 20,
284
+ enabled: Optional[bool] = None,
285
+ search: Optional[str] = None,
286
+ service: PolicyService = Depends(get_policy_service),
287
+ ):
288
+ """
289
+ List all policies from the enforcer service.
290
+
291
+ Supports pagination, filtering by enabled status, and search.
292
+ """
293
+ if not service.is_enabled:
294
+ return PolicyListResponse(
295
+ policies=[],
296
+ total=0,
297
+ page=page,
298
+ limit=limit,
299
+ has_more=False,
300
+ )
301
+
302
+ policies = await service.list_policies(
303
+ page=page,
304
+ limit=limit,
305
+ enabled=enabled,
306
+ search=search,
307
+ )
308
+
309
+ # Enhance with association counts
310
+ responses = []
311
+ for policy in policies:
312
+ associations = service.list_associations(policy_id=policy.id)
313
+ policy_dict = policy.model_dump()
314
+ # Ensure policy_content exists (list endpoint may not return it)
315
+ if not policy_dict.get("policy_content"):
316
+ policy_dict["policy_content"] = ""
317
+ # Convert datetime objects to ISO strings
318
+ if policy_dict.get("created_at"):
319
+ policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
320
+ if policy_dict.get("updated_at"):
321
+ policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
322
+ responses.append(
323
+ PolicyResponse(
324
+ **policy_dict,
325
+ organization_id=service.organization_id,
326
+ association_count=len(associations),
327
+ )
328
+ )
329
+
330
+ # Calculate total and has_more
331
+ # Note: The enforcer service list_policies returns all policies for now
332
+ # We'll implement proper pagination when needed
333
+ total = len(responses)
334
+ has_more = False # Since we're returning all results for now
335
+
336
+ return PolicyListResponse(
337
+ policies=responses,
338
+ total=total,
339
+ page=page,
340
+ limit=limit,
341
+ has_more=has_more,
342
+ )
343
+
344
+
345
+ @router.put("/{policy_id}", response_model=PolicyResponse)
346
+ async def update_policy(
347
+ policy_id: str,
348
+ update: PolicyUpdate,
349
+ service: PolicyService = Depends(get_policy_service),
350
+ ):
351
+ """Update an existing policy"""
352
+ if not service.is_enabled:
353
+ raise HTTPException(
354
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
355
+ detail="Policy enforcer is not configured",
356
+ )
357
+
358
+ try:
359
+ updated_policy = await service.update_policy(policy_id, update)
360
+
361
+ # Count associations
362
+ associations = service.list_associations(policy_id=policy_id)
363
+
364
+ policy_dict = updated_policy.model_dump()
365
+ # Convert datetime objects to ISO strings
366
+ if policy_dict.get("created_at"):
367
+ policy_dict["created_at"] = policy_dict["created_at"].isoformat() if hasattr(policy_dict["created_at"], "isoformat") else str(policy_dict["created_at"])
368
+ if policy_dict.get("updated_at"):
369
+ policy_dict["updated_at"] = policy_dict["updated_at"].isoformat() if hasattr(policy_dict["updated_at"], "isoformat") else str(policy_dict["updated_at"])
370
+
371
+ return PolicyResponse(
372
+ **policy_dict,
373
+ organization_id=service.organization_id,
374
+ association_count=len(associations),
375
+ )
376
+ except PolicyNotFoundError:
377
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
378
+ except PolicyValidationError as e:
379
+ error_detail = {
380
+ "error": str(e),
381
+ "code": "VALIDATION_ERROR",
382
+ "errors": getattr(e, 'errors', []),
383
+ }
384
+ # Include details if available
385
+ if hasattr(e, 'details') and e.details:
386
+ error_detail["details"] = e.details
387
+
388
+ logger.error(
389
+ "policy_update_validation_error",
390
+ policy_id=policy_id,
391
+ error=str(e),
392
+ errors=error_detail["errors"],
393
+ details=error_detail.get("details")
394
+ )
395
+
396
+ raise HTTPException(
397
+ status_code=status.HTTP_400_BAD_REQUEST,
398
+ detail=error_detail
399
+ )
400
+ except Exception as e:
401
+ logger.error("update_policy_failed", policy_id=policy_id, error=str(e))
402
+ raise HTTPException(
403
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
404
+ detail={"error": str(e), "code": "INTERNAL_ERROR"}
405
+ )
406
+
407
+
408
+ @router.delete("/{policy_id}", status_code=status.HTTP_204_NO_CONTENT)
409
+ async def delete_policy(
410
+ policy_id: str,
411
+ service: PolicyService = Depends(get_policy_service),
412
+ ):
413
+ """
414
+ Delete a policy from the enforcer service.
415
+
416
+ This will also remove all associations with entities.
417
+ """
418
+ if not service.is_enabled:
419
+ raise HTTPException(
420
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
421
+ detail="Policy enforcer is not configured",
422
+ )
423
+
424
+ try:
425
+ await service.delete_policy(policy_id)
426
+ except PolicyNotFoundError:
427
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
428
+
429
+
430
+ @router.post("/{policy_id}/validate", response_model=ValidationResultResponse)
431
+ async def validate_policy(
432
+ policy_id: str,
433
+ service: PolicyService = Depends(get_policy_service),
434
+ ):
435
+ """
436
+ Validate a policy's Rego syntax and structure.
437
+
438
+ Returns validation results with errors and warnings.
439
+ """
440
+ if not service.is_enabled:
441
+ raise HTTPException(
442
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
443
+ detail="Policy enforcer is not configured",
444
+ )
445
+
446
+ try:
447
+ result = await service.validate_policy(policy_id)
448
+ return {
449
+ "valid": result.valid,
450
+ "errors": result.errors,
451
+ "warnings": result.warnings,
452
+ }
453
+ except PolicyNotFoundError:
454
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Policy not found")
455
+ except Exception as e:
456
+ logger.error("validate_policy_failed", policy_id=policy_id, error=str(e))
457
+ raise HTTPException(
458
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
459
+ detail={"error": str(e), "code": "INTERNAL_ERROR"}
460
+ )
461
+
462
+
463
+ # ============================================================================
464
+ # Policy Association Endpoints
465
+ # ============================================================================
466
+
467
+ @router.post("/associations", response_model=PolicyAssociationResponse, status_code=status.HTTP_201_CREATED)
468
+ async def create_policy_association(
469
+ association: PolicyAssociationCreate,
470
+ request: Request,
471
+ service: PolicyService = Depends(get_policy_service),
472
+ organization: dict = Depends(get_current_organization),
473
+ ):
474
+ """
475
+ Create a policy association (link a policy to an entity).
476
+
477
+ Entities can be agents, teams, or environments.
478
+ Priority determines which policy wins in case of conflicts (higher wins).
479
+ """
480
+ if not service.is_enabled:
481
+ raise HTTPException(
482
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
483
+ detail="Policy enforcer is not configured",
484
+ )
485
+
486
+ # Extract user email from request if available
487
+ created_by = None
488
+ if hasattr(request.state, "user_email"):
489
+ created_by = request.state.user_email
490
+
491
+ try:
492
+ return await service.create_association(association, created_by=created_by)
493
+ except PolicyNotFoundError:
494
+ raise HTTPException(
495
+ status_code=status.HTTP_404_NOT_FOUND,
496
+ detail=f"Policy {association.policy_id} not found",
497
+ )
498
+ except Exception as e:
499
+ logger.error("create_association_failed", error=str(e))
500
+ raise HTTPException(
501
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
502
+ detail=str(e),
503
+ )
504
+
505
+
506
+ @router.get("/associations", response_model=List[PolicyAssociationResponse])
507
+ def list_policy_associations(
508
+ entity_type: Optional[EntityType] = None,
509
+ entity_id: Optional[str] = None,
510
+ policy_id: Optional[str] = None,
511
+ enabled: Optional[bool] = None,
512
+ service: PolicyService = Depends(get_policy_service),
513
+ ):
514
+ """
515
+ List policy associations with filtering.
516
+
517
+ Can filter by entity type, entity ID, policy ID, and enabled status.
518
+ """
519
+ return service.list_associations(
520
+ entity_type=entity_type,
521
+ entity_id=entity_id,
522
+ policy_id=policy_id,
523
+ enabled=enabled,
524
+ )
525
+
526
+
527
+ @router.get("/associations/{association_id}", response_model=PolicyAssociationResponse)
528
+ def get_policy_association(
529
+ association_id: str,
530
+ service: PolicyService = Depends(get_policy_service),
531
+ ):
532
+ """Get a specific policy association by ID"""
533
+ association = service.get_association(association_id)
534
+ if not association:
535
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Association not found")
536
+ return association
537
+
538
+
539
+ @router.patch("/associations/{association_id}", response_model=PolicyAssociationResponse)
540
+ def update_policy_association(
541
+ association_id: str,
542
+ update: PolicyAssociationUpdate,
543
+ service: PolicyService = Depends(get_policy_service),
544
+ ):
545
+ """Update a policy association (e.g., enable/disable, change priority)"""
546
+ association = service.update_association(association_id, update)
547
+ if not association:
548
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Association not found")
549
+ return association
550
+
551
+
552
+ @router.delete("/associations/{association_id}", status_code=status.HTTP_204_NO_CONTENT)
553
+ def delete_policy_association(
554
+ association_id: str,
555
+ service: PolicyService = Depends(get_policy_service),
556
+ ):
557
+ """Delete a policy association"""
558
+ if not service.delete_association(association_id):
559
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Association not found")
560
+
561
+
562
+ # ============================================================================
563
+ # Policy Resolution and Evaluation Endpoints
564
+ # ============================================================================
565
+
566
+ @router.get("/resolved/{entity_type}/{entity_id}", response_model=List[ResolvedPolicy])
567
+ async def resolve_entity_policies(
568
+ entity_type: EntityType,
569
+ entity_id: str,
570
+ include_details: bool = False,
571
+ service: PolicyService = Depends(get_policy_service),
572
+ ):
573
+ """
574
+ Resolve all policies applicable to an entity considering inheritance.
575
+
576
+ Inheritance order: environment > team > agent
577
+ Returns policies with source information showing where each policy comes from.
578
+
579
+ Set include_details=true to fetch full policy content from enforcer service.
580
+ """
581
+ if not service.is_enabled:
582
+ return []
583
+
584
+ return await service.resolve_entity_policies(
585
+ entity_type=entity_type,
586
+ entity_id=entity_id,
587
+ include_details=include_details,
588
+ )
589
+
590
+
591
+ @router.post("/evaluate/{entity_type}/{entity_id}", response_model=EvaluationResponse)
592
+ async def evaluate_entity_policies(
593
+ entity_type: EntityType,
594
+ entity_id: str,
595
+ request: EvaluationRequest,
596
+ service: PolicyService = Depends(get_policy_service),
597
+ ):
598
+ """
599
+ Evaluate all policies for an entity against input data.
600
+
601
+ This evaluates all inherited policies and returns aggregated results.
602
+ """
603
+ if not service.is_enabled:
604
+ return EvaluationResponse(
605
+ allowed=True,
606
+ violations=[],
607
+ policy_results={},
608
+ )
609
+
610
+ results = await service.evaluate_policies(
611
+ entity_type=entity_type,
612
+ entity_id=entity_id,
613
+ input_data=request.input_data,
614
+ )
615
+
616
+ # Aggregate results
617
+ allowed = all(result.allow for result in results.values())
618
+ all_violations = []
619
+ for result in results.values():
620
+ all_violations.extend(result.violations)
621
+
622
+ policy_results = {
623
+ policy_id: result.model_dump()
624
+ for policy_id, result in results.items()
625
+ }
626
+
627
+ return EvaluationResponse(
628
+ allowed=allowed,
629
+ violations=all_violations,
630
+ policy_results=policy_results,
631
+ )
632
+
633
+
634
+ @router.post("/check-authorization/{entity_type}/{entity_id}", response_model=AuthorizationCheckResponse)
635
+ async def check_entity_authorization(
636
+ entity_type: EntityType,
637
+ entity_id: str,
638
+ request: AuthorizationCheckRequest,
639
+ service: PolicyService = Depends(get_policy_service),
640
+ ):
641
+ """
642
+ Check if an entity is authorized to perform an action.
643
+
644
+ This is a convenience endpoint for common authorization checks.
645
+ It evaluates all policies and returns a simple authorized/denied response.
646
+ """
647
+ if not service.is_enabled:
648
+ return AuthorizationCheckResponse(
649
+ authorized=True,
650
+ violations=[],
651
+ policies_evaluated=0,
652
+ )
653
+
654
+ authorized, violations = await service.check_entity_authorization(
655
+ entity_type=entity_type,
656
+ entity_id=entity_id,
657
+ action=request.action,
658
+ resource=request.resource,
659
+ context=request.context,
660
+ )
661
+
662
+ # Count policies
663
+ resolved = await service.resolve_entity_policies(entity_type, entity_id)
664
+
665
+ return AuthorizationCheckResponse(
666
+ authorized=authorized,
667
+ violations=violations,
668
+ policies_evaluated=len(resolved),
669
+ )