fix(dse+linter): Drittland-Applicability, kein na-Detail, kurze Titel, Linter-Wortgrenzen
- 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>
This commit is contained in:
@@ -26,7 +26,9 @@ def test_dse_detects_core_obligations():
|
||||
"bei der Aufsichtsbehoerde. ") * 3
|
||||
out = _run(text)
|
||||
assert out.agent == "dse"
|
||||
assert out.mc_total == 33 # ART13_CHECKLIST komplett
|
||||
# 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)
|
||||
@@ -42,3 +44,22 @@ 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)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Disclaimer-Linter: Wort-Grenzen — Rechtsbegriffe passieren, Claims geblockt."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from compliance.services.specialist_agents._base import (
|
||||
AgentOutput,
|
||||
Finding,
|
||||
Severity,
|
||||
lint_output,
|
||||
)
|
||||
|
||||
|
||||
def _out(action: str) -> AgentOutput:
|
||||
now = datetime.now(timezone.utc)
|
||||
f = Finding(check_id="X", agent="t", agent_version="1",
|
||||
severity=Severity.MEDIUM, title="Titel", action=action)
|
||||
return AgentOutput(agent="t", agent_version="1", started_at=now,
|
||||
finished_at=now, duration_ms=0, findings=[f])
|
||||
|
||||
|
||||
def test_schutzgarantien_not_scrubbed():
|
||||
out = lint_output(_out("Geeignete Schutzgarantien nach Art. 46 angeben."))
|
||||
assert "Schutzgarantien" in out.findings[0].action
|
||||
assert "neutraler Wortlaut" not in out.findings[0].action
|
||||
|
||||
|
||||
def test_garantiert_claim_still_blocked():
|
||||
out = lint_output(_out("Dies ist garantiert konform."))
|
||||
assert "garantiert" not in out.findings[0].action.lower()
|
||||
assert "neutraler Wortlaut" in out.findings[0].action
|
||||
Reference in New Issue
Block a user