feat(pipeline): implement Control Dependency Engine (Block 9)
Core engine (dependency_engine.py): - 5 dependency types: prerequisite, supersedes, compensating_control, conditional_requirement, scope_exclusion - Generic condition evaluator (JSONB rules with AND/OR/NOT/field ops) - Priority-based conflict resolution - Cycle detection (DFS) + topological sort - Full evaluation with MCP-compatible dependency_resolution trace - 39 tests all passing (incl. GHV scenario from user requirements) Automatic generator (dependency_generator.py): - Ontology-based: same normalized_object + phase sequence -> prerequisite - Pattern-based: define->implement, implement->monitor, etc. - Domain packs: YAML rules for GDPR, AI Act, CRA, Security, Labor Contracts - 14 tests all passing API routes (dependency_routes.py): - CRUD for dependencies - POST /evaluate with dependency resolution - POST /generate (auto-generation with dry_run) - POST /validate (cycle detection) - GET /graph (nodes + edges for visualization) Prompt enhancement (decomposition_pass.py): - Added dependency_hints + lifecycle_phase_order to Pass 0b prompt - Stored in generation_metadata for post-processing DB migration: control_dependencies + control_evaluation_results tables 126 tests total, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -220,6 +220,9 @@ class AtomicControlCandidate:
|
||||
pass_criteria: list = field(default_factory=list)
|
||||
fail_criteria: list = field(default_factory=list)
|
||||
check_type: str = ""
|
||||
# Dependency Engine Felder
|
||||
dependency_hints: list = field(default_factory=list)
|
||||
lifecycle_phase_order: int = 0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
@@ -238,6 +241,8 @@ class AtomicControlCandidate:
|
||||
"pass_criteria": self.pass_criteria,
|
||||
"fail_criteria": self.fail_criteria,
|
||||
"check_type": self.check_type,
|
||||
"dependency_hints": self.dependency_hints,
|
||||
"lifecycle_phase_order": self.lifecycle_phase_order,
|
||||
}
|
||||
|
||||
|
||||
@@ -2133,7 +2138,9 @@ Antworte als JSON:
|
||||
"severity": "critical|high|medium|low",
|
||||
"category": "security|privacy|governance|operations|finance|reporting",
|
||||
"check_type": "technical_config_check|document_clause_check|code_pattern_check|evidence_check|interview_required",
|
||||
"merge_key": "action_type:normalized_object:control_phase"
|
||||
"merge_key": "action_type:normalized_object:control_phase",
|
||||
"dependency_hints": ["dependency_type:action_type:normalized_object (Voraussetzungen, Ersetzungen, Kompensationen)"],
|
||||
"lifecycle_phase_order": "1-13 (1=scope, 2=definition, 4=implementation, 7=monitoring, 8=testing, 12=reporting)"
|
||||
}}"""
|
||||
|
||||
|
||||
@@ -2229,7 +2236,9 @@ Jedes Control hat dieses Format:
|
||||
"severity": "critical|high|medium|low",
|
||||
"category": "security|privacy|governance|operations|finance|reporting",
|
||||
"check_type": "technical_config_check|document_clause_check|code_pattern_check|evidence_check|interview_required",
|
||||
"merge_key": "action_type:normalized_object:control_phase"
|
||||
"merge_key": "action_type:normalized_object:control_phase",
|
||||
"dependency_hints": ["dependency_type:action_type:normalized_object (Voraussetzungen, Ersetzungen, Kompensationen)"],
|
||||
"lifecycle_phase_order": "1-13 (1=scope, 2=definition, 4=implementation, 7=monitoring, 8=testing, 12=reporting)"
|
||||
}}"""
|
||||
|
||||
|
||||
@@ -2971,6 +2980,8 @@ class DecompositionPass:
|
||||
pass_criteria=_ensure_list(parsed.get("pass_criteria", [])),
|
||||
fail_criteria=_ensure_list(parsed.get("fail_criteria", [])),
|
||||
check_type=parsed.get("check_type", ""),
|
||||
dependency_hints=_ensure_list(parsed.get("dependency_hints", [])),
|
||||
lifecycle_phase_order=int(parsed.get("lifecycle_phase_order", 0) or 0),
|
||||
)
|
||||
# Store merge_key from LLM output in metadata
|
||||
llm_merge_key = parsed.get("merge_key", "")
|
||||
@@ -2980,6 +2991,12 @@ class DecompositionPass:
|
||||
atomic.parent_control_uuid = obl["parent_uuid"]
|
||||
atomic.obligation_candidate_id = obl["candidate_id"]
|
||||
|
||||
# Set lifecycle_phase_order deterministically if not set by LLM
|
||||
if not atomic.lifecycle_phase_order:
|
||||
from services.control_ontology import classify_action, get_phase_order
|
||||
action_type = classify_action(obl.get("action", "") or atomic.title)
|
||||
atomic.lifecycle_phase_order = get_phase_order(action_type)
|
||||
|
||||
# Cap severity for implementation-specific obligations
|
||||
if obl.get("is_implementation_specific") and atomic.severity in (
|
||||
"critical", "high"
|
||||
@@ -3438,6 +3455,9 @@ class DecompositionPass:
|
||||
"pass_criteria": atomic.pass_criteria or [],
|
||||
"fail_criteria": atomic.fail_criteria or [],
|
||||
"check_type": atomic.check_type or "",
|
||||
# Dependency Engine Felder
|
||||
"dependency_hints": atomic.dependency_hints or [],
|
||||
"lifecycle_phase_order": atomic.lifecycle_phase_order or 0,
|
||||
}),
|
||||
"framework_id": "14b1bdd2-abc7-4a43-adae-14471ee5c7cf",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user