feat: Framework Decomposition Engine + Composite Detection for Pass 0b
Adds a routing layer between Pass 0a and Pass 0b that classifies obligations into atomic/compound/framework_container. Framework-container obligations (e.g. "CCM-Praktiken fuer AIS") are decomposed into concrete sub-obligations via an internal framework registry before Pass 0b composition. - New: framework_decomposition.py with routing, matching, decomposition - New: Framework registry (NIST SP 800-53, OWASP ASVS, CSA CCM) as JSON - New: Composite detection flags on atomic controls (is_composite, atomicity) - New: gen_meta fields: framework_ref, framework_domain, decomposition_source - Integration: _route_and_compose() in run_pass0b() deterministic path - 248 tests (198 decomposition + 50 framework), all passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ from compliance.services.decomposition_pass import (
|
||||
_validate_atomic_control,
|
||||
_PATTERN_CANDIDATES_MAP,
|
||||
_PATTERN_CANDIDATES_BY_ACTION,
|
||||
_is_composite_obligation,
|
||||
)
|
||||
|
||||
|
||||
@@ -1049,6 +1050,123 @@ class TestOutputValidator:
|
||||
issues = _validate_atomic_control(ac, "implement", "policy")
|
||||
assert not any("raw infinitive" in i for i in issues)
|
||||
|
||||
def test_composite_obligation_warns(self):
|
||||
"""Composite obligations produce a WARN in validation."""
|
||||
ac = AtomicControlCandidate(
|
||||
title="CCM-Praktiken", objective="x",
|
||||
test_procedure=["tp"], evidence=["ev"],
|
||||
)
|
||||
ac._is_composite = True # type: ignore[attr-defined]
|
||||
issues = _validate_atomic_control(ac, "implement", "policy")
|
||||
assert any("composite" in i for i in issues)
|
||||
|
||||
def test_non_composite_no_warn(self):
|
||||
"""Non-composite obligations do NOT produce composite WARN."""
|
||||
ac = AtomicControlCandidate(
|
||||
title="MFA", objective="x",
|
||||
test_procedure=["tp"], evidence=["ev"],
|
||||
)
|
||||
ac._is_composite = False # type: ignore[attr-defined]
|
||||
issues = _validate_atomic_control(ac, "implement", "technical_control")
|
||||
assert not any("composite" in i for i in issues)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# COMPOSITE / FRAMEWORK DETECTION TESTS
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCompositeDetection:
|
||||
"""Tests for _is_composite_obligation()."""
|
||||
|
||||
def test_ccm_praktiken_detected(self):
|
||||
"""'CCM-Praktiken für AIS implementieren' is composite."""
|
||||
assert _is_composite_obligation(
|
||||
"CCM-Praktiken für AIS implementieren", "CCM-Praktiken"
|
||||
)
|
||||
|
||||
def test_kontrollen_gemaess_nist(self):
|
||||
"""'Kontrollen gemäß NIST umsetzen' is composite."""
|
||||
assert _is_composite_obligation(
|
||||
"Kontrollen gemäß NIST SP 800-53 umsetzen", "Kontrollen"
|
||||
)
|
||||
|
||||
def test_iso_27001_referenced(self):
|
||||
"""ISO 27001 reference in object triggers composite."""
|
||||
assert _is_composite_obligation(
|
||||
"Maßnahmen umsetzen", "ISO 27001 Anhang A"
|
||||
)
|
||||
|
||||
def test_owasp_framework(self):
|
||||
"""OWASP reference triggers composite."""
|
||||
assert _is_composite_obligation(
|
||||
"OWASP Top 10 Maßnahmen implementieren", "Sicherheitsmaßnahmen"
|
||||
)
|
||||
|
||||
def test_bsi_grundschutz(self):
|
||||
"""BSI reference triggers composite."""
|
||||
assert _is_composite_obligation(
|
||||
"BSI-Grundschutz-Kompendium anwenden", "IT-Grundschutz"
|
||||
)
|
||||
|
||||
def test_anforderungen_gemaess(self):
|
||||
"""'Anforderungen gemäß X' is composite."""
|
||||
assert _is_composite_obligation(
|
||||
"Anforderungen gemäß EU AI Act umsetzen", "Anforderungen"
|
||||
)
|
||||
|
||||
def test_simple_mfa_not_composite(self):
|
||||
"""'MFA implementieren' is atomic, not composite."""
|
||||
assert not _is_composite_obligation(
|
||||
"Multi-Faktor-Authentifizierung implementieren", "MFA"
|
||||
)
|
||||
|
||||
def test_simple_policy_not_composite(self):
|
||||
"""'Sicherheitsrichtlinie dokumentieren' is atomic."""
|
||||
assert not _is_composite_obligation(
|
||||
"Eine Sicherheitsrichtlinie dokumentieren und pflegen",
|
||||
"Sicherheitsrichtlinie",
|
||||
)
|
||||
|
||||
def test_encryption_not_composite(self):
|
||||
"""'Daten verschlüsseln' is atomic."""
|
||||
assert not _is_composite_obligation(
|
||||
"Personenbezogene Daten bei der Übertragung verschlüsseln",
|
||||
"Personenbezogene Daten",
|
||||
)
|
||||
|
||||
def test_composite_flags_on_atomic(self):
|
||||
"""_compose_deterministic sets composite flags on the atomic."""
|
||||
atomic = _compose_deterministic(
|
||||
obligation_text="CCM-Praktiken für AIS implementieren",
|
||||
action="implementieren",
|
||||
object_="CCM-Praktiken",
|
||||
parent_title="AI System Controls",
|
||||
parent_severity="high",
|
||||
parent_category="security",
|
||||
is_test=False,
|
||||
is_reporting=False,
|
||||
)
|
||||
assert atomic._is_composite is True # type: ignore[attr-defined]
|
||||
assert atomic._atomicity == "composite" # type: ignore[attr-defined]
|
||||
assert atomic._requires_decomposition is True # type: ignore[attr-defined]
|
||||
|
||||
def test_non_composite_flags_on_atomic(self):
|
||||
"""_compose_deterministic sets atomic flags for non-composite."""
|
||||
atomic = _compose_deterministic(
|
||||
obligation_text="MFA implementieren",
|
||||
action="implementieren",
|
||||
object_="MFA",
|
||||
parent_title="Access Control",
|
||||
parent_severity="high",
|
||||
parent_category="security",
|
||||
is_test=False,
|
||||
is_reporting=False,
|
||||
)
|
||||
assert atomic._is_composite is False # type: ignore[attr-defined]
|
||||
assert atomic._atomicity == "atomic" # type: ignore[attr-defined]
|
||||
assert atomic._requires_decomposition is False # type: ignore[attr-defined]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PROMPT BUILDER TESTS
|
||||
|
||||
Reference in New Issue
Block a user