cookie_checks.py:
- cookie_names_listed: now also matches CMP placeholder notation
(BMW: 'Adfpc###', 'CT###') and 'Diese Datenverarbeitung verwendet die
folgenden Cookies oder ähnliche Technologien' as list-shape signal.
Cryptic vendor names like 'audience', 'adformfrpid' are accepted via
the surrounding markup, not by hard-coding each one.
- cookie_providers_named: new pattern 'Gesetzt von: <Firma>' (BMW/ePaaS
per-cookie vendor naming) + recognition of full legal-form names
(Adform A/S, BMW AG, Adobe Systems Software Ireland Limited).
- cookie_duration_values: now matches 'Ablauf: 1 Jahr' / 'Speicherdauer:
30 Tage' (BMW format) in addition to the legacy '<n> <unit>'.
New L1 + L2 checks for controller in cookie-policy:
- cookie_controller (L1): the cookie policy must name Verantwortlich(er)
- cookie_controller_address (L2): PLZ + Ort or address keywords
- cookie_controller_contact_or_link (L2): email/phone OR link back to
Datenschutzerklärung (the practical equivalent — BMW does this)
New L2 checks (parented under opt_out):
- cookie_optout_links: detects per-provider opt-out URLs in the text
- cookie_privacy_policy_links: per-provider privacy-policy URLs
New service: cookie_link_validator.py
- extract_links(text): pulls all https?://… URLs that follow 'Opt-Out
Link:' / 'Link zur Privacy Policy:' (deduped)
- validate_links(links): probes every URL concurrently (HEAD first, GET
fallback for 405/403). 10 parallel, 8s per request, 60s batch cap.
Returns reachable=True/False + status + final_url.
- build_check_items(): renders 2 CheckItems (opt-out + privacy-policy),
each pass if ALL links 2xx/3xx, fail with up-to-5 broken-link examples.
Hook in _check_single: doc_type=='cookie' triggers the validator after
regex+MC checks. Recomputes correctness with the new L2 items.
This addresses two concrete BMW observations:
1. BMW's per-cookie structure (Name + Zweck + Ablauf, Gesetzt von: …,
Opt-Out Link: …) now recognised → 'Konkrete Cookie-Namen aufgelistet'
and 'Konkrete Speicherdauern' should pass.
2. Defective opt-out URLs surface as compliance findings rather than
silently passing — Art. 7(3) DSGVO requires a working withdrawal
path per provider.