From 8e3d05f17294d05ad87da1c776866ae8da04072e Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 7 Jun 2026 00:28:29 +0200 Subject: [PATCH] test(elli-gt): GT-Coverage-Integration-Test + Sprint-Briefing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/test_elli_gt_coverage.py: 7 Charakterisierungstests die einen synthetischen Elli-State konstruieren und sicherstellen, dass die 5 neuen Detektoren (B13-B16 + B9-Cleanup) genau die erwarteten GT-IDs fangen. Regressionsschutz. - zeroclaw/docs/audits/2026-06-06-elli-gt-coverage-sprint.md: Sprint-Zusammenfassung mit GT-Bilanz (12/13 voll, 1/13 wartet auf #7), Commit-Liste und Morgen-Agenda-Kandidaten. Combined Sprint-Test-Run: 72/72 grün. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../tests/test_elli_gt_coverage.py | 210 ++++++++++++++++++ .../2026-06-06-elli-gt-coverage-sprint.md | 201 +++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 backend-compliance/tests/test_elli_gt_coverage.py create mode 100644 zeroclaw/docs/audits/2026-06-06-elli-gt-coverage-sprint.md diff --git a/backend-compliance/tests/test_elli_gt_coverage.py b/backend-compliance/tests/test_elli_gt_coverage.py new file mode 100644 index 00000000..3a4403ab --- /dev/null +++ b/backend-compliance/tests/test_elli_gt_coverage.py @@ -0,0 +1,210 @@ +"""GT-Coverage-Integration-Test gegen den Elli-Audit (2026-06-06). + +Charakterisierungstest für die 5 GT-Lücken die in diesem Sprint +geschlossen wurden. Konstruiert einen synthetischen `state`-dict, der +Elli's reale Befunde widerspiegelt, und stellt sicher, dass jeder +der neuen B13-B16 + B9-Cleanup Detektoren genau die erwarteten +GT-IDs fängt. + +Ground-Truth-Quelle: zeroclaw/docs/ground-truth/elli_eco_2026-06-06.json +""" + +from compliance.services.ai_legal_basis_check import check_ai_legal_basis +from compliance.services.impressum_multi_entity_check import ( + check_multi_entity_impressum, +) +from compliance.services.retention_conflict_check import ( + check_retention_conflicts, +) +from compliance.services.url_slug_drift_check import check_url_slug_drift +from compliance.services.widerrufsbelehrung_reachability_check import ( + check_widerrufsbelehrung_reachability, +) + + +# Elli-Fixture: minimaler State mit den Datenfeldern die jeder Check +# braucht. Texte sind verdichtet, enthalten aber jedes prüf-relevante +# Signal (Vertex AI / lit. f, Logfile-Widerspruch, B2C-Shop, etc.). +_ELLI_IMPRESSUM = """ +Impressum + +Volkswagen Group Charging GmbH +Karl-Liebknecht-Str. 32, 10178 Berlin +Amtsgericht Charlottenburg HRB 208967 B +Geschäftsführer: Giovanni Palazzo, Mark Möller +Telefon: 00800 3554 1111 + +Elli Mobility GmbH +Karl-Liebknecht-Str. 32, 10178 Berlin +Amtsgericht Charlottenburg HRB 274616 B +USt-IdNr.: DE814424009 +Geschäftsführer: Joschi Jennermann, Sebastian Steffen +""" + +_ELLI_DSE = """ +Datenschutzerklärung der Elli Mobility GmbH. + +Server-Logfiles: Bei jedem Zugriff werden Logfiles gespeichert. +Die Logfiles werden für 7 Tage gespeichert. Bei Sicherheitsvorfällen +werden Logfiles bis zu 30 Tage aufbewahrt. + +AI Assistant: Für unseren Chatbot setzen wir Google Vertex AI ein. +Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO (berechtigtes +Interesse). Speicherdauer 6 Monate. + +Newsletter-Abonnement: Wir speichern Anmeldedaten 24 Monate ab +Abmeldung. +""" + +_ELLI_HOMEPAGE = ( + "Elli — Charging Solutions. In den Warenkorb. Lieferzeit 2 Tage. " + "Preis inkl. MwSt. Wallbox. Naturstrom. Ladetarif buchen." +) + + +def _build_elli_state(widerruf_reachable: bool = False) -> dict: + doc_entries = [ + {"doc_type": "impressum", + "url": "https://www.elli.eco/de/impressum", + "text": _ELLI_IMPRESSUM, + "auto_discovered": True, + "discovery_attempted": True}, + {"doc_type": "dse", + "url": "https://www.elli.eco/de/datenschutz", + "text": _ELLI_DSE, + "auto_discovered": True, + "discovery_attempted": True}, + {"doc_type": "cookie", + "url": "https://www.elli.eco/de/cookies", + "text": "x" * 600, + "auto_discovered": True, + "discovery_attempted": True}, + {"doc_type": "widerruf", + "url": "https://www.elli.eco/de/widerruf" if widerruf_reachable + else "", + "text": ("x" * 600) if widerruf_reachable else "", + "auto_discovered": widerruf_reachable, + "discovery_attempted": True}, + ] + return { + "doc_entries": doc_entries, + "doc_texts": { + "impressum": _ELLI_IMPRESSUM, + "dse": _ELLI_DSE, + }, + "home_text": _ELLI_HOMEPAGE, + } + + +# ──────────────────────────────────────────────────────────────────────── +# B9 cleanup — IMPRESSUM-001 +# ──────────────────────────────────────────────────────────────────────── + +class TestImpressum001Coverage: + def test_per_entity_ust_id_detected(self): + state = _build_elli_state() + findings = check_multi_entity_impressum(state) + ust = [f for f in findings + if f["check_id"] == "IMPRESSUM-MULTI-UST_ID"] + assert len(ust) == 1 + assert ust[0]["entities_missing"] == [ + "Volkswagen Group Charging GmbH", + ] + assert ust[0]["entities_present"] == ["Elli Mobility GmbH"] + + +# ──────────────────────────────────────────────────────────────────────── +# B13 — WIDERRUFSBELEHRUNG-001 +# ──────────────────────────────────────────────────────────────────────── + +class TestWiderrufsbelehrung001Coverage: + def test_b2c_strong_high_finding(self): + state = _build_elli_state(widerruf_reachable=False) + findings = check_widerrufsbelehrung_reachability(state) + assert len(findings) == 1 + assert findings[0]["check_id"] == "WIDERRUF-REACH-001" + assert findings[0]["severity"] == "HIGH" + assert findings[0]["b2c_scope"] == "b2c_strong" + + def test_widerruf_reachable_suppresses_finding(self): + state = _build_elli_state(widerruf_reachable=True) + assert check_widerrufsbelehrung_reachability(state) == [] + + +# ──────────────────────────────────────────────────────────────────────── +# B14 — TH-RETENTION-001 +# ──────────────────────────────────────────────────────────────────────── + +class TestTHRetention001Coverage: + def test_logfile_7_vs_30_finding(self): + state = _build_elli_state() + findings = check_retention_conflicts(state) + log = [f for f in findings if f["category"] == "logfile"] + assert len(log) == 1 + assert 7.0 in log[0]["values_days"] + assert 30.0 in log[0]["values_days"] + + +# ──────────────────────────────────────────────────────────────────────── +# B15 — AI-ACT-RISK-001 +# ──────────────────────────────────────────────────────────────────────── + +class TestAIActRisk001Coverage: + def test_vertex_ai_lit_f_finding(self): + state = _build_elli_state() + findings = check_ai_legal_basis(state) + assert len(findings) == 1 + assert findings[0]["check_id"] == "AI-LEGAL-BASIS-001" + assert "Vertex" in findings[0]["provider"] + + +# ──────────────────────────────────────────────────────────────────────── +# B16 — URL-STRUCTURE-001 +# ──────────────────────────────────────────────────────────────────────── + +class TestURLStructure001Coverage: + def test_cookie_richtlinie_404_emits_finding(self): + # Mock _head_status: alle Standard-Slugs ausser dem discovered + # liefern 404 — Elli's reale Lage. + from unittest.mock import patch + state = _build_elli_state() + with patch( + "compliance.services.url_slug_drift_check._head_status", + return_value=404, + ): + findings = check_url_slug_drift(state) + cookie_findings = [f for f in findings if f["doc_type"] == "cookie"] + assert len(cookie_findings) == 1 + assert cookie_findings[0]["severity"] == "LOW" + assert "cookie-richtlinie" in cookie_findings[0]["alt_slugs_404"] + + +# ──────────────────────────────────────────────────────────────────────── +# All-Detector-Smoke — sicherstellen, dass die 5 Checks zusammen +# gegen Elli mindestens 5 unterschiedliche Finding-IDs ergeben. +# ──────────────────────────────────────────────────────────────────────── + +class TestElliFullCoverage: + def test_all_five_detectors_fire(self): + from unittest.mock import patch + state = _build_elli_state(widerruf_reachable=False) + all_findings: list[dict] = [] + all_findings.extend(check_multi_entity_impressum(state)) + all_findings.extend(check_widerrufsbelehrung_reachability(state)) + all_findings.extend(check_retention_conflicts(state)) + all_findings.extend(check_ai_legal_basis(state)) + with patch( + "compliance.services.url_slug_drift_check._head_status", + return_value=404, + ): + all_findings.extend(check_url_slug_drift(state)) + check_ids = {f["check_id"] for f in all_findings} + expected = { + "IMPRESSUM-MULTI-UST_ID", + "WIDERRUF-REACH-001", + "RETENTION-CONFLICT-001", + "AI-LEGAL-BASIS-001", + "URL-SLUG-DRIFT-001", + } + missing = expected - check_ids + assert not missing, f"GT-Lücken nicht gefangen: {missing}" diff --git a/zeroclaw/docs/audits/2026-06-06-elli-gt-coverage-sprint.md b/zeroclaw/docs/audits/2026-06-06-elli-gt-coverage-sprint.md new file mode 100644 index 00000000..198bc3f0 --- /dev/null +++ b/zeroclaw/docs/audits/2026-06-06-elli-gt-coverage-sprint.md @@ -0,0 +1,201 @@ +# Sprint-Zusammenfassung: Elli-GT-Coverage (Stand 2026-06-06) + +## TL;DR + +Engine erkennt jetzt **5 GT-Lücken zusätzlich**, die im Elli-Audit +(`zeroclaw/docs/ground-truth/elli_eco_2026-06-06.json`) als +"muss-detect" markiert waren. Plus ein P0-Bug-Fix, der jeden +Compliance-Check still auf "failed" umschlagen ließ. + +**Test-Status:** 72/72 grün (5 neue Unit-Test-Suites + 1 GT-Coverage- +Integration-Suite). + +**Deploy:** alle 6 Commits auf `main`, Production-Container healthy +(`https://api-dev.breakpilot.ai/health` + `https://sdk-dev.breakpilot.ai/health`). + +## Was wurde gebaut + +### 0. Bug-Fix Orchestrator (P0, blocking) + +| Commit | Inhalt | +|--------|--------| +| `11c7e14` | `_orchestrator.py`: fehlende Imports `run_b12` + `run_phase_c2` ergänzt. Ohne Fix: NameError → try/except → jeder Check auf "failed". | + +### 1. B13 — Widerrufsbelehrung-Reachability (GT WIDERRUFSBELEHRUNG-001) + +| Datei | Zweck | +|-------|-------| +| `services/widerrufsbelehrung_reachability_check.py` | Check + B2C-Scope-Detection | +| `api/agent_check/_b13_wiring.py` | Orchestrator-Anschluss + HTML-Block | +| `services/mail_render_v2/_compose.py` | `widerruf_reach_html` in V2-Layout | +| `tests/test_widerrufsbelehrung_reachability_check.py` | 13 Tests | + +**Logik:** Wenn doc_type `widerruf` discovery_attempted=True, kein +ausreichend langer Text, kein Footer-Link **und** B2C-Scope erkannt +(Warenkorb / Wallbox / MwSt) → HIGH Finding. B2B-only-Override schützt +vor False-Positives. + +**Norm:** Art. 246a § 1 Abs. 2 Nr. 1 EGBGB i.V.m. § 312d BGB. + +### 2. B9 Cleanup — Per-Entity USt-IdNr (GT IMPRESSUM-001) + +| Datei | Zweck | +|-------|-------| +| `services/impressum_multi_entity_check.py` | Entity-Namen-Cleanup | +| `tests/test_impressum_multi_entity_check.py` | 11 neue Tests | + +**Problem:** Multi-Entity-Check fand die fehlende USt-IdNr bei VW +Group Charging GmbH bereits, aber Entity-Namen waren mit +Header-Müll verunreinigt (`'Impressum\n\nVolkswagen ...'`). + +**Fix:** `_ENTITY_PAT` lässt nur Space im Namen zu (kein `\s`); +`_clean_entity_name()` strippt Header-Worte und nimmt nur die letzte +Zeile vor Legal-Form-Suffix. + +### 3. B14 — Widersprüchliche Speicherdauer (GT TH-RETENTION-001) + +| Datei | Zweck | +|-------|-------| +| `services/retention_conflict_check.py` | Satz-Boundary-Scan + Cluster-Logik | +| `api/agent_check/_b14_wiring.py` | Wiring + HTML-Block | +| `tests/test_retention_conflict_check.py` | 11 Tests | + +**Logik:** Sätze splitten, pro Satz: Kategorie-Anker (logfile, +contact_form, application, newsletter, invoice, session_cookie) + +Retention-Werte beide drin? Tage-Cluster mit ±20% Toleranz: `30 Tage` +und `1 Monat` = 1 Cluster, `7 Tage` und `30 Tage` = 2 Cluster → MEDIUM +Finding. + +**Anti-Pattern vermieden:** Frühe Version nutzte ±300-Zeichen-Fenster +und produzierte Cross-Category-Leakage — Satz-Boundary fixt das. + +**Norm:** DSGVO Art. 5 Abs. 1 lit. a + Art. 13 Abs. 2 lit. a. + +### 4. B15 — AI-Act Rechtsgrundlage (GT AI-ACT-RISK-001) + +| Datei | Zweck | +|-------|-------| +| `services/ai_legal_basis_check.py` | LLM-Vendor-Erkennung + lit.f-Pattern | +| `api/agent_check/_b15_wiring.py` | Wiring + HTML-Block | +| `tests/test_ai_legal_basis_check.py` | 17 Tests | + +**Logik:** Aus KB (`chat_providers.json`) nur Echte-LLM-Vendors +filtern (Vertex AI, OpenAI/GPT, Anthropic/Claude). Absatz-Split, +pro Absatz: LLM-Mention **und** lit. f / berechtigtes Interesse → +MEDIUM Finding. Negativ-Filter: wenn auch lit. a / Einwilligung im +Absatz, skip (Side-Purpose). + +**Norm:** DSGVO Art. 6 Abs. 1 lit. a vs lit. f + AI Act Art. 50 + 51. + +### 5. B16 — Footer-Label-vs-URL-Slug-Drift (GT URL-STRUCTURE-001) + +| Datei | Zweck | +|-------|-------| +| `services/url_slug_drift_check.py` | Aktive HEAD-Probes | +| `api/agent_check/_b16_wiring.py` | Wiring + HTML-Block | +| `tests/test_url_slug_drift_check.py` | 13 Tests | + +**Logik:** Aus auto-discovered URLs Origin + Sprach-Prefix +extrahieren, pro doc_type 2-4 kanonische Standard-Slugs probieren +(ThreadPoolExecutor, 2s Timeout, HEAD → GET-Fallback bei 405). Wenn +alternativer Slug 404/410 → LOW Finding. Cap 18 Probes total, +abschaltbar via `URL_SLUG_PROBE_DISABLED=1`. + +**Severity:** LOW — kein juristisches Hardfail, aber SEO/Bookmark- +Bruch. + +### 6. GT-Coverage-Integration-Test + +`tests/test_elli_gt_coverage.py` — 7 Tests, die einen synthetischen +Elli-State konstruieren und sicherstellen, dass jeder der 5 neuen +Detektoren genau die erwartete GT-ID fängt. Charakterisierungstest +gegen Regressions. + +## GT-Coverage-Bilanz + +Vor dem Sprint (12/13): + +| GT-ID | Status | +|-------|--------| +| COOKIE-CONSENT-UX-001 | ❌ blockiert auf #7 Mobile Playwright | +| TH-RETENTION-001 | ❌ Engine sah nur Single-Value | +| AI-ACT-RISK-001 | ❌ Lit. f vs LLM-Vendor nicht korreliert | +| IMPRESSUM-001 | ⚠️ erkannt, aber Entity-Namen verunreinigt | +| WIDERRUFSBELEHRUNG-001 | ❌ kein B2C-Reachability-Check | +| URL-STRUCTURE-001 | ❌ keine aktiven Slug-Probes | +| Rest (7 IDs) | ✅ | + +Nach dem Sprint (12/13 voll + 1 partial): + +| GT-ID | Status | Check | +|-------|--------|-------| +| COOKIE-CONSENT-UX-001 | ⏳ wartet auf #7 | (Mobile Playwright) | +| COOKIE-CONSENT-UX-002 | ✅ | B9 CMP-Provider | +| TH-RETENTION-001 | ✅ NEU | B14 | +| TH-RETENTION-002 | ✅ | B12 | +| VENDOR-CONSISTENCY-001 | ✅ | B4 | +| AI-ACT-TRANSPARENCY-001 | ✅ | B5 | +| AI-ACT-RISK-001 | ✅ NEU | B15 | +| IMPRESSUM-001 | ✅ NEU (clean) | B9 | +| IMPRESSUM-002 | ✅ | B6 DPO-Cross-Doc | +| WIDERRUFSBELEHRUNG-001 | ✅ NEU | B13 | +| TERMS-STALENESS-001 | ✅ | B7 | +| TRANSFER-001 | ✅ | B10 | +| URL-STRUCTURE-001 | ✅ NEU | B16 | + +**Endstand: 12/13 vollständig erkannt, 1/13 blockiert auf #7 +Mobile-Playwright-Sprint.** + +## Commit-Liste + +``` +65e8bb9 feat(b16): Footer-Label-vs-URL-Slug-Drift-Check +b0b7f80 feat(b15): AI-Act Rechtsgrundlage-Check +6aad774 feat(b14): widersprüchliche Speicherdauer im selben Doc +8b9cad8 fix(b9): clean entity names in multi-entity-impressum +b9baa8c feat(b13): Widerrufsbelehrung-Reachability-Check +11c7e14 fix(orchestrator): add missing run_b12 + run_phase_c2 imports +``` + +## Was offen ist (Morgen-Agenda-Kandidaten) + +1. **#7 — B1 Mobile-Playwright-Sprint.** Letzte offene GT-Lücke + (COOKIE-CONSENT-UX-001 — Mobile-Reachability für Consent-Reopen). + Pflicht-Trigger für 13/13. + +2. **#18 — Specialist-Agents Phase 2 (LLM-gestützt).** Bisher nur + Pattern-Match-Impressum-Agent (`specialist_agents/impressum_agent.py`), + nicht im Orchestrator integriert. Phase 2 sollte denselben Output + produzieren, aber LLM-basiert + Cross-Customer-KB. + +3. **Plausibility-LLM-Empty-Response.** Bekannter Bug, non-blocking, + aber Quality-Improvement-Ziel — `_phase_d2b_plausibility.py` + liefert gelegentlich leere LLM-Antworten. + +4. **Browser-Matrix Stage 1.b (consent_scanner-Refactor proper).** + Aktuell via Workaround `cookie_behavior_per_browser.py` umgangen; + `consent_scanner.py` 567 LOC ist in `loc-exceptions.txt` getragen. + +5. **Real-World-Smoke gegen Elli.** Den vollen Compliance-Check + gegen `www.elli.eco` triggern und prüfen, wieviele der 5 neuen + Findings tatsächlich emittiert werden (Test war synthetisch). + +## Test-Bilanz + +``` +test_widerrufsbelehrung_reachability_check.py 13/13 +test_impressum_multi_entity_check.py 11/11 +test_retention_conflict_check.py 11/11 +test_ai_legal_basis_check.py 17/17 +test_url_slug_drift_check.py 13/13 +test_elli_gt_coverage.py 7/7 + ──── +Sprint-Tests gesamt 72/72 +``` + +## URLs + +- Admin: https://admin-dev.breakpilot.ai +- Backend Health: https://api-dev.breakpilot.ai/health +- SDK Health: https://sdk-dev.breakpilot.ai/health +- GT-Quelle: `zeroclaw/docs/ground-truth/elli_eco_2026-06-06.json`