feat(impressum-agent): Tesla-Pattern + KBA-Hint + News-Doc-Type
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m20s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 30s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 6s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped

User-Feedback Tesla-Impressum: 10 FAIL bei 46 Worten — viele False-
Positives. Nach Tuning: 5 juristisch saubere Findings.

Impressum-Agent Patterns:
  - name_anbieter zusätzlich label-frei matchen (Firma+Rechtsform+
    Anschrift, Tesla schreibt ohne "Anbieter:" Label).
  - vertretungsberechtigte akzeptiert jetzt "Management" / "Director"
    als alternative (US-Konzern-Habit), aber emittiert separates
    Sub-Finding "Label sollte Geschäftsführer für § 5 TMG sein".
  - aufsichtsbehoerde-Pattern um KBA / Bundesnetzagentur erweitert.
  - NEU: verantwortlicher_redaktion (§ 18 MStV bei Blog/News).
  - NEU: verbraucher_streitbeilegung (§ 36 VSBG bei B2C).
  - Auto-Detection von Automotive-Branche: explizite Begriffe ODER
    bekannte Hersteller-Namen (Tesla/BMW/Mercedes/Audi/VW/Porsche…).
    Triggert KBA-Hint im aufsichtsbehoerde-Finding-Action.

Frontend (_document_types.ts):
  - Extrahiert aus ComplianceCheckTab.tsx (vorher inline).
  - NEU: doc_type "news" für Blog/Newsroom-URL → § 18 MStV-Pflicht-
    angaben prüfen. User-Hinweis: tesla.com/de_de/blog ist
    relevanter Audit-Input neben DSE/Impressum.

Smoke gegen Tesla-Impressum (46 Worte):
  Vorher 10 Findings (5 davon FP).
  Jetzt 5 Findings — alle juristisch korrekt:
    [MED] Management statt Geschäftsführer
    [LOW] KBA als Aufsichtsbehörde fehlt
    [MED] § 18 MStV-Verantwortlicher fehlt (Tesla Blog!)
    [MED] § 36 VSBG-Hinweis fehlt
    [MED] ODR-Plattform-Link fehlt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-08 12:07:08 +02:00
