test(elli-gt): GT-Coverage-Integration-Test + Sprint-Briefing
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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}"
|
||||
@@ -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`
|
||||
Reference in New Issue
Block a user