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:
@@ -73,6 +73,9 @@ class DSEAgent(BaseSpecialistAgent):
|
||||
async def evaluate(self, agent_input: AgentInput) -> AgentOutput:
|
||||
start = datetime.now(timezone.utc)
|
||||
text = (agent_input.text or "").strip()
|
||||
sc = (agent_input.context or {}).get("scan_context") or {}
|
||||
tc_applies = str(sc.get("third_country_transfer", "")).lower() in (
|
||||
"yes", "true", "1", "ja")
|
||||
coverage: list[McCoverage] = []
|
||||
findings: list[Finding] = []
|
||||
|
||||
@@ -91,44 +94,54 @@ class DSEAgent(BaseSpecialistAgent):
|
||||
continue
|
||||
m = _search(_compiled(c), text)
|
||||
l1_present[c["id"]] = m is not None
|
||||
coverage.append(self._cov(c, m, text))
|
||||
coverage.append(self._cov(c, m, text, tc_applies))
|
||||
if m is None:
|
||||
findings.append(self._finding(c, present=False))
|
||||
findings.append(self._finding(c, False, tc_applies))
|
||||
|
||||
# L2 (vollständig/korrekt?) — nur wenn die übergeordnete L1 vorhanden ist
|
||||
# (sonst kein Doppel-Finding zum selben Mangel).
|
||||
# L2 (vollständig/korrekt?) — nur wenn die übergeordnete L1 da ist. Fehlt
|
||||
# die L1, deckt deren Finding die Lücke ab → KEIN irreführendes 'na'
|
||||
# (nicht anwendbar) für das Detail (z.B. Transfermechanismus bei BMW).
|
||||
for c in ART13_CHECKLIST:
|
||||
if c.get("level", 1) != 2:
|
||||
continue
|
||||
parent = c.get("parent")
|
||||
if parent and not l1_present.get(parent, False):
|
||||
coverage.append(McCoverage(
|
||||
mc_id=c["id"], status="na", label=c["label"],
|
||||
reason="übergeordnete Pflichtangabe fehlt"))
|
||||
continue
|
||||
m = _search(_compiled(c), text)
|
||||
coverage.append(self._cov(c, m, text))
|
||||
coverage.append(self._cov(c, m, text, tc_applies))
|
||||
if m is None:
|
||||
findings.append(self._finding(c, present=True))
|
||||
findings.append(self._finding(c, True, tc_applies))
|
||||
|
||||
return self._finalize(start, findings, coverage, 0.7, "")
|
||||
|
||||
def _cov(self, c: dict, m, text: str) -> McCoverage:
|
||||
@staticmethod
|
||||
def _eff_sev(c: dict, tc_applies: bool) -> str:
|
||||
"""Drittland ist bei dokumentiertem Drittlandtransfer (Scan-Kontext)
|
||||
keine weiche MEDIUM-Empfehlung mehr, sondern HIGH (Konzern/US-Provider)."""
|
||||
if tc_applies and c["id"] in ("third_country", "third_country_mechanism"):
|
||||
return "HIGH"
|
||||
return c.get("severity", "MEDIUM")
|
||||
|
||||
def _cov(self, c: dict, m, text: str, tc_applies: bool) -> McCoverage:
|
||||
if m is not None:
|
||||
return McCoverage(
|
||||
mc_id=c["id"], status="ok", label=c["label"],
|
||||
reason="Pattern-Treffer",
|
||||
found=_match_value(text, m.start(), m.end()))
|
||||
sev = c.get("severity", "MEDIUM")
|
||||
sev = self._eff_sev(c, tc_applies)
|
||||
return McCoverage(
|
||||
mc_id=c["id"], status=_COV_FAIL.get(sev, "medium"),
|
||||
label=c["label"],
|
||||
reason="fehlt" if c.get("level", 1) == 1 else "Detail unvollständig")
|
||||
|
||||
def _finding(self, c: dict, present: bool) -> Finding:
|
||||
sev = c.get("severity", "MEDIUM")
|
||||
def _finding(self, c: dict, present: bool, tc_applies: bool) -> Finding:
|
||||
sev = self._eff_sev(c, tc_applies)
|
||||
# Titel + Maßnahme bewusst KURZ (treibt den Recommendation-Titel); die
|
||||
# ausführliche Begründung steht als evidence auf der Finding-Karte.
|
||||
title = (f"{c['label']}: Detail unvollständig" if present
|
||||
else f"{c['label']} fehlt")
|
||||
action = (f"{c['label']} präzisieren." if present
|
||||
else f"{c['label']} in der Datenschutzerklärung ergänzen.")
|
||||
return Finding(
|
||||
check_id=f"DSE-{c['id']}",
|
||||
agent=self.agent_id, agent_version=self.agent_version,
|
||||
@@ -137,7 +150,7 @@ class DSEAgent(BaseSpecialistAgent):
|
||||
severity_reason=("detail_incomplete" if present
|
||||
else "pflichtangabe_missing"),
|
||||
title=title, norm=_norm_of(c["label"]),
|
||||
action=c.get("hint", ""), confidence=0.7,
|
||||
action=action, evidence=(c.get("hint") or "")[:280], confidence=0.7,
|
||||
sources=[EvidenceSource(
|
||||
source_type=SourceType.REGEX, source_id=c["id"],
|
||||
detail="kein Pattern-Treffer", confidence=0.7)],
|
||||
|
||||
Reference in New Issue
Block a user