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:
Benjamin Admin
2026-03-23 12:11:55 +01:00
parent 1a63f5857b
commit 48ca0a6bef
8 changed files with 2744 additions and 18 deletions

View File

@@ -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