"""B19 wiring — Cookie-Coherence-Check (Salesforce-as-essential).""" from __future__ import annotations import html import logging from collections import Counter from compliance.services.cookie_coherence_check import check_cookie_coherence logger = logging.getLogger(__name__) def run_b19(state: dict) -> None: # Step 3 — Auto-Learning: alle deklarierten Cookies dieser Site # in cookie_behavior_audits loggen (Cross-Site-Konsens-Basis). try: from compliance.services.cookie_observation_logger import ( log_observations, ) stats = log_observations(state) logger.info("B19 observation-logger: %s", stats) except Exception as e: logger.warning("observation-logger skipped: %s", e) new = check_cookie_coherence(state) if not new: return extras = state.get("extra_findings") or [] extras.extend(new) state["extra_findings"] = extras state["cookie_coherence_html"] = _render(new) state["cookie_coherence_findings"] = new logger.info("B19 cookie-coherence: %d finding(s)", len(new)) def _render(findings: list[dict]) -> str: # Aggregate per type for the summary chip by_type = Counter(f.get("check_id") for f in findings) severity_color = { "HIGH": "#dc2626", "MEDIUM": "#f59e0b", "LOW": "#64748b", } # Show only the top 12 cards in the mail; rest goes to CSV cards = [] for f in findings[:12]: sev = (f.get("severity") or "").upper() color = severity_color.get(sev, "#475569") meta = "" if f.get("cookie_name"): meta += ( "
" f"Cookie: {html.escape(f['cookie_name'])}" f" · Vendor: {html.escape(f.get('vendor') or '?')}" "
" ) if f.get("declared_category"): meta += ( "
" f"declared: {html.escape(f['declared_category'])}" + (f" · actual (KB): {html.escape(f['actual_category'])}" if f.get("actual_category") else "") + "
" ) cards.append( f"
" f"
" f"{sev} · {html.escape(f.get('check_id') or '')}
" f"
" f"{html.escape(f.get('title') or '')}
" f"
" f"{html.escape(f.get('norm') or '')}
" f"{meta}" f"
" f"{html.escape(f.get('evidence') or '')}
" f"
" f"→ Abstellung: " f"{html.escape(f.get('recommended_action') or '')}
" "
" ) type_summary = " · ".join( f"{k.split('-')[-1]}: {v}" for k, v in by_type.most_common() ) return ( "
" "

" f"🍪 Cookie-Kohärenz ({len(findings)} Befunde)" "

" f"

" f"Vergleich Site-Deklaration vs Open Cookie Database (2287) + " f"BreakPilot-KB.
Verteilung: {type_summary}

" + "".join(cards) + (f"

" f"… und {len(findings)-12} weitere — vollständige Liste " f"in cookies-full.csv im ZIP-Anhang.

" if len(findings) > 12 else "") + "
" )