diagram-to-iac 0.6.0__py3-none-any.whl → 0.8.0__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 (77) hide show
  1. diagram_to_iac/__init__.py +10 -0
  2. diagram_to_iac/actions/__init__.py +7 -0
  3. diagram_to_iac/actions/git_entry.py +174 -0
  4. diagram_to_iac/actions/supervisor_entry.py +116 -0
  5. diagram_to_iac/actions/terraform_agent_entry.py +207 -0
  6. diagram_to_iac/agents/__init__.py +26 -0
  7. diagram_to_iac/agents/demonstrator_langgraph/__init__.py +10 -0
  8. diagram_to_iac/agents/demonstrator_langgraph/agent.py +826 -0
  9. diagram_to_iac/agents/git_langgraph/__init__.py +10 -0
  10. diagram_to_iac/agents/git_langgraph/agent.py +1018 -0
  11. diagram_to_iac/agents/git_langgraph/pr.py +146 -0
  12. diagram_to_iac/agents/hello_langgraph/__init__.py +9 -0
  13. diagram_to_iac/agents/hello_langgraph/agent.py +621 -0
  14. diagram_to_iac/agents/policy_agent/__init__.py +15 -0
  15. diagram_to_iac/agents/policy_agent/agent.py +507 -0
  16. diagram_to_iac/agents/policy_agent/integration_example.py +191 -0
  17. diagram_to_iac/agents/policy_agent/tools/__init__.py +14 -0
  18. diagram_to_iac/agents/policy_agent/tools/tfsec_tool.py +259 -0
  19. diagram_to_iac/agents/shell_langgraph/__init__.py +21 -0
  20. diagram_to_iac/agents/shell_langgraph/agent.py +122 -0
  21. diagram_to_iac/agents/shell_langgraph/detector.py +50 -0
  22. diagram_to_iac/agents/supervisor_langgraph/__init__.py +17 -0
  23. diagram_to_iac/agents/supervisor_langgraph/agent.py +1947 -0
  24. diagram_to_iac/agents/supervisor_langgraph/demonstrator.py +22 -0
  25. diagram_to_iac/agents/supervisor_langgraph/guards.py +23 -0
  26. diagram_to_iac/agents/supervisor_langgraph/pat_loop.py +49 -0
  27. diagram_to_iac/agents/supervisor_langgraph/router.py +9 -0
  28. diagram_to_iac/agents/terraform_langgraph/__init__.py +15 -0
  29. diagram_to_iac/agents/terraform_langgraph/agent.py +1216 -0
  30. diagram_to_iac/agents/terraform_langgraph/parser.py +76 -0
  31. diagram_to_iac/core/__init__.py +7 -0
  32. diagram_to_iac/core/agent_base.py +19 -0
  33. diagram_to_iac/core/enhanced_memory.py +302 -0
  34. diagram_to_iac/core/errors.py +4 -0
  35. diagram_to_iac/core/issue_tracker.py +49 -0
  36. diagram_to_iac/core/memory.py +132 -0
  37. diagram_to_iac/services/__init__.py +10 -0
  38. diagram_to_iac/services/observability.py +59 -0
  39. diagram_to_iac/services/step_summary.py +77 -0
  40. diagram_to_iac/tools/__init__.py +11 -0
  41. diagram_to_iac/tools/api_utils.py +108 -26
  42. diagram_to_iac/tools/git/__init__.py +45 -0
  43. diagram_to_iac/tools/git/git.py +956 -0
  44. diagram_to_iac/tools/hello/__init__.py +30 -0
  45. diagram_to_iac/tools/hello/cal_utils.py +31 -0
  46. diagram_to_iac/tools/hello/text_utils.py +97 -0
  47. diagram_to_iac/tools/llm_utils/__init__.py +20 -0
  48. diagram_to_iac/tools/llm_utils/anthropic_driver.py +87 -0
  49. diagram_to_iac/tools/llm_utils/base_driver.py +90 -0
  50. diagram_to_iac/tools/llm_utils/gemini_driver.py +89 -0
  51. diagram_to_iac/tools/llm_utils/openai_driver.py +93 -0
  52. diagram_to_iac/tools/llm_utils/router.py +303 -0
  53. diagram_to_iac/tools/sec_utils.py +4 -2
  54. diagram_to_iac/tools/shell/__init__.py +17 -0
  55. diagram_to_iac/tools/shell/shell.py +415 -0
  56. diagram_to_iac/tools/text_utils.py +277 -0
  57. diagram_to_iac/tools/tf/terraform.py +851 -0
  58. diagram_to_iac-0.8.0.dist-info/METADATA +99 -0
  59. diagram_to_iac-0.8.0.dist-info/RECORD +64 -0
  60. {diagram_to_iac-0.6.0.dist-info → diagram_to_iac-0.8.0.dist-info}/WHEEL +1 -1
  61. diagram_to_iac-0.8.0.dist-info/entry_points.txt +4 -0
  62. diagram_to_iac/agents/codegen_agent.py +0 -0
  63. diagram_to_iac/agents/consensus_agent.py +0 -0
  64. diagram_to_iac/agents/deployment_agent.py +0 -0
  65. diagram_to_iac/agents/github_agent.py +0 -0
  66. diagram_to_iac/agents/interpretation_agent.py +0 -0
  67. diagram_to_iac/agents/question_agent.py +0 -0
  68. diagram_to_iac/agents/supervisor.py +0 -0
  69. diagram_to_iac/agents/vision_agent.py +0 -0
  70. diagram_to_iac/core/config.py +0 -0
  71. diagram_to_iac/tools/cv_utils.py +0 -0
  72. diagram_to_iac/tools/gh_utils.py +0 -0
  73. diagram_to_iac/tools/tf_utils.py +0 -0
  74. diagram_to_iac-0.6.0.dist-info/METADATA +0 -16
  75. diagram_to_iac-0.6.0.dist-info/RECORD +0 -32
  76. diagram_to_iac-0.6.0.dist-info/entry_points.txt +0 -2
  77. {diagram_to_iac-0.6.0.dist-info → diagram_to_iac-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,507 @@
1
+ """
2
+ Policy Agent - Security Policy Enforcement for Terraform
3
+
4
+ This PolicyAgent enforces security policies using tfsec scanning and provides
5
+ policy gate functionality to block terraform apply operations on critical violations.
6
+ It integrates with the existing multi-agent architecture following established patterns.
7
+
8
+ Key Features:
9
+ - tfsec security scanning with JSON output
10
+ - Policy evaluation and apply blocking on critical violations
11
+ - JSON findings artifacts for audit trails
12
+ - Integration with LangGraph multi-agent workflow
13
+ - Follows established security patterns with ShellExecutor
14
+
15
+ Architecture:
16
+ 1. LLM-based planner analyzes input and routes to appropriate nodes
17
+ 2. Policy scan node executes tfsec scans using secure shell execution
18
+ 3. Policy evaluation node determines if violations should block operations
19
+ 4. Artifact creation node posts JSON findings for audit trails
20
+ 5. Block node prevents terraform apply on critical violations
21
+ """
22
+
23
+ import os
24
+ import time
25
+ import uuid
26
+ import logging
27
+ from datetime import datetime
28
+ from pathlib import Path
29
+ from typing import TypedDict, Annotated, Optional, List, Dict, Any
30
+
31
+ import yaml
32
+ from langchain_core.messages import HumanMessage, BaseMessage
33
+ from langgraph.graph import StateGraph, END
34
+ from langgraph.checkpoint.memory import MemorySaver
35
+ from pydantic import BaseModel, Field
36
+
37
+ # Import core infrastructure
38
+ from diagram_to_iac.core.agent_base import AgentBase
39
+ from diagram_to_iac.core.memory import create_memory, LangGraphMemoryAdapter
40
+ from .tools.tfsec_tool import TfSecTool, TfSecScanInput, TfSecScanOutput
41
+
42
+
43
+ # --- Pydantic Schemas for Agent I/O ---
44
+ class PolicyAgentInput(BaseModel):
45
+ """Input schema for PolicyAgent operations."""
46
+ query: str = Field(..., description="Policy enforcement query or command")
47
+ repo_path: str = Field(..., description="Path to Terraform repository")
48
+ thread_id: Optional[str] = Field(default=None, description="Thread ID for conversation tracking")
49
+ operation_type: str = Field(default="scan", description="Type of policy operation (scan, evaluate, block)")
50
+
51
+
52
+ class PolicyAgentOutput(BaseModel):
53
+ """Output schema for PolicyAgent operations."""
54
+ result: str = Field(..., description="Policy enforcement result")
55
+ thread_id: str = Field(..., description="Thread ID for conversation tracking")
56
+ policy_status: str = Field(..., description="Policy status (PASS, BLOCK, ERROR)")
57
+ findings_count: int = Field(default=0, description="Number of policy findings")
58
+ critical_findings: int = Field(default=0, description="Number of critical findings")
59
+ should_block_apply: bool = Field(default=False, description="Whether to block terraform apply")
60
+ artifact_path: Optional[str] = Field(default=None, description="Path to findings artifact")
61
+ error_message: Optional[str] = Field(default=None, description="Error message if operation failed")
62
+
63
+
64
+ # --- LangGraph State Schema ---
65
+ class PolicyAgentState(TypedDict):
66
+ """State schema for PolicyAgent workflow."""
67
+ messages: Annotated[List[BaseMessage], "conversation history"]
68
+ query: str
69
+ repo_path: str
70
+ thread_id: str
71
+ operation_type: str
72
+ scan_results: Optional[TfSecScanOutput]
73
+ policy_status: str
74
+ should_block_apply: bool
75
+ artifact_path: Optional[str]
76
+ error_message: Optional[str]
77
+ final_result: str
78
+
79
+
80
+ class PolicyAgent(AgentBase):
81
+ """
82
+ Policy Agent for security policy enforcement in Terraform workflows.
83
+
84
+ This agent provides security policy scanning and enforcement using tfsec,
85
+ with the ability to block terraform apply operations on critical violations.
86
+ """
87
+
88
+ def __init__(self, config_path: str = None, memory_type: str = "persistent"):
89
+ """Initialize PolicyAgent with configuration and tools."""
90
+ self.config_path = config_path
91
+
92
+ # Load configuration
93
+ self.config = self._load_config()
94
+
95
+ # Initialize logging
96
+ self.logger = logging.getLogger(__name__)
97
+
98
+ # Initialize memory
99
+ self.memory = create_memory(memory_type=memory_type)
100
+ self.memory_adapter = LangGraphMemoryAdapter(self.memory)
101
+
102
+ # Initialize tools
103
+ self.tfsec_tool = TfSecTool(config_path=config_path)
104
+
105
+ # Build LangGraph workflow
106
+ self.workflow = self._build_workflow()
107
+
108
+ def _load_config(self) -> Dict[str, Any]:
109
+ """Load agent configuration from YAML file."""
110
+ if self.config_path and os.path.exists(self.config_path):
111
+ with open(self.config_path, 'r') as f:
112
+ return yaml.safe_load(f)
113
+
114
+ # Default configuration
115
+ config_file = os.path.join(os.path.dirname(__file__), "config.yaml")
116
+ if os.path.exists(config_file):
117
+ with open(config_file, 'r') as f:
118
+ return yaml.safe_load(f)
119
+
120
+ # Fallback minimal configuration
121
+ return {
122
+ "policy": {
123
+ "tfsec": {
124
+ "enabled": True,
125
+ "block_on_severity": ["CRITICAL", "HIGH"],
126
+ "artifact_on_severity": ["CRITICAL", "HIGH", "MEDIUM"]
127
+ }
128
+ }
129
+ }
130
+
131
+ def _build_workflow(self) -> StateGraph:
132
+ """Build the LangGraph workflow for policy enforcement."""
133
+ workflow = StateGraph(PolicyAgentState)
134
+
135
+ # Add nodes
136
+ workflow.add_node("planner", self._planner_llm_node)
137
+ workflow.add_node("policy_scan", self._policy_scan_node)
138
+ workflow.add_node("policy_evaluate", self._policy_evaluate_node)
139
+ workflow.add_node("policy_block", self._policy_block_node)
140
+ workflow.add_node("policy_report", self._policy_report_node)
141
+
142
+ # Add edges
143
+ workflow.set_entry_point("planner")
144
+ workflow.add_conditional_edges(
145
+ "planner",
146
+ self._route_after_planner,
147
+ {
148
+ "policy_scan": "policy_scan",
149
+ "policy_evaluate": "policy_evaluate",
150
+ "policy_block": "policy_block",
151
+ "policy_report": "policy_report",
152
+ "end": END
153
+ }
154
+ )
155
+
156
+ # Policy scan flows to evaluation
157
+ workflow.add_edge("policy_scan", "policy_evaluate")
158
+
159
+ # Policy evaluation routes to block or report
160
+ workflow.add_conditional_edges(
161
+ "policy_evaluate",
162
+ self._route_after_evaluation,
163
+ {
164
+ "policy_block": "policy_block",
165
+ "policy_report": "policy_report"
166
+ }
167
+ )
168
+
169
+ # Block and report both end
170
+ workflow.add_edge("policy_block", END)
171
+ workflow.add_edge("policy_report", END)
172
+
173
+ return workflow.compile(checkpointer=MemorySaver())
174
+
175
+ def _planner_llm_node(self, state: PolicyAgentState):
176
+ """LLM-based planner node for routing policy operations."""
177
+ try:
178
+ query_content = state.get("query", "")
179
+
180
+ # Use simple rule-based routing for now (can be enhanced with LLM later)
181
+ route_decision = self._determine_route(query_content, state)
182
+
183
+ self.memory.add_to_conversation(
184
+ "assistant",
185
+ f"Policy planner routing to: {route_decision}",
186
+ {"agent": "policy_agent", "node": "planner", "route": route_decision}
187
+ )
188
+
189
+ return {"final_result": route_decision}
190
+
191
+ except Exception as e:
192
+ self.logger.error(f"Policy planner error: {str(e)}", exc_info=True)
193
+ return {"final_result": "end", "error_message": str(e)}
194
+
195
+ def _determine_route(self, query: str, state: PolicyAgentState) -> str:
196
+ """Determine routing based on query content and state."""
197
+ query_lower = query.lower()
198
+ operation_type = state.get("operation_type", "scan")
199
+
200
+ if operation_type == "scan" or "scan" in query_lower:
201
+ return "policy_scan"
202
+ elif operation_type == "evaluate" or "evaluate" in query_lower:
203
+ return "policy_evaluate"
204
+ elif operation_type == "block" or "block" in query_lower:
205
+ return "policy_block"
206
+ elif "report" in query_lower or "artifact" in query_lower:
207
+ return "policy_report"
208
+ else:
209
+ return "policy_scan" # Default to scan
210
+
211
+ def _policy_scan_node(self, state: PolicyAgentState):
212
+ """Execute tfsec security scan on the repository."""
213
+ try:
214
+ repo_path = state.get("repo_path", "")
215
+
216
+ self.logger.info(f"Starting policy scan for repository: {repo_path}")
217
+
218
+ # Configure scan input
219
+ scan_input = TfSecScanInput(
220
+ repo_path=repo_path,
221
+ output_format="json",
222
+ timeout=self.config.get("policy", {}).get("tfsec", {}).get("timeout_seconds", 120)
223
+ )
224
+
225
+ # Execute scan
226
+ scan_results = self.tfsec_tool.scan(scan_input)
227
+
228
+ if scan_results.scan_successful:
229
+ self.logger.info(f"Policy scan completed: {scan_results.total_findings} findings")
230
+ status_msg = f"Policy scan completed successfully with {scan_results.total_findings} findings"
231
+ else:
232
+ self.logger.error(f"Policy scan failed: {scan_results.error_message}")
233
+ status_msg = f"Policy scan failed: {scan_results.error_message}"
234
+
235
+ self.memory.add_to_conversation(
236
+ "assistant",
237
+ status_msg,
238
+ {
239
+ "agent": "policy_agent",
240
+ "node": "policy_scan",
241
+ "findings_count": scan_results.total_findings,
242
+ "scan_successful": scan_results.scan_successful
243
+ }
244
+ )
245
+
246
+ return {
247
+ "scan_results": scan_results,
248
+ "policy_status": "SCANNED" if scan_results.scan_successful else "ERROR"
249
+ }
250
+
251
+ except Exception as e:
252
+ error_msg = f"Policy scan node error: {str(e)}"
253
+ self.logger.error(error_msg, exc_info=True)
254
+ return {
255
+ "policy_status": "ERROR",
256
+ "error_message": error_msg
257
+ }
258
+
259
+ def _policy_evaluate_node(self, state: PolicyAgentState):
260
+ """Evaluate scan results and determine if apply should be blocked."""
261
+ try:
262
+ scan_results = state.get("scan_results")
263
+ if not scan_results:
264
+ return {"policy_status": "ERROR", "error_message": "No scan results to evaluate"}
265
+
266
+ # Get blocking configuration
267
+ block_on_severity = self.config.get("policy", {}).get("tfsec", {}).get("block_on_severity", ["CRITICAL", "HIGH"])
268
+
269
+ # Determine if apply should be blocked
270
+ should_block = self.tfsec_tool.should_block_apply(scan_results, block_on_severity)
271
+
272
+ if should_block:
273
+ self.logger.warning(f"Policy evaluation: BLOCK - {scan_results.critical_count} critical, {scan_results.high_count} high severity findings")
274
+ policy_status = "BLOCK"
275
+ else:
276
+ self.logger.info(f"Policy evaluation: PASS - No blocking violations found")
277
+ policy_status = "PASS"
278
+
279
+ self.memory.add_to_conversation(
280
+ "assistant",
281
+ f"Policy evaluation: {policy_status} - Should block apply: {should_block}",
282
+ {
283
+ "agent": "policy_agent",
284
+ "node": "policy_evaluate",
285
+ "policy_status": policy_status,
286
+ "should_block_apply": should_block,
287
+ "critical_findings": scan_results.critical_count,
288
+ "high_findings": scan_results.high_count
289
+ }
290
+ )
291
+
292
+ return {
293
+ "policy_status": policy_status,
294
+ "should_block_apply": should_block
295
+ }
296
+
297
+ except Exception as e:
298
+ error_msg = f"Policy evaluation error: {str(e)}"
299
+ self.logger.error(error_msg, exc_info=True)
300
+ return {
301
+ "policy_status": "ERROR",
302
+ "error_message": error_msg
303
+ }
304
+
305
+ def _policy_block_node(self, state: PolicyAgentState):
306
+ """Handle policy blocking - prevent terraform apply and create artifacts."""
307
+ try:
308
+ scan_results = state.get("scan_results")
309
+ repo_path = state.get("repo_path", "")
310
+
311
+ # Create artifact for audit trail
312
+ artifact_path = self._create_artifact(scan_results, repo_path)
313
+
314
+ block_msg = f"POLICY VIOLATION: Terraform apply blocked due to {scan_results.critical_count} critical and {scan_results.high_count} high severity policy violations"
315
+
316
+ self.logger.warning(block_msg)
317
+ self.memory.add_to_conversation(
318
+ "assistant",
319
+ block_msg,
320
+ {
321
+ "agent": "policy_agent",
322
+ "node": "policy_block",
323
+ "action": "blocked_apply",
324
+ "artifact_path": artifact_path
325
+ }
326
+ )
327
+
328
+ return {
329
+ "final_result": block_msg,
330
+ "artifact_path": artifact_path
331
+ }
332
+
333
+ except Exception as e:
334
+ error_msg = f"Policy block node error: {str(e)}"
335
+ self.logger.error(error_msg, exc_info=True)
336
+ return {
337
+ "final_result": error_msg,
338
+ "error_message": error_msg
339
+ }
340
+
341
+ def _policy_report_node(self, state: PolicyAgentState):
342
+ """Generate policy report and artifacts."""
343
+ try:
344
+ scan_results = state.get("scan_results")
345
+ repo_path = state.get("repo_path", "")
346
+
347
+ # Create artifact for audit trail
348
+ artifact_path = self._create_artifact(scan_results, repo_path)
349
+
350
+ report_msg = f"Policy scan completed: {scan_results.total_findings} findings ({scan_results.critical_count} critical, {scan_results.high_count} high, {scan_results.medium_count} medium, {scan_results.low_count} low)"
351
+
352
+ self.logger.info(report_msg)
353
+ self.memory.add_to_conversation(
354
+ "assistant",
355
+ report_msg,
356
+ {
357
+ "agent": "policy_agent",
358
+ "node": "policy_report",
359
+ "action": "generated_report",
360
+ "artifact_path": artifact_path
361
+ }
362
+ )
363
+
364
+ return {
365
+ "final_result": report_msg,
366
+ "artifact_path": artifact_path
367
+ }
368
+
369
+ except Exception as e:
370
+ error_msg = f"Policy report node error: {str(e)}"
371
+ self.logger.error(error_msg, exc_info=True)
372
+ return {
373
+ "final_result": error_msg,
374
+ "error_message": error_msg
375
+ }
376
+
377
+ def _create_artifact(self, scan_results: TfSecScanOutput, repo_path: str) -> Optional[str]:
378
+ """Create JSON artifact with policy findings."""
379
+ try:
380
+ # Get artifact configuration
381
+ artifacts_config = self.config.get("policy", {}).get("artifacts", {})
382
+ output_dir = artifacts_config.get("output_dir", "/workspace/.policy_findings")
383
+
384
+ # Create artifact path
385
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
386
+ filename = artifacts_config.get("json_filename", "policy_findings_{timestamp}.json").format(timestamp=timestamp)
387
+ artifact_path = os.path.join(output_dir, filename)
388
+
389
+ # Create artifact
390
+ if self.tfsec_tool.create_findings_artifact(scan_results, artifact_path):
391
+ return artifact_path
392
+ else:
393
+ self.logger.warning("Failed to create policy findings artifact")
394
+ return None
395
+
396
+ except Exception as e:
397
+ self.logger.error(f"Error creating policy artifact: {str(e)}")
398
+ return None
399
+
400
+ def _route_after_planner(self, state: PolicyAgentState) -> str:
401
+ """Route after planner decision."""
402
+ final_result = state.get("final_result", "")
403
+
404
+ if final_result == "policy_scan":
405
+ return "policy_scan"
406
+ elif final_result == "policy_evaluate":
407
+ return "policy_evaluate"
408
+ elif final_result == "policy_block":
409
+ return "policy_block"
410
+ elif final_result == "policy_report":
411
+ return "policy_report"
412
+ else:
413
+ return "end"
414
+
415
+ def _route_after_evaluation(self, state: PolicyAgentState) -> str:
416
+ """Route after policy evaluation."""
417
+ should_block = state.get("should_block_apply", False)
418
+
419
+ if should_block:
420
+ return "policy_block"
421
+ else:
422
+ return "policy_report"
423
+
424
+ # --- AgentBase Implementation ---
425
+
426
+ def plan(self, agent_input: PolicyAgentInput) -> Dict[str, Any]:
427
+ """Generate plan for policy enforcement."""
428
+ return {
429
+ "operation": "policy_enforcement",
430
+ "repo_path": agent_input.repo_path,
431
+ "query": agent_input.query,
432
+ "steps": ["scan", "evaluate", "report_or_block"]
433
+ }
434
+
435
+ def run(self, agent_input: PolicyAgentInput) -> PolicyAgentOutput:
436
+ """Execute policy enforcement workflow."""
437
+ try:
438
+ thread_id = agent_input.thread_id or str(uuid.uuid4())
439
+
440
+ # Prepare initial state
441
+ initial_state = PolicyAgentState(
442
+ messages=[HumanMessage(content=agent_input.query)],
443
+ query=agent_input.query,
444
+ repo_path=agent_input.repo_path,
445
+ thread_id=thread_id,
446
+ operation_type=agent_input.operation_type,
447
+ scan_results=None,
448
+ policy_status="PENDING",
449
+ should_block_apply=False,
450
+ artifact_path=None,
451
+ error_message=None,
452
+ final_result=""
453
+ )
454
+
455
+ # Execute workflow
456
+ config = {"configurable": {"thread_id": thread_id}}
457
+ final_state = None
458
+
459
+ for state in self.workflow.stream(initial_state, config):
460
+ final_state = state
461
+
462
+ if not final_state:
463
+ raise RuntimeError("Workflow execution failed - no final state")
464
+
465
+ # Extract results from final state
466
+ state_values = list(final_state.values())[0] if final_state else {}
467
+ scan_results = state_values.get("scan_results")
468
+
469
+ return PolicyAgentOutput(
470
+ result=state_values.get("final_result", "Policy enforcement completed"),
471
+ thread_id=thread_id,
472
+ policy_status=state_values.get("policy_status", "COMPLETED"),
473
+ findings_count=scan_results.total_findings if scan_results else 0,
474
+ critical_findings=scan_results.critical_count if scan_results else 0,
475
+ should_block_apply=state_values.get("should_block_apply", False),
476
+ artifact_path=state_values.get("artifact_path"),
477
+ error_message=state_values.get("error_message")
478
+ )
479
+
480
+ except Exception as e:
481
+ self.logger.error(f"Policy agent execution failed: {str(e)}", exc_info=True)
482
+ return PolicyAgentOutput(
483
+ result=f"Policy enforcement failed: {str(e)}",
484
+ thread_id=agent_input.thread_id or "unknown",
485
+ policy_status="ERROR",
486
+ error_message=str(e)
487
+ )
488
+
489
+ def report(self, agent_output: PolicyAgentOutput) -> str:
490
+ """Generate human-readable report of policy enforcement results."""
491
+ report_lines = [
492
+ "=== Policy Enforcement Report ===",
493
+ f"Status: {agent_output.policy_status}",
494
+ f"Total Findings: {agent_output.findings_count}",
495
+ f"Critical Findings: {agent_output.critical_findings}",
496
+ f"Should Block Apply: {agent_output.should_block_apply}",
497
+ ]
498
+
499
+ if agent_output.artifact_path:
500
+ report_lines.append(f"Artifact: {agent_output.artifact_path}")
501
+
502
+ if agent_output.error_message:
503
+ report_lines.append(f"Error: {agent_output.error_message}")
504
+
505
+ report_lines.append(f"Result: {agent_output.result}")
506
+
507
+ return "\n".join(report_lines)
@@ -0,0 +1,191 @@
1
+ """
2
+ PolicyAgent Integration Example
3
+
4
+ This example demonstrates how the PolicyAgent integrates with the existing
5
+ multi-agent workflow to provide security policy enforcement.
6
+ """
7
+
8
+ # Example usage in supervisor workflow:
9
+
10
+ def enhanced_terraform_workflow_with_policy_gate(repo_path: str):
11
+ """
12
+ Enhanced terraform workflow with policy gate integration.
13
+
14
+ This shows how the PolicyAgent integrates with the existing workflow
15
+ to block terraform apply operations on critical security violations.
16
+ """
17
+
18
+ # Step 1: Standard terraform init and plan (existing workflow)
19
+ terraform_result = terraform_agent.run(TerraformAgentInput(
20
+ query=f"terraform plan in {repo_path}",
21
+ thread_id=thread_id
22
+ ))
23
+
24
+ if terraform_result.error_message:
25
+ # Handle terraform errors with existing error classification
26
+ error_tags = classify_terraform_error(terraform_result.error_message)
27
+
28
+ if "policy_block" in error_tags:
29
+ # Route to PolicyAgent for detailed policy analysis
30
+ policy_result = policy_agent.run(PolicyAgentInput(
31
+ query="evaluate security policies and determine if apply should be blocked",
32
+ repo_path=repo_path,
33
+ operation_type="evaluate"
34
+ ))
35
+
36
+ if policy_result.should_block_apply:
37
+ # Block terraform apply and create GitHub issue
38
+ return create_policy_violation_issue(policy_result)
39
+
40
+ # Step 2: Pre-apply policy scan (new step)
41
+ policy_result = policy_agent.run(PolicyAgentInput(
42
+ query="scan repository for security violations before terraform apply",
43
+ repo_path=repo_path,
44
+ operation_type="scan"
45
+ ))
46
+
47
+ if policy_result.should_block_apply:
48
+ # Critical violations found - block apply
49
+ logger.warning(f"Terraform apply blocked due to {policy_result.critical_findings} critical policy violations")
50
+
51
+ # Create GitHub issue with policy findings
52
+ return create_policy_blocking_issue(policy_result)
53
+
54
+ # Step 3: Continue with terraform apply (existing workflow)
55
+ if policy_result.policy_status == "PASS":
56
+ apply_result = terraform_agent.run(TerraformAgentInput(
57
+ query=f"terraform apply in {repo_path}",
58
+ thread_id=thread_id
59
+ ))
60
+
61
+ # Step 4: Post-apply audit artifact (new step)
62
+ policy_agent.run(PolicyAgentInput(
63
+ query="create audit artifact with policy scan results",
64
+ repo_path=repo_path,
65
+ operation_type="report"
66
+ ))
67
+
68
+ return apply_result
69
+ else:
70
+ return policy_result
71
+
72
+
73
+ def create_policy_violation_issue(policy_result):
74
+ """Create GitHub issue for policy violations."""
75
+ issue_title = f"Security Policy Violations Detected - {policy_result.critical_findings} Critical Issues"
76
+
77
+ issue_body = f"""
78
+ ## Security Policy Violations
79
+
80
+ **Status:** {policy_result.policy_status}
81
+ **Critical Findings:** {policy_result.critical_findings}
82
+ **Total Findings:** {policy_result.findings_count}
83
+
84
+ ### Action Required
85
+ Terraform apply has been blocked due to critical security policy violations.
86
+
87
+ ### Audit Artifact
88
+ Policy findings have been saved to: `{policy_result.artifact_path}`
89
+
90
+ ### Next Steps
91
+ 1. Review the policy findings in the artifact file
92
+ 2. Fix the security violations
93
+ 3. Re-run the terraform workflow
94
+
95
+ ### Technical Details
96
+ ```
97
+ {policy_result.result}
98
+ ```
99
+ """
100
+
101
+ return git_agent.run(GitAgentInput(
102
+ query=f"open issue '{issue_title}' with policy violation details",
103
+ issue_body=issue_body
104
+ ))
105
+
106
+
107
+ # Example configuration integration:
108
+
109
+ POLICY_ENFORCEMENT_CONFIG = {
110
+ "policy": {
111
+ "tfsec": {
112
+ "enabled": True,
113
+ "block_on_severity": ["CRITICAL", "HIGH"],
114
+ "artifact_on_severity": ["CRITICAL", "HIGH", "MEDIUM"],
115
+ "timeout_seconds": 120
116
+ },
117
+ "artifacts": {
118
+ "output_dir": "/workspace/.policy_findings",
119
+ "json_filename": "policy_findings_{timestamp}.json"
120
+ }
121
+ },
122
+ "integration": {
123
+ "supervisor": {
124
+ "pre_apply_scan": True,
125
+ "post_apply_audit": True,
126
+ "create_github_issues": True
127
+ }
128
+ }
129
+ }
130
+
131
+
132
+ # Example routing in supervisor planner:
133
+
134
+ def enhanced_supervisor_routing():
135
+ """
136
+ Enhanced supervisor routing with policy gate integration.
137
+
138
+ This shows how the supervisor routes requests to the PolicyAgent
139
+ based on terraform error classification and workflow state.
140
+ """
141
+
142
+ routing_keys = {
143
+ "clone": "ROUTE_TO_CLONE",
144
+ "stack_detect": "ROUTE_TO_STACK_DETECT",
145
+ "branch_create": "ROUTE_TO_BRANCH_CREATE",
146
+ "terraform": "ROUTE_TO_TERRAFORM",
147
+ "policy_scan": "ROUTE_TO_POLICY_SCAN", # New routing
148
+ "policy_gate": "ROUTE_TO_POLICY_GATE", # New routing
149
+ "issue": "ROUTE_TO_ISSUE",
150
+ "end": "ROUTE_TO_END"
151
+ }
152
+
153
+ # Enhanced planner prompt with policy awareness
154
+ planner_prompt = f"""
155
+ You are the supervisor planner for a DevOps-in-a-Box system that manages
156
+ Terraform-based IaC deployment pipelines with security policy enforcement.
157
+
158
+ Available routing options:
159
+ 1. {routing_keys["clone"]} - Clone repository
160
+ 2. {routing_keys["stack_detect"]} - Detect technology stack
161
+ 3. {routing_keys["branch_create"]} - Create feature branch
162
+ 4. {routing_keys["terraform"]} - Execute terraform operations
163
+ 5. {routing_keys["policy_scan"]} - Scan for security policy violations
164
+ 6. {routing_keys["policy_gate"]} - Evaluate policy gate for apply blocking
165
+ 7. {routing_keys["issue"]} - Create GitHub issue
166
+ 8. {routing_keys["end"]} - End workflow
167
+
168
+ Security Policy Integration:
169
+ - Before terraform apply, always route to policy_scan for security validation
170
+ - If critical violations are found, route to policy_gate for apply blocking
171
+ - Policy violations should create GitHub issues with detailed findings
172
+
173
+ Route to the appropriate action based on the user input and workflow state.
174
+ """
175
+
176
+ return planner_prompt
177
+
178
+
179
+ if __name__ == "__main__":
180
+ print("PolicyAgent Integration Example")
181
+ print("===============================")
182
+ print()
183
+ print("This example shows how the PolicyAgent integrates with the existing")
184
+ print("multi-agent workflow to provide security policy enforcement.")
185
+ print()
186
+ print("Key integration points:")
187
+ print("1. Pre-apply security scanning")
188
+ print("2. Policy-based apply blocking")
189
+ print("3. GitHub issue creation for violations")
190
+ print("4. Audit artifact generation")
191
+ print("5. Enhanced supervisor routing")