3c6deac1c5
- Linter: FORBIDDEN_OUTPUT_TERMS per Wortgrenze → 'Schutzgarantien'/'geeignete Garantien' (Art. 46) passieren, 'garantiert'-Claims bleiben geblockt. - DSE: L2-Detail wird übersprungen statt 'na', wenn die L1-Pflichtangabe fehlt (kein irreführendes 'nicht anwendbar' für z.B. Transfermechanismus). - DSE: Drittland → HIGH bei dokumentiertem Drittlandtransfer (scan_context via AgentInput.context) — BMW (Konzern, US-Provider) ist kein weiches MEDIUM. - DSE: Titel/Maßnahme kurz (treibt den Recommendation-Titel); ausführliche Begründung als evidence — behebt 120-Zeichen-abgeschnittene Überschriften. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
66 lines
2.7 KiB
Python
66 lines
2.7 KiB
Python
"""DSEAgent — kuratierte Art-13/14-Checkliste (kein Library-Firehose)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
from compliance.services.specialist_agents import REGISTRY, AgentInput
|
|
|
|
|
|
def _run(text: str):
|
|
return asyncio.run(
|
|
REGISTRY.get("dse").evaluate(AgentInput(doc_type="dse", text=text)))
|
|
|
|
|
|
def test_dse_agent_registered():
|
|
assert REGISTRY.get("dse") is not None
|
|
|
|
|
|
def test_dse_detects_core_obligations():
|
|
text = (
|
|
"Datenschutzerklaerung. Verantwortlich im Sinne der DSGVO ist die "
|
|
"Muster GmbH, Musterstrasse 1, 12345 Berlin. E-Mail: info@muster.de. "
|
|
"Datenschutzbeauftragter: dsb@muster.de. Zwecke der Verarbeitung und "
|
|
"Rechtsgrundlage Art. 6 Abs. 1. Empfaenger Ihrer Daten. Speicherdauer "
|
|
"der Daten. Ihre Rechte: Auskunft, Loeschung, Widerspruch, Beschwerde "
|
|
"bei der Aufsichtsbehoerde. ") * 3
|
|
out = _run(text)
|
|
assert out.agent == "dse"
|
|
# 10 L1-Pflichtangaben immer + L2-Details deren Parent vorhanden ist
|
|
# (fehlende Parents → L2 übersprungen, kein 'na'-Rauschen).
|
|
assert 10 <= out.mc_total <= 33
|
|
ok = [c.label for c in out.mc_coverage if c.status == "ok"]
|
|
assert any("Verantwortlich" in lbl for lbl in ok)
|
|
assert any("Rechtsgrundlage" in lbl for lbl in ok)
|
|
|
|
|
|
def test_dse_missing_obligations_are_findings():
|
|
out = _run("Lorem ipsum dolor sit amet consectetur adipiscing elit. " * 6)
|
|
assert out.findings
|
|
assert any(f.severity == "HIGH" for f in out.findings)
|
|
|
|
|
|
def test_dse_short_text_skips():
|
|
out = _run("zu kurz")
|
|
assert out.confidence == 0.0
|
|
assert all(c.status == "skipped" for c in out.mc_coverage)
|
|
|
|
|
|
def test_third_country_high_when_applicable_no_na_detail_short_action():
|
|
# Text ohne Drittland-Abschnitt + Scan-Kontext drittland=ja:
|
|
# - third_country (L1) fehlt → HIGH (nicht weiches MEDIUM)
|
|
# - Transfermechanismus (L2) → KEIN 'na' (übersprungen, Parent deckt ab)
|
|
# - Titel/Maßnahme kurz (kein 280-Zeichen-Hint als Recommendation-Titel)
|
|
text = ("Datenschutz. Verantwortlich ist die Muster GmbH, info@muster.de. "
|
|
"Zwecke und Rechtsgrundlage Art. 6. Speicherdauer. Ihre Rechte. ") * 4
|
|
out = asyncio.run(REGISTRY.get("dse").evaluate(AgentInput(
|
|
doc_type="dse", text=text,
|
|
context={"scan_context": {"third_country_transfer": "yes"}})))
|
|
tc = [f for f in out.findings if "Drittland" in f.title]
|
|
assert tc and tc[0].severity == "HIGH"
|
|
assert not any(c.status == "na" and "Transfermechanismus" in c.label
|
|
for c in out.mc_coverage)
|
|
assert all(len(f.action) < 110 for f in out.findings)
|
|
# Detail-Begründung bleibt als evidence erhalten
|
|
assert any(f.evidence for f in out.findings)
|