parent d208a2bde2
commit b4ce3528e5
2 changed files with 135 additions and 11 deletions
@@ -0,0 +1,21 @@
/**
* DOCUMENT_TYPES — canonical compliance-doc taxonomy for the
* /sdk/agent ComplianceCheckTab form.
*
* Each entry maps to a doc_type that the backend Phase-A discovery /
* Phase-B per-doc-check pipeline recognises.
*/
export const DOCUMENT_TYPES = [
{ id: 'dse', label: 'DSI (Datenschutzinformation)', required: true },
{ id: 'impressum', label: 'Impressum', required: true },
{ id: 'social_media', label: 'Social Media DSE', required: false },
{ id: 'cookie', label: 'Cookie-Richtlinie', required: false },
{ id: 'agb', label: 'AGB', required: false },
{ id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen', required: false },
{ id: 'widerruf', label: 'Widerrufsbelehrung', required: false },
{ id: 'dsb', label: 'DSB-Kontakt', required: false },
{ id: 'news', label: 'Blog/Newsroom (für § 18 MStV)', required: false },
] as const
export type DocTypeId = typeof DOCUMENT_TYPES[number]['id']
@@ -29,9 +29,20 @@ PFLICHTANGABEN = {
"label": "Name + Anschrift des Anbieters",
"norm": "§ 5 Abs. 1 Nr. 1 TMG",
"patterns": [
# Label-Form: "Anbieter:", "Diensteanbieter:", "Verantwortlicher:"
re.compile(r"\b(?:Anbieter|Diensteanbieter|"
r"Verantwortlich(?:er Anbieter)?)\s*[:.\s]",
re.IGNORECASE),
# Label-frei: Firma (Rechtsform) + Strasse + PLZ — Tesla-
# Pattern. Wenn das Impressum ohne explizites "Anbieter:"-
# Label direkt mit Firma+Adresse loslegt, gilt das auch.
re.compile(
r"\b[A-ZÄÖÜ][\w\-\& ]{1,80}?\s+"
r"(?:GmbH|AG|UG|KG|SE|GbR|OHG|Limited|Ltd|LLC)\s*"
r"[\s\S]{0,400}?"
r"\b\d{5}\s+[A-ZÄÖÜ]",
re.IGNORECASE,
),
],
"severity_if_missing": "HIGH",
},
@@ -75,21 +86,76 @@ PFLICHTANGABEN = {
"label": "Vertretungsberechtigte Person",
"norm": "§ 5 Abs. 1 Nr. 1 TMG (juristische Personen)",
"patterns": [
re.compile(r"(?:Geschäftsführer|Vertretungsberechtigt|"
r"vertreten\s+durch)\s*[:.\s]",
# Korrekte Label: "Geschäftsführer:", "Vertretungsberechtigt:"
re.compile(r"(?:Gesch(?:ae|ä)ftsf(?:ue|ü)hrer|"
r"Vertretungsberechtigt|vertreten\s+durch)"
r"\s*[:.\s]",
re.IGNORECASE),
# US-Konzern-Habit: "Management" als nicht-rechtskonformes
# Alternativlabel (Tesla, Apple, etc.). Erkennen als
# vorhanden — separater Sub-Finding meldet die Label-
# Korrekturpflicht.
re.compile(r"\bManagement\s*[:.\s]\s*[A-ZÄÖÜ]", re.IGNORECASE),
re.compile(r"\bDirector(?:s|en)?\s*[:.\s]\s*[A-ZÄÖÜ]",
re.IGNORECASE),
],
"severity_if_missing": "HIGH",
},
"vertretungsberechtigte_label_korrekt": {
"label": "Korrekte Bezeichnung 'Geschäftsführer' statt 'Management'",
"norm": "§ 5 Abs. 1 Nr. 1 TMG (Deutsch-Pflicht, gerichtsfest)",
"patterns": [
# PASSED nur wenn DEUTSCHES Label vorhanden.
re.compile(r"(?:Gesch(?:ae|ä)ftsf(?:ue|ü)hrer|"
r"Vorstand|"
r"Vertretungsberechtigt|vertreten\s+durch)"
r"\s*[:.\s]",
re.IGNORECASE),
],
"severity_if_missing": "MEDIUM",
},
"aufsichtsbehoerde": {
"label": "Aufsichtsbehörde (regulierte Branchen)",
"norm": "§ 5 Abs. 1 Nr. 3 TMG (Branchen-bedingt)",
"patterns": [
re.compile(r"Aufsichtsbeh(?:ö|oe)rde\s*[:.\s]", re.IGNORECASE),
re.compile(r"\bBAFin\b|\bBNetzA\b|\bLKA\b", re.IGNORECASE),
re.compile(
r"\bBAFin\b|\bBNetzA\b|\bLKA\b|\bKBA\b|"
r"Kraftfahrt-?Bundesamt|Bundesnetzagentur|"
r"Bundesanstalt\s+f(?:ü|ue)r",
re.IGNORECASE,
),
],
"severity_if_missing": "LOW",
},
"verantwortlicher_redaktion": {
"label": "Verantwortlicher § 18 MStV (journalistisch-redaktionell)",
"norm": "§ 18 MStV (bei Blog/News/Magazin/Newsroom Pflicht)",
"patterns": [
re.compile(
r"(?:Verantwortlich(?:er|e)?\s+(?:f(?:ue|ü)r|i\.S\.d\.|"
r"nach|gem(?:ae|ä)ß)\s+§\s*18|"
r"V\.i\.S\.d\.\s*§?\s*18|"
r"redaktionell\s+Verantwortlich)",
re.IGNORECASE,
),
],
"severity_if_missing": "MEDIUM",
},
"verbraucher_streitbeilegung": {
"label": "Verbraucher-Streitbeilegung-Hinweis",
"norm": "§ 36 VSBG (B2C-Anbieter Pflicht)",
"patterns": [
re.compile(
r"(?:Verbraucherschlichtungs|VSBG|"
r"Streitbeilegung|"
r"Schlichtungsstelle|"
r"alternative\s+Streit(?:beilegung|schlichtung))",
re.IGNORECASE,
),
],
"severity_if_missing": "MEDIUM",
},
"berufsangaben": {
"label": "Berufsbezeichnung + Berufsrechtliche Angaben",
"norm": "§ 5 Abs. 1 Nr. 5 TMG (Kammerberufe)",
@@ -122,23 +188,63 @@ def evaluate(impressum_text: str,
return []
business_scope = business_scope or set()
findings: list[dict] = []
# Auto-detect KFZ-Hersteller / Auto-Direktvertrieb für KBA-Hint.
# Erst: explizite Begriffe (Fahrzeug, Automobil, …).
# Dann: bekannte Hersteller-Namen (BMW, Tesla, Mercedes, …).
is_automotive = bool(re.search(
r"\b(?:KFZ|Fahrzeug(?:e|herstellung|verkauf)?|Automobil|"
r"E-Auto|Elektroauto|Auto-?Konfigurator|"
r"Elektrofahrzeug|Hybrid-?Fahrzeug)\b",
impressum_text, re.IGNORECASE,
)) or bool(re.search(
r"\b(?:Tesla|BMW|Mercedes-?Benz|Audi|Volkswagen|Porsche|"
r"Volvo|Stellantis|Skoda|Seat|Cupra|MINI|Smart|"
r"Opel|Ford\s+Deutschland|Hyundai|Kia|Toyota|Mazda|"
r"Nissan|Honda|Subaru|Lexus|Polestar|NIO|BYD|Rivian|"
r"Lucid)\s+(?:Germany|Deutschland|Group|Holding|AG|"
r"GmbH|S(?:E|\.A\.))\b",
impressum_text, re.IGNORECASE,
))
for field_id, spec in PFLICHTANGABEN.items():
# Skip context-dependent fields when scope doesn't match
if field_id == "odr_link" and "ecommerce" not in business_scope:
continue
if field_id == "aufsichtsbehoerde" and (
"regulated_profession" not in business_scope
and "financial_services" not in business_scope
and "insurance" not in business_scope
if field_id == "aufsichtsbehoerde" and not (
"regulated_profession" in business_scope
or "financial_services" in business_scope
or "insurance" in business_scope
or is_automotive
):
continue
if field_id == "berufsangaben" and (
"regulated_profession" not in business_scope
):
continue
if field_id == "verantwortlicher_redaktion" and (
"editorial" not in business_scope
):
continue
if field_id == "verbraucher_streitbeilegung" and (
"ecommerce" not in business_scope
and "b2c" not in business_scope
):
continue
found = any(p.search(impressum_text) for p in spec["patterns"])
if found:
continue
# Context-aware action: KBA-Hint bei Auto-Branche
action = (
f"{spec['label']} im Impressum ergänzen "
f"(Pflichtangabe nach {spec['norm']})."
)
if field_id == "aufsichtsbehoerde" and is_automotive:
action = (
"Aufsichtsbehörde im Impressum benennen. Für "
"KFZ-Hersteller/-Vertrieb typisch: Kraftfahrt-"
"Bundesamt (KBA), Fördestraße 16, 24944 Flensburg, "
"www.kba.de. Bei Ladestrom-Vertrieb zusätzlich "
"Bundesnetzagentur (BNetzA)."
)
findings.append({
"check_id": f"IMPRESSUM-AGENT-{field_id.upper()}",
"agent": "impressum_agent_v1",
@@ -147,10 +253,7 @@ def evaluate(impressum_text: str,
"severity_reason": "missing",
"title": f"Pflichtangabe '{spec['label']}' fehlt im Impressum",
"norm": spec["norm"],
"action": (
f"{spec['label']} im Impressum ergänzen "
f"(Pflichtangabe nach {spec['norm']})."
),
"action": action,
})
if findings:
logger.info(