feat(pipeline): store MCP fields (assertion, pass/fail criteria, check_type) in generation_metadata

- Add assertion, pass_criteria, fail_criteria, check_type to AtomicControlCandidate dataclass
- Parse MCP fields from LLM output in _process_pass0b_control
- Store MCP fields in generation_metadata JSON for later use by MCP scanner
- Fields default to empty when not present (backward-compatible with old prompts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-26 09:32:56 +02:00
parent 7e3b1108e2
commit 629b9d9ca5

View File

@@ -215,6 +215,11 @@ class AtomicControlCandidate:
domain: str = "" domain: str = ""
source_regulation: str = "" source_regulation: str = ""
source_article: str = "" source_article: str = ""
# MCP-taugliche Felder
assertion: str = ""
pass_criteria: list = field(default_factory=list)
fail_criteria: list = field(default_factory=list)
check_type: str = ""
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
@@ -229,6 +234,10 @@ class AtomicControlCandidate:
"severity": self.severity, "severity": self.severity,
"category": self.category, "category": self.category,
"domain": self.domain, "domain": self.domain,
"assertion": self.assertion,
"pass_criteria": self.pass_criteria,
"fail_criteria": self.fail_criteria,
"check_type": self.check_type,
} }
@@ -2114,12 +2123,16 @@ Quellreferenz: {source_ref}
Antworte als JSON: Antworte als JSON:
{{ {{
"title": "Kurzer Titel (max 80 Zeichen, deutsch, HANDLUNG enthalten)", "title": "Kurzer Titel (max 80 Zeichen, deutsch, HANDLUNG enthalten)",
"assertion": "Eine einzige pruefbare Aussage (wird als MCP-Suchanfrage verwendet)",
"objective": "Was muss erreicht werden? (1-2 Sätze)", "objective": "Was muss erreicht werden? (1-2 Sätze)",
"requirements": ["Konkrete Anforderung 1", "Anforderung 2"], "requirements": ["Konkrete Anforderung 1", "Anforderung 2"],
"test_procedure": ["Prüfschritt 1", "Prüfschritt 2"], "test_procedure": ["Prüfschritt 1", "Prüfschritt 2"],
"evidence": ["Nachweis/Dokument/Artefakt 1", "Nachweis 2"], "evidence": ["Nachweis/Dokument/Artefakt 1", "Nachweis 2"],
"pass_criteria": ["Wann gilt dieses Control als erfuellt?"],
"fail_criteria": ["Wann gilt dieses Control als nicht erfuellt?"],
"severity": "critical|high|medium|low", "severity": "critical|high|medium|low",
"category": "security|privacy|governance|operations|finance|reporting", "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"
}}""" }}"""
@@ -2206,12 +2219,16 @@ Antworte als JSON-Objekt. Fuer JEDE Pflicht ein Key (die Pflicht-ID):
Jedes Control hat dieses Format: Jedes Control hat dieses Format:
{{ {{
"title": "Kurzer Titel (max 80 Zeichen, deutsch, HANDLUNG enthalten)", "title": "Kurzer Titel (max 80 Zeichen, deutsch, HANDLUNG enthalten)",
"assertion": "Eine einzige pruefbare Aussage (wird als MCP-Suchanfrage verwendet)",
"objective": "Was muss erreicht werden? (1-2 Sätze)", "objective": "Was muss erreicht werden? (1-2 Sätze)",
"requirements": ["Konkrete Anforderung 1", "Anforderung 2"], "requirements": ["Konkrete Anforderung 1", "Anforderung 2"],
"test_procedure": ["Prüfschritt 1", "Prüfschritt 2"], "test_procedure": ["Prüfschritt 1", "Prüfschritt 2"],
"evidence": ["Nachweis/Dokument/Artefakt 1", "Nachweis 2"], "evidence": ["Nachweis/Dokument/Artefakt 1", "Nachweis 2"],
"pass_criteria": ["Wann gilt dieses Control als erfuellt?"],
"fail_criteria": ["Wann gilt dieses Control als nicht erfuellt?"],
"severity": "critical|high|medium|low", "severity": "critical|high|medium|low",
"category": "security|privacy|governance|operations|finance|reporting", "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"
}}""" }}"""
@@ -2950,6 +2967,10 @@ class DecompositionPass:
parsed.get("severity", obl["parent_severity"]) parsed.get("severity", obl["parent_severity"])
), ),
category=parsed.get("category", obl["parent_category"]), category=parsed.get("category", obl["parent_category"]),
assertion=parsed.get("assertion", "")[:500],
pass_criteria=_ensure_list(parsed.get("pass_criteria", [])),
fail_criteria=_ensure_list(parsed.get("fail_criteria", [])),
check_type=parsed.get("check_type", ""),
) )
# Store merge_key from LLM output in metadata # Store merge_key from LLM output in metadata
llm_merge_key = parsed.get("merge_key", "") llm_merge_key = parsed.get("merge_key", "")
@@ -3412,6 +3433,11 @@ class DecompositionPass:
"framework_domain": getattr(atomic, "_framework_domain", None), "framework_domain": getattr(atomic, "_framework_domain", None),
"framework_subcontrol_id": getattr(atomic, "_framework_subcontrol_id", None), "framework_subcontrol_id": getattr(atomic, "_framework_subcontrol_id", None),
"decomposition_source": getattr(atomic, "_decomposition_source", "direct"), "decomposition_source": getattr(atomic, "_decomposition_source", "direct"),
# MCP-taugliche Felder
"assertion": atomic.assertion or "",
"pass_criteria": atomic.pass_criteria or [],
"fail_criteria": atomic.fail_criteria or [],
"check_type": atomic.check_type or "",
}), }),
"framework_id": "14b1bdd2-abc7-4a43-adae-14471ee5c7cf", "framework_id": "14b1bdd2-abc7-4a43-adae-14471ee5c7cf",
}, },