- master-controls route: guard all mapping queries with hasMappingTables() so
an unseeded DB degrades to empty filters instead of a 500.
- docker-compose: MinIO endpoint/keys/secure overridable via env (prod defaults
preserved) — enables per-environment local config.
- migration 151: reproducible iace_projects.parent_project_id (was ad-hoc).
[migration-approved]
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-existing tech-debt file (~535 LOC in the CI tree) that grew past the
500-line hard cap and has blocked the repo-wide loc-budget check since #657.
Not related to the IACE work in flight. Documented with a Phase-2 split
rationale; the exceptions list stays the escape hatch the check itself points to.
[guardrail-change]
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 2: Live-Filter an /sdk/master-controls (Use Case, Quell-Regulierung,
Verifikations-Methode, Coverage, Primärzweck-Toggle, category via Member-EXISTS).
API mit EXISTS-Filtern + gecachten Meta-Counts in master-controls/route.ts.
Phase A: neue UseCase telekommunikation + Fix der Impressum-Fehlrouten im
Register (TKG/AT-TKG->telekommunikation, telemedien->dse, GewO->handelsrecht);
echte Impressum-Quellen (TMG/Mediengesetz) bleiben impressum. Deterministischer
Seed aus source_regulation; Tests grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnosis: engine F mean 3.56 vs professional 2.56; the dominant disagreement was
normal-operation hazards getting F=4 where the professional assigned 2. Lowered
the lifecycle→F mapping (normal operation 4→3, occasional phases 3→2). New
TestGT_RiskComparison_CrossGT runs the exact production comparison on BOTH GTs:
F within±1 rose to 95% (robot cell) and 94% (lift) — generic, not lift-tuned.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "Zugeordnet" tab already expanded to a GT-vs-Engine detail comparison; the
"Fehlend" and "Engine Findings" tabs were flat and could not be inspected.
Extracted GTDetailBlock / EngineDetailBlock from DetailComparison and made both
tables expandable (chevron) — missing rows show the full GT entry, extra rows
show the full engine hazard (incl. measures, norms, clarification status).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
#1 Risk-number comparison in the benchmark: ComputeRiskComparison derives the
tool's S/F/W/P + Fine-Kinney per matched hazard and compares to the GT values;
exposed on the benchmark response and rendered in a new RiskComparison table
with GREEN/YELLOW/RED traffic lights on the risk number R (like the Excel),
plus per-axis within-1 agreement cards.
#2 Generic misuse pattern HP2103 "Personenbefoerderung auf Hebezeug" — gated to
lift-family machine types, fires for ANY lifting device (not machine-specific).
#3 Benchmark matcher is now 1:n — one broad engine hazard may cover several
fine-grained GT sub-scenarios (foot/hand/leg crush), so coverage reflects real
risk coverage rather than 1:1 wording matches.
Validated on BOTH ground truths (robot cell + lift): leakage 0, ghosts 0,
coverage held.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Member-canonical_controls tragen meist kein evidence_type/verification_method
(wie schon source_citation). primary_verification_method() leitet die Methode
deterministisch aus dem Primaer-Use-Case ab (impressum->document,
code_security->source_code, ...). Populiert mc_verification beim naechsten Seed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(1) extractNarrativeFromMetadata now reads every limits-form field generically
(no whitelist) — intended use, foreseeable misuse, all machine limits and all
four interface groups (electrical/mechanical/pneumatic/software). Field-schema
drift no longer silently drops hazard sources.
(2) withUniversalLifecycles always adds normal_operation/setup/maintenance/
cleaning to the matched lifecycle phases — these occur on virtually every
machine and the professional assesses them, so their hazards must be derived
even when the form omits them.
Kistenhubgeraet recall jumped 42.9% -> 74.3% (electrical 9% -> 82%) from the
field-name fix alone; this broadens it further.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
extractNarrativeFromMetadata looked for field names that don't exist in the real
limits-form schema (interfaces_description, control_system_description,
energy_sources, space_limits, foreseeable_misuse), so it effectively read only
general_description + intended_purpose. The electrical/mechanical/pneumatic/
software interface fields — each a hazard source — were silently dropped, which
is why electrical hazard coverage was 9% for the Kistenhubgeraet.
Now reads the actual schema fields incl. electrical_interfaces /
mechanical_interfaces / pneumatic_hydraulic_interfaces / software_interfaces /
energy_supply / spatial_limits / foreseeable_misuses, plus array fields
(operating_modes, person_groups, industry_sectors). Legacy names kept.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A "Splitterflug bei Werkzeugbruch" pattern leaked into a lift re-seed because
its press hint ("Pressraum") lives in ZoneDE, which applyDomainGates did not
scan. Add ZoneDE to the gated text. Leakage stays 0, ghosts 0, coverage held.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Observed on a real Kistenhubgeraet (lift) project: generic mechanical patterns
(e.g. HP1000 "Quetschen Arm zwischen Pressenteilen") carry NO machine type and
only generic tags (crush_point, rotating_part), so they fired for a lift; the
narrow domain-gate terms missed their press/welding/glass wording.
Broadens domainGateTerms (pressenteil, pressraum, blechbearbeitung,
punktschweiss, schweisselektrod, elektrodenspalt) and adds a dom_glass domain
(glasschneid/glasbearbeitung/...) with its emit keywords. New test pins that the
four observed leakers now require a dom_* tag. Ghost=0, Leakage=0, coverage held
on both GTs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New tab /sdk/iace/[projectId]/risikobewertung. Per hazard it shows BOTH models
side by side — EN-62061-style (S/F/W/P) and Fine-Kinney (P/E/C) — with
BreakPilot's justified suggested values from public data, the visible formula,
and editable fields that recompute the score + risk band live. The professional
adjusts the values (e.g. from his own licensed DIN/Beuth data); we only supply
the formula + inputs, reproduce no norm table.
Consumes GET .../hazards/:hid/risk-suggestion. Registered in IACE_NAV_ITEMS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GET /projects/:id/hazards/:hid/risk-suggestion returns BreakPilot's justified
starting values for BOTH risk models per hazard:
- EN-62061-style F/W/P/S (the Excel format the professional knows)
- Fine-Kinney P/E/C (US-recognized)
each with a plain-language justification + the visible formula. Read-only and
computed from public-data anchors (ESAW/NIOSH/OSHA via the engine estimators) —
the professional adjusts the values; no norm table is stored or reproduced.
Adds EstimateFrequency (lifecycle -> 1-5) and BuildRiskSuggestion. Go SDK has no
OpenAPI baseline, so the only contract surface is the frontend consumer (the new
Risikobewertung tab, next).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fine-Kinney (Fine 1971 / Kinney-Wiruth 1976): Risk = Probability x Exposure x
Consequence — a PUBLISHED, freely-usable method (not a DIN/Beuth/ISO standard),
widely used incl. CE-marking. Gives the professional a second, US-recognized
model alongside the EN-62061-style one; German exporters get both for free and
adjust with their own licensed norm data.
risk_fine_kinney.go: SuggestFineKinney derives justified P/E/C from public
anchors (ESAW frequency -> P, lifecycle -> E, de-biased severity -> C on the
Fine-Kinney consequence scale) + ComputeFineKinney(p,e,c) so the professional
can override with his own values. No norm table stored.
GT benchmark (rank concordance vs the professional): Fine-Kinney 75.4% — beats
the EN-62061-style model (69.3%) and the raw engine (57%).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The engine's hand-set DefaultSeverity systematically over-estimates severity
(GT shows crushing 3.3 vs 2.2, struck_by 3.1 vs 2.5; electrical was already
close). EstimateSeverity blends the pattern default 50/50 with the contact
mode's GT-calibrated typical severity (baseS) — keeps pattern-specific signal,
removes the bias. Our own model, no norm table.
Effect across both GTs: severity within +-1 78%->88%; risk RANK concordance
57%->69% (Kistenhub 45%->70%). Wired into iace_handler_init.go so the
BreakPilot risk line uses the de-biased severity.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Der Agent lieferte "alles gruen": _load_controls gab auf macmini nur 3 von 75
doc_type='impressum'-MCs zurueck (Sidecar mc_classification.db hat nur 4/75 als
text-matchbar klassifiziert). Tiefere Ursache: die 75 doc_type='impressum'-MCs
sind fehl-klassifiziert (60/75 canonical_scope='other'; Prefixes TRD/SEC/GOV =
Geschaeftsbriefe/Marktplatz/Bestellung, NICHT §5 TMG Website-Impressum).
Fix: Der Impressum-Agent erzeugt Findings jetzt aus seinen 12 autoritativen
§5-TMG/DDG-Pattern-MCs (mcs.py) statt aus dem verunreinigten DB-Set —
deterministisch, scope-aware, field_id = semantisches Feld. Semantic-Validator-
Demote + Massnahmen + Rollup bleiben. Die 5-Impressum-GT-Tests laufen jetzt
echt durch: 0 Falsch-Positive.
DB-Master-Controls fuer Impressum deaktiviert bis zum MC-Re-Filtering (separate
Aufgabe: die doc_type-Klassifizierung der Vorgaenger-Session muss bereinigt
werden).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
IP/copyright fix: ComputePLr reproduced the EN ISO 13849-1 Anhang A risk-graph
decision table (S/F/P -> PLr a..e) and SeverityToS/ExposureToF its parameter
binning, emitted into every hazard description. Removed — we may not reproduce
DIN/Beuth norm logic.
Replaced with BreakPilot's OWN risk model:
- risk_estimation.go: probability (W) + avoidance (P) estimated from public,
permissively-licensed accident statistics (Eurostat ESAW, CC BY 4.0) by
contact mode, calibrated to our ground-truth corpus; own risk index + bands.
- iace_handler_init.go now emits "Risikoeinschaetzung (BreakPilot-Modell):
S F W P -> Risiko: <level>" instead of the norm PLr string.
- DATA_SOURCES.md: data provenance + license register (ESAW CC BY 4.0; BLS/OSHA
public domain; HSE OGL; DGUV + DIN/Beuth explicitly excluded).
- gt_risk_benchmark_test.go: first GT validation of risk numbers — W within +-1
99%, P 93% vs the professional across both ground truths.
Removed risk_graph_test.go (pinned the reproduced norm table).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Damit die Specialist-Agents auf vollstaendigem Website-Content arbeiten:
A — _find_dsi_links pierct jetzt Shadow-DOM (Web-Components wie Usercentrics/
Mercedes) rekursiv; versteckte (display:none) Links werden erfasst + als
Coverage-Metadatum geflaggt.
B — _expand_to_fixpoint klappt Akkordeons/Tabs/Hover-Menues in einer Schleife
auf, bis das DOM stabil ist (statt 1 Pass); erweiterte Selektoren;
Coverage-Telemetrie (Runden, expandierte Elemente, DOM-Wachstum, Shadow-/
versteckte Links) → Response + Backend-Log.
C — legacy_url_cdx.cdx_enumerate listet via Wayback-CDX-API ALLE je
archivierten URLs der Domain → findet Orphan-/Legacy-Seiten, die nie im
Slug-Raster standen (z.B. nicht mehr verlinktes /datenschutz, per Direkt-
URL noch erreichbar). Fliesst durch das bestehende Legacy-URL-Inventar.
Tests: test_legacy_url_cdx.py (6) + consent-tester/tests/test_dsi_discovery.py
(Pure-Helper + Real-Browser-Integration). Alle gruen, LOC-Gate gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regression: Der v3-Agent-Pfad baute eine parallele MC-Pipeline
(_load_impressum_mcs / _load_cookie_mcs, Roh-SELECT) und lief damit an
allen Schutzmechanismen der Engine vorbei → GOV/Branchen-MCs als HIGH bei
OEM/Zulieferer, fremde MCs (Bestellbestätigung), und action=check_question
(Fragen statt Maßnahmen im Frontend).
- Agent delegiert MC-Laden an rag_document_checker._load_controls
(P72-Scope, check_type='text', fits_doc_type/scope_requires).
- Subtraktives Sektor-Gate (SECTOR_PREFIXES) + Themen-Gate am Agent-Rand.
- action = konkrete Maßnahme (Imperativ) statt check_question.
- rag_document_checker: from __future__ import annotations (3.9-Import).
- mcs: Name-Pattern erkennt "Aktiengesellschaft" (OEM-Impressums).
- Tote GT-/Semantic-/Routes-Tests wiederbelebt (v3-Mismatch +
agent.cascade-Patch-Target). Alle 72 Specialist-Tests grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
User-Korrektur 2026-06-09:
(1) Begriff 'MC' steht im Projekt fuer Master-Control aus
canonical_controls (314k Eintraege, ~1.800 fuer dieses Tool). Mein
neuer Agent-Code hatte 'MC' als Abkuerzung fuer 'Machine-Check'
verwendet — Naming-Konflikt. Frontend-Methodik-Box jetzt:
- 'Pattern-Check' statt 'Machine-Check'
- Explizit: 'Diese Pattern-IDs (IMP-MC-001) sind interne Test-IDs,
NICHT die Master-Control-IDs aus der canonical_controls-DB'
- Roadmap-Hinweis: formale Verknuepfung Pattern→Master-Control folgt
Backend-Variablen mc_id bleiben technisch unveraendert (Refactor
waere gross), aber UI darf sie nicht als 'Master-Control' bezeichnen.
(2) LLM-Modell-Default war 'qwen2.5:7b' — Projekt nutzt aber das
groessere 'qwen3.5:35b-a3b' auf macmini (ENV SELF_HOSTED_LLM_MODEL).
_escalation.py default jetzt: SELF_HOSTED_LLM_MODEL als Fallback,
und Methodik-Erklaerung nennt das richtige Modell.
(3) Methodik-Erklaerung erweitert um Sprint-1.10 Semantic-Validator
und Sprint-1.11 Auto-Learning-Pattern-Library + Cross-Placement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug bei BMW: dsi-discovery liefert HTML-Entities ( ) als
Literal-Strings ohne Decode. Beispiel im BMW-Impressum:
'wird gesetzlich durch den Vorstand (Milan Nedeljkovic, …)'
Mein Pattern erwartet ':' / '.' / Whitespace nach Vorstand →
matched nicht das '&' → false-positive HIGH-Finding.
Fix 1 (Hauptfix): Test-Harness ruft html.unescape() vor agent.evaluate()
auf, so dass jeder Agent sauberen Text bekommt — entkoppelt von
dsi-discovery-Eigenarten.
Fix 2 (Belt-and-suspenders): Pattern duldet jetzt auch '(' direkt
nach Vorstand/Geschaeftsfuehrer (falls Decode mal fehlschlaegt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Statt der simplen dsi-discovery-Wrapper-Funktion ruft der Test-Harness
jetzt _fetch_text() aus agent_check/_fetch.py — die VOLLE Pipeline
die auch der produktive Compliance-Check verwendet:
- consent-tester dsi-discovery mit 240s Timeout (statt 120s)
- doc_type-aware max_documents (1 für cookie/dse, 3 für impressum)
- CMP-Payload-Capture (ePaaS, OneTrust …)
- HTTP-Fallback mit Browser-User-Agent + DomainRateLimiter
- HTML-Tag-Strip wenn Playwright fail
Damit funktionieren Cloudflare-/Anti-Bot-geschützte Sites wie BMW
und Elli auch im Test-Harness — vorher Timeout nach 90s.
Plus: bei leerem Fetch klare Fehlermeldung im Slot
('Cloudflare-/Anti-Bot-geschützt — Tipp: Text manuell einfügen')
statt silent-fail. cmp_payloads landen jetzt auch im Vault.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Safetykon-Bug: 'Geschäftsführung:' (Sammelbegriff für GF einer GmbH)
matched das alte Pattern 'Geschäftsführer' nicht — False-Positive
IMPRESSUM-AGENT-VERTRETUNGSBERECHTIGTE_LABEL_KORREKT.
Pattern erweitert: Geschäftsführer|Geschäftsführung|Geschäftsführerin
+ Vorstand|Vorstandsvorsitzender + Inhaber|persönlich haftend.
Test test_safetykon_geschaeftsfuehrung_passes ergänzt (11/11 grün).
frontend: SlotCard zeigt jetzt Badge bei 0/0/0-Slots
('Dokument konnte nicht geladen werden') statt silent-fail, +
bei 0 Findings ein 'alle MCs OK'-Badge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/app/artifacts gehört root und appuser darf nicht mkdir machen — Endpoint
crashte mit PermissionError. Default jetzt /tmp/breakpilot/agent_runs.
EVIDENCE_VAULT_ROOT-Env-Var bleibt für persistente Volumes nutzbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend registriert specialist-agent-Routes über den compliance-Router,
prefix wird /api/compliance/specialist-agent/* (statt /api/v1/...).
Frontend-Proxy hat auf /api/v1/specialist-agent/* gezeigt — 404.
Verifiziert auf macmini:
curl http://localhost:8002/api/compliance/specialist-agent/agents
→ 200 {"agents": [{"agent_id": "impressum", ...},
{"agent_id": "cookie_policy", ...}]}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) B22 Cross-Domain (fix#59):
Elli-Test fand AGB auf logpay.de NICHT obwohl URL in doc_entries
korrekt. Vermutete Ursache: Discovery-Phase A drops/überschreibt
Original-URL bei PDF-Fetch-Fail (word_count=0).
Fix: _collect_audit_urls() iteriert über state.doc_entries +
rejected_url + req.documents — Cross-Domain-Hosting ist
unabhängig vom Text-Inhalt. Plus Trace-Logging für künftige
Diagnose. Dedup per (doc_type, host_sld).
(2) B17 Audit-Walk-Fail-Fallback (fix#60):
BMW v5 hatte audit_walk=None ohne Mail-Hinweis. Vermutlich
180s-Timeout bei OneTrust-CMP-Banner-Tour.
Fix: Timeout 180s → 300s. Plus: Bei Fail wird ein Hinweis-
Stub mit error-Grund in state["audit_walk"] + HTML-Block
geschrieben — Reviewer sieht den Fail statt silent-skip.
(3) company_name + origin_domain im Backend (fix#61):
Frontend sendet seit ec03317 die zwei Felder — Backend ignorierte
sie.
Fix: ComplianceCheckRequest-Schema um company_name +
origin_domain erweitert. phase_e_email priorisiert User-Input
vor URL-Heuristik für site_name. Bei origin_domain ohne
ableitbare doc_entries-domain wird der User-Input als domain
übernommen.
(4) Plausibility-LLM Fallback-Modell (fix#62):
qwen3:30b-a3b liefert auf großen DSEs (BMW 122 FAIL) gehäuft
leere format='json'-Responses — Circuit-Breaker griff aber
Phase blieb nutzlos.
Fix: Default-Modell auf qwen2.5:7b umgestellt (4× kleiner,
zuverlässiger bei format=json, ausreichendes Reasoning für
PASS/MODIFY/DROP-Klassifikation). Plus Strategy-C eingeführt
— Fallback-Modell (llama3.2:3b) wenn primary leer bleibt.
BATCH_SIZE 4 → 3. ENV-Switches PLAUSIBILITY_LLM_MODEL +
PLAUSIBILITY_FALLBACK_MODEL für Tuning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ComplianceCheckTab.tsx bekommt zwei neue UI-Felder oberhalb des
PreScanWizard:
- Firma → z.B. 'Tesla Germany GmbH'
- Domain (Site-Origin) → z.B. 'https://www.tesla.com/de_de'
Beide werden:
- in localStorage persistiert (Hook _useCompanyOrigin.ts)
- im POST-Body als company_name + origin_domain mitgeschickt
- haben Vorrang vor LLM-extracted_profile (Backend nutzt
eingegebene Werte falls vorhanden, fallback auf Inferenz)
Datei jetzt 489 LOC (war vorher 461 + 28 für die Inputs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ComplianceCheckTab.tsx war 519 LOC und blockte jeden weiteren Edit
(500-LOC-Hard-Cap). Drei Concerns ausgelagert:
- _document_types.ts: DOCUMENT_TYPES + DocTypeId (inkl. news doc_type)
- _compliance_storage.ts: STORAGE_KEY_*, DocState/HistoryEntry types,
emptyDocState/initState helpers, countWords
- _useCompliancePolling.ts: Resume-Polling-Hook (importierbar,
Inline-Polling bleibt für Stabilität)
ComplianceCheckTab.tsx ist jetzt 461 LOC (-58).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-Feedback BMW v5: "740 Cookies verschwunden auf 31, Übersicht
verloren". Drei Anpassungen:
Mail-Restrukturierung (_executive_summary.py + _compose.py):
- render_executive_summary(): Top-of-mail TL;DR mit
Compliance-Score (gross + farbig), Top-3-Findings nach
Severity, Cookie-Statistik (deklariert/Browser/Drittland),
Severity-Verteilungs-Chips.
- collapsible(): wrapt jeden Block in <details>/<summary>.
Mailpit + alle modernen Mail-Clients rendern das nativ.
- _compose.py: alle 18+ B-Blöcke + per_doc + per_theme +
legacy_html in Akkordeons. NUR Critical-Findings + Sofort-
massnahmen sind immer offen — Reviewer sieht ~15 Zeilen
Übersicht und klappt selektiv auf.
- Cookie-Inventar (742) hat jetzt eigene Sektion ganz oben
(Akkordeon "🍪 Cookie-Inventar"), Vendor-Karten parallel.
B22 Cross-Domain-Legal-Doc-Detector (cross_domain_doc_check.py):
Real-Beispiel User-Feedback: Elli's AGB liegt auf docs.logpay.de
statt elli.eco. Detektor erkennt SLD-Mismatch:
- HIGH bei agb / widerruf (vertragsrelevant)
- MEDIUM bei dse / nutzungsbedingungen
- INFO bei cookie / impressum (Best-Practice)
Norm: DSGVO Art. 28 (AVV-Pflicht für Hosting) + Art. 13 Abs. 1
lit. e (Empfänger) + § 312i BGB (Cool-URLs).
9/9 Tests grün inkl. Elli/LogPay Pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Erweitert Phase 1 (Backend 5-Stage Lifecycle, Migration 148) jetzt auch
im Frontend: Status-Pills, Buttons und Modal-Texte differenzieren nun
zwischen DSB- und Mandanten-Pruefung.
- WorkflowStatusBar zeigt 5 Schritte: draft -> review_internal ->
review_client -> approved -> published, mit status-spezifischen
Action-Buttons (Save/Submit, DSB-Freigabe, Mandant-Freigabe, Publish).
- ApprovalModal differenziert Mode 'approve-internal' / 'approve-client' /
'reject' mit eigenen Titles und Button-Labels.
- useWorkflowActions ruft neue Endpoints /approve-internal und
/approve-client (Backend Phase 1); approveVersion bleibt als
Backward-Compat-Alias.
- page.tsx leitet Modal-Confirm an passende Action weiter und akzeptiert
review_internal/review_client im draftVersion-Filter.
- _types.ts: Status-Union + STATUS_LABELS um beide Review-Stufen
erweitert; alter 'review'-Wert bleibt fuer Bestandsdaten erhalten.
- CompareView, SplitViewEditor, HistoryPanel: Status-Rendering und neue
Action-Labels (submitted_internal, approved_internal, approved_client).
LOC-Exception fuer admin-compliance/lib/sdk/types/sdk-steps.ts (525):
zentrale SDK-Step-Registry mit kanonischer Reihenfolge — splits wuerden
die globale seq-Garantie zerreissen.
[guardrail-change]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei verwandte Mechanismen für DSE-Beweisbarkeit + URL-Hygiene.
Plan B + PDF — Versions-Beweisbarkeit-MCs (dse_checks.py):
- mc-dse_version_date (HIGH) — sichtbares Stand/Versionsdatum
Pflicht. 12 Regex-Pattern: "Stand: April 2024", ISO-Datum,
"Letzte Aktualisierung", "Version 3.2", englische
Varianten ("Last updated", "Effective date as of …").
Norm: Art. 7 Abs. 1 DSGVO (Nachweisbarkeit Einwilligung).
- mc-dse_version_proof (MED) — PDF-Download oder
versionierte Archiv-URL. Reine HTML-DSE ohne Snapshot ist
juristisch fragil. 8 Pattern: .pdf, Download-Hinweis,
web.archive.org, /dse-vNNN.html.
Norm: DSK-Orientierungshilfe 2024.
Plan A — Legacy-URL-Discovery (legacy_url_discovery.py + B20):
Vier komplementäre Quellen:
A.1 /sitemap.xml + Sub-Sitemaps parsen, auf compliance-
relevante Slugs filtern
A.2 archive.org/wayback/available pro Slug — wenn Wayback
zeigt ≥18 Monate alten Snapshot UND Seite heute noch
200 liefert UND nicht im Footer → Legacy-Verdacht
A.3 Slug-Permutations: 6 doc_types × 6 Slug-Varianten ×
5 Lang-Prefixe × 4 Brand-Parameter
A.4 Banner-Modal-Links (über consent-tester Stufe 4 Tour)
Mail-Block "🗂️ Legacy-URL-Inventar" mit Tabelle: URL · HTTP ·
Wayback-Alter · Footer · Empfehlung (301/Offline/Behalten).
Engine entscheidet NICHT was Legacy ist — präsentiert das
Inventar, Kunde wählt.
Real-World-Smoke Elli:
/en/cookies → HTTP 200, Wayback 69 Mo alt, nicht im Footer
→ "Legacy-Verdacht, 301 setzen"
/en/impressum → HTTP 302, redirected → "behalten"
Plan C — Multi-Version-DSE-Analyse (multi_version_dse.py):
Wenn ≥2 DSE-URLs reachable: pro Variante DSB-Name + Datum +
Wortzahl + SHA-256 extrahieren, Inkonsistenzen flaggen
(date_divergent, dsb_divergent, no_date_count).
Mail-Block "📑 Mehrere DSE-Versionen erkannt" mit
Vergleichstabelle + rotem Hinweis "Nur eine Version kann
gültig sein". Beispiel Elli: /de/datenschutz (Mollstr-DSB,
2022) vs /de/datenschutzerklaerung?brand=elli (Proliance,
ohne Datum).
API-Response erweitert um legacy_url_inventory +
html_blocks.legacy_urls + multi_version_dse_html im V2-Layout.
ENV-Override: LEGACY_URL_DISABLED=1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neue Compliance-Admin-Seite /sdk/document-library: zeigt alle compliance_
legal_documents mit aktueller Version, gruppiert nach Empfehlungs-Klassi-
fikation, filterbar nach Status + Volltextsuche.
Backend (Service + Routes):
- LegalDocumentService.list_documents_with_versions() — JOIN über docs +
latest/published version in einem Roundtrip statt N+1
- GET /api/v1/compliance/legal-documents/documents-with-versions
liefert {documents:[{...doc, latest_version, published_version}]}
Admin-Frontend:
- app/sdk/document-library/page.tsx (350 LOC)
- Lädt Docs + Recommend parallel
- Mapped jedes Doc per .type → Recommend-Item (klassifiziert in
required/recommended/optional/uncategorized)
- 4 Sektionen mit Klassifikations-Chip + Anzahl-Badge
- Tabelle pro Sektion: Titel · Type · Status · Version · Geändert · Override
- Status-Filter (alle / draft / review_internal / review_client /
approved / published / archived / rejected)
- Klick auf Zeile → /sdk/workflow?doc=<uuid>
- Empty state mit Link zum Generator (Bulk-Modus)
- workflow/page.tsx: auto-select bei ?doc=<uuid> URL-Param
- lib/sdk/types/sdk-steps.ts: 'document-library' bei seq=2500 im Paket
'dokumentation' registriert (sichtbar in der SDK-Sidebar)
Workflow-Hookup vervollständigt: Library → click → Workflow öffnet
direkt das gewünschte Dokument im SplitViewEditor, keine manuelle
Selektion über DocumentSelectorBar mehr nötig.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 of the workspace-cutover initiative: the Document Generator
gets a Bulk-Generate mode that produces every recommended document
in one click instead of forcing the user through 25+ per-template
clicks.
New: BulkGenerateModal.tsx (430 LOC)
- On open: POSTs current CompanyProfile + ComplianceScope answers
to /api/sdk/v1/compliance/recommend (Phase 1 endpoint)
- Matches each recommendation's document_type against allTemplates
- Shows tabular list: classification chip, title, document_type,
source citation; checkboxes pre-selected for required+recommended
(only where a template exists)
- On submit: sequentially renders each selected template using the
same pipeline as GeneratorSection (runRuleset → applyBlockRemoval
→ applyConditionalBlocks → placeholder replace), then POSTs
documents + version v1.0 draft
- Per-row progress: ⏳ generiere → ✓ erstellt / ✗ Fehler / —
übersprungen; final summary counts
page.tsx:
- Imports BulkGenerateModal
- Adds prominent "Empfohlene generieren →" CTA above the
RecommendedDocuments block
- Wires SDK state (companyProfile, complianceScope) into the modal
Profile mapper:
- CompanyProfile (camelCase): employeeCount, businessModel,
isDataProcessor → org_employee_count, org_business_model,
comp_has_processors
- ComplianceScope answers (questionId/value): pass through 1:1
since the rule system uses the same field names as the wizard
- compliance_depth_level pulled from decision.determinedLevel
End-to-end flow:
1. User completes CompanyProfile + ComplianceScope
2. Clicks "Empfohlene generieren →"
3. Reviews 25-30 prefilled checkboxes
4. Clicks "Generieren" — modal iterates, all docs land as drafts
in compliance_legal_documents + version v1.0
5. Phase 3 (next): document-library tab makes them findable
6. Phase 4 (next-next): workspace consumes these directly
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of the workspace-cutover initiative: compliance becomes the
single source of truth for documents. Step one is making the existing
compliance_legal_documents workflow rich enough to express the DSB→
Mandant approval pattern that the workspace's 5-stage UI needed.
Migration 148:
- Adds CHECK constraint on status (was free-form VARCHAR20)
- Allows: draft, review, review_internal, review_client, approved,
published, archived, rejected (legacy "review" kept for backward
compat — 0 existing rows so no backfill needed)
- Adds CHECK on approvals.action with extended values:
submitted_internal, submitted_client, approved_internal,
approved_client, rejected_internal, rejected_client
- Adds 6 new columns for the richer audit trail: submitted_by/at,
approved_internal_by/at, approved_client_by/at
Service:
- New methods submit_internal_review, approve_internal, approve_client
- submit_review / approve kept as backwards-compat aliases that map to
the new methods
- reject() now reads current status to log specific rejected_internal
or rejected_client action
- _version_to_response includes all new audit fields
Routes:
- POST /versions/{id}/submit-internal-review
- POST /versions/{id}/approve-internal (DSB sagt OK → Mandant ist dran)
- POST /versions/{id}/approve-client (Mandant sagt OK → approved)
- Existing submit-review / approve endpoints stay but map through aliases
Schema:
- VersionResponse extended with optional submitted_by/at,
approved_internal_by/at, approved_client_by/at fields
This unlocks Phase 2 (Generate-All in compliance generator), Phase 3
(Document-Library tab in admin), Phase 4 (workspace cutover — drop its
own document storage and route everything through this lifecycle).
[migration-approved]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BMW4 zeigte 1037 UNK-Findings — die Mail wurde damit unleserlich.
Drei pragmatische Anpassungen:
1. UNK severity: LOW → INFO. Mail-Renderer zeigt jetzt nur
HIGH/MEDIUM/LOW; INFO bleibt im API-Payload + CSV.
2. UNK wird NICHT emittiert wenn Vendor=First-Party-Owner
(z.B. "BMW AG" auf bmw.de). Heuristik _is_first_party_owner
vergleicht Vendor-Name gegen Domain-SLD.
3. auto_learning threshold ≥3 Sites → ≥1 Site. Second-time-Audit
einer Site hat ihre eigenen Cookies bereits gelernt → kein
UNK mehr. Single-site Auto-Learning ist absichtlich
konservativ (Annotation, kein Truth).
Effekt: erwartete Reduktion bei BMW von 1037 UNK → ~50-100
(nur unbekannte 3rd-party-Vendoren). Mail wird lesbar, MAE-
Findings (Salesforce-as-essential) bleiben prominent sichtbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KRITISCH: Mein vorheriger B19-Edit hatte send_email() versehentlich
in den _build_cookie_csv_extra-Helper geschoben (NACH dem return {}).
Mail wurde nie versendet (email_status=skipped war Folge — state[
"email_result"] nie gesetzt).
Fix:
- send_email + state["email_result"]/site_name/domain/doc_count
zurück in run_phase_e (BMW4 hat 1520 findings produziert aber
keine Mail verschickt).
- _build_cookie_csv_extra ist jetzt eine echte Modul-Funktion
NACH run_phase_e.
Plus: phase_f_persist.response.html_blocks um "cookie_coherence"
ergänzt (B19-HTML-Block fehlte im API-Schema).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stufe 4 — Cookie-Banner-Tour vor dem Accept-Klick:
- audit_walk_banner_tour.tour_cookie_banner(): öffnet Settings
(16 Phrase-Varianten), scrollt vertikal, aktiviert jedes
[role=tab], expandet jedes [aria-expanded=false] / details /
summary + 14 CMP-spezifische Selektoren. Max 35 Klicks,
Best-Effort.
- audit_walk_recorder ruft tour_cookie_banner() VOR
_try_accept_banner auf — Reviewer sieht den vollen Consent-
Katalog im Video (Vendor-Liste, Kategorien, Zwecke).
- Recorder unter 500 LOC (412+155 split).
Stufe 5 — Annotierte Screenshots pro Finding:
- finding_annotator.annotate_url(): WebKit headless, JS-Inject
eines rot-banner-Labels oben + roter Outline um das Element
(Selector oder Text-Match).
- finding_annotator.annotate_findings(): dispatched 3 Cases —
B1 Tap-Target (Anchor markiert mit "Tap-Target X×Y px"),
B16 URL-Slug-Drift (404-Seite mit "/<slug> 404"),
B13 Widerruf (Footer markiert "Widerruf-Link fehlt").
- routes_audit_walk.POST /annotate-findings (consent-tester).
- _b17_wiring ruft annotate-findings nach record_audit_walk und
speichert annotations in walk.annotations.
- audit_walk_zip_builder packt PNGs nach findings/<name>.png ins
ZIP — Reviewer hat Beweis-Bilder im Postfach.
Plausibility Circuit-Breaker:
- Nach 6 consecutive empty batches (PLAUSIBILITY_EMPTY_BUDGET=6)
bricht die ganze Phase ab statt 200 Calls zu warten. Fix für
qwen3-down + große DSE-Sites (BMW: ohne Breaker 21min, mit
Breaker ~3min).
audit_walk_zip_builder fängt walk.annotations ab und legt sie unter
findings/<fname>.png im ZIP-Anhang ab.
V2-Default:
- docker-compose.yml backend-compliance.environment.MAIL_RENDER_V2:
default 'true'. Ohne diesen Override liefert die Engine
weiterhin das alte Legacy-Mail-Layout, in dem die B-Wiring-
Blöcke nicht sichtbar sind.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 Backlog-Items aus dem Multi-Site-Briefing in einem Sprint:
1. B13 B2C-Soft-Hints — Versicherungs/Tarif/Buchungs-Marker
_B2C_WEAK erweitert um "Reiseversicherung", "Tarifrechner",
"Online-Antrag", "Flug buchen", "Stromtarif" etc.
Fängt Allianz-Reise-Chatbot (vorher False-Negative).
2. Chatbot-Policy-Discovery (chatbot_policy_discovery.py)
Probt 14 Standard-Slugs (privacypolicychatbot, chatbot-datenschutz,
ai-policy, ki-datenschutz, ...) × 5 Lang-Prefixe auf jeder
submitted Origin. Successful >300-Wort-Findings werden in
doc_texts['dse'] gemerged. Audit-Trail über
doc_entries[dse].chatbot_policy_sources.
Hebt Westfield-iAdvize-Lücke.
3. API-Response-Payload erweitert
phase_f_persist.response um extra_findings, audit_walk und
html_blocks erweitert. B-Wiring-Output (B1, B3-B18) ist nicht
mehr nur im Mail-HTML versteckt — externe Aufrufer sehen jeden
Finding. Schema additiv, legacy clients ignorieren neue Felder.
4. Plausibility-LLM Empty-Response-Fix
Resilienz-Strategie A→B→C→D:
A) format='json' (strict, default)
B) format='' (loose, _try_extract_json mit ```json-fence + prose-
wrap-Unterstützung)
C) Split-Batch-Recursion (vorhanden)
D) Give up, leeres dict (callers behandeln als skipped)
Plus _post_llm() als isolierter LLM-Call-Helper, catched
Network-Errors.
5. Specialist-Agents Phase 2 LLM (MVP) — Impressum-Agent
impressum_agent_llm.py: qwen3:30b-a3b mit § 5 TMG System-Prompt,
business_scope-hints aus profile_dict. Output identisches Schema
wie pattern-agent für ein Merge ohne API-Bruch.
_b18_wiring.py orchestriert beide Agents + deduplet nach
field_id, rendert lila V2-Block mit KB/LLM-Tags pro Finding.
Pattern-first im Dedup (deterministisch + stable).
Tests: 107/107 grün (7 Test-Suites + chatbot-discovery + b18).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Video + walk.json werden nach Aufnahme zu DSMS-IPFS hochgeladen.
Die zurückgegebenen CIDs sind manipulationssichere Audit-Anker —
Reviewer können das Walk-Video Monate später noch verifizieren und
auf Unverändertheit prüfen.
consent-tester:
- _upload_to_dsms(): Best-Effort-Upload zu /api/v1/documents
(Bearer-Token, document_type=audit_walk_video|meta). DSMS-Down
bricht den Walk nicht ab — CID fehlt einfach im result.
- record_audit_walk(): nach video.webm + walk.json erzeugt, beide
hochladen. walk.json wird re-written sodass es BEIDE CIDs
selbstreferenziell enthält.
- ENV: DSMS_GATEWAY_URL + DSMS_BEARER konfigurierbar.
backend:
- _b17_wiring._publicize_gateway_url(): DSMS gibt intern
http://dsms-node:8080/ipfs/{cid} zurück. Für die Audit-Mail
wird das via env DSMS_PUBLIC_GATEWAY (default
https://dsms-dev.breakpilot.ai) durch eine extern erreichbare
URL ersetzt.
- Render-Block: gelber DSMS-Anchor-Hinweis mit Video-CID +
walk.json-CID, beide als klickbare Links zur public Gateway.
Real-World-Smoke gegen Elli:
- Video-CID: QmbdFwtSymPuWGYYdC6eNZ1eEvVLsTYmoRRxEo5L6BXgwt
- walk.json-CID: QmWaTqwZq4KVd5wYFVAKB12uZtAosPqoG1X4m1azysXYJi
- DSMS-Upload erfolgreich, gateway_url im response
Tests: 12/12 grün (+2 für DSMS-Anchor-Render-Pfade inkl.
Internal-Host → Public-Gateway-Rewrite).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nach jedem Compliance-Doc-Aufruf werden alle Akkordeons /
<details> / [aria-expanded=false] / Trigger-Patterns geklickt
und im Video aufgenommen.
- _expand_accordions(): 7 Selektor-Patterns, max 25 Expansionen
pro Seite, Dedup nach inner_text (verhindert Endlos-Loops bei
nesteten Strukturen). Scroll-into-view + click + 400ms warten
sicher dass das Klick-Result im Video erfasst wird.
- _visit_link(): Returns (nav_event, expand_event) Tuple. Expand
läuft nur bei HTTP 2xx + ohne nav-error.
- 1500ms post-expand wait gibt der Kamera Zeit, den finalen
Zustand mitzuschneiden.
Backend B17 render: "expand_accordions" Action wird als "5
Akkordeon/Details-Sektion(en) entfaltet" gerendert. Bei 0:
"Keine Akkordeons gefunden" (neutraler Hinweis, kein Fehler).
Real-World-Smoke gegen Elli:
Impressum: 0 Akkordeons (keine)
Datenschutzerkl: 5 Akkordeons aufgeklappt
Nutzungsbeding: 0 Akkordeons
Video-Größe verdoppelt sich (581 KB → 1.14 MB) — Reviewer sieht
jetzt den vollen DSE-Vendor-Tabellen-Inhalt im Video.
Tests: 10/10 grün (+2 für Akkordeon-Render-Pfade).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Smoke gegen www.elli.eco hat 3 Bugs offengelegt, die in den
synthetischen Tests nicht greifbar waren — Real-Texte haben
Abkürzungen, HTML-Stripping-Artefakte, andere Formulierungen.
B9 Multi-Entity-Impressum — vorher: 13 "Entities" statt 2.
- Block-Boundary jetzt HRB-Anker-basiert (jeder HRB-Eintrag
markiert eine Entity). Robuster als Legal-Form-Anker, der bei
"Programmierung der Webseite Acme GmbH" über-matchte.
- _NAME_BLOCKLIST gegen 11 typische False-Positives
(programmierung, webseite, umsatzsteueridentifik, ...).
- _LEADING_NOISE_RE strippt Email-TLD-Artefakte ("eco "),
deutsche Artikel ("Die "), URL-Fragmente.
- _USTID_PAT fängt jetzt auch die Vollform
("Umsatzsteueridentifikationsnummer der … ist DE…") über eine
zweite Pattern-Alternative mit [\s\S]{0,80}? Bridge.
- Dedup gleicher Entity-Namen — Mehrfacherwähnung in einem Doc
zählt als EINE Entity.
- Fallback auf alten Legal-Form-Anker wenn keine HRBs vorhanden
(z.B. e.V. ohne HR-Pflicht).
B14 Retention-Conflict — Anchor-Liste erweitert:
- "protokolldat" / "protokollierung der zugriffe" /
"zugriffsdat" / "zugriffsprotokoll" als zusätzliche
Logfile-Anchors (Elli's reale DSE-Wortwahl statt "Logfile").
B15 AI-Legal-Basis — kein Code-Fix. Elli's aktuelle DSE enthält
keine LLM-Provider-Erwähnung mehr; der GT-Anker (2026-06-06) ist
seither veraltet. 0 Findings ist korrekt für den aktuellen Stand.
Tests: 3 neue Real-World-Regression-Tests in
test_impressum_multi_entity_check.py::TestRealWorldElliPattern.
Combined: 75/75 grün.
Real-World-Smoke gegen Elli (HTTP→Text via crude strip):
B9: Entities 13→2 ✓, IMPRESSUM-MULTI-UST_ID → VW ✓
B13: 1 Finding (b2c_strong) ✓
B14: 0 (Elli hat aktuell nur EINEN Retention-Wert für Logs)
B15: 0 (LLM nicht erwähnt, korrekt)
B16: 3 Findings (impressum/dse/cookie Standard-Slug-Brüche) ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raw text() queries return JSONB columns as JSON-encoded Python strings,
not as Python list/dict objects. The existing isinstance check then fails
and silently falls back to defaults — so list-valued fields like
target_markets, offerings, processing_systems, ai_systems were always
returned as their defaults regardless of stored content.
Add a JSON-decode pass over _JSONB_FIELDS before the type check.
Verified: PATCH of target_markets=["DE","EU"] now round-trips through
GET correctly. Previously the DB had the right data but GET returned
["DE"] (the default).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SQLAlchemy's text() parser treats `:name::jsonb` ambiguously when the
trailing `::jsonb` follows immediately — psycopg2 receives the literal
`:name::jsonb` string and raises a SyntaxError because `:` isn't a
psycopg2 placeholder syntax.
The fix uses ANSI CAST(:name AS JSONB) which is semantically identical
in PostgreSQL but lets SQLAlchemy unambiguously substitute the
parameter.
Effects: PATCH and POST/upsert on /api/v1/company-profile now actually
update the row. Before this fix both endpoints returned 500 (or 200
with stale data) and never persisted edits.
Files touched:
- _company_profile_sql.py (build_upsert_params / execute_update /
execute_insert): 12 JSONB columns
- company_profile_service.py: PATCH dynamic JSONB column,
audit log insert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
Erkennt: LLM/GPAI-System (Vertex AI, OpenAI/GPT, Claude) wird in
DSE oder Cookie-Doc auf Art. 6 Abs. 1 lit. f (berechtigtes Interesse)
gestützt — statt auf lit. a (Einwilligung).
GT-Anker (Elli AI-ACT-RISK-001): Vertex-AI-Chatbot mit lit. f
deklariert. Bei LLM-Prompt/Output-Logging + US-Transfer +
Profiling-Ähnlichkeit ist Interessenabwägung fragwürdig.
Heuristik:
- KB-basiert (chat_providers.json filter: ai_capable + LLM-Type-Hint)
- LLM-Vendor-Aliases inkl. Marken-Familien (PaLM, Gemini, GPT-4,
ChatGPT, Claude 3, Azure OpenAI)
- Absatz-Boundary-Scope: Provider + lit. f im selben Absatz
- Negativ-Filter: wenn lit. a / Einwilligung ebenfalls im Absatz →
kein Finding (Side-Purpose-Erwähnung)
- Dedup pro (doc_type, provider_id)
Severity: MEDIUM.
Norm: DSGVO Art. 6 Abs. 1 lit. a vs lit. f + AI Act Art. 50 + 51.
Tests: 17/17 grün.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Multi-Entity-Check fängt Elli's USt-IdNr-Lücke (VW Group Charging
GmbH hat keine, Elli Mobility GmbH hat eine), aber Entity-Namen waren
mit Header-Noise verunreinigt:
'Impressum\n\nVolkswagen Group Charging GmbH'
'eco\n\nElli Mobility GmbH'
Behoben:
- _ENTITY_PAT lässt nur Space im Namen zu (kein \s/\n mehr)
- _clean_entity_name() trimmt Header-Worte (Impressum, Anbieter, ...)
und nimmt nur die letzte Zeile vor Legal-Form-Suffix
- 11 neue Tests, davon einer mit Elli-like Impressum als
Charakterisierungs-Test
Damit ist die finale Finding-Ausgabe für Audit-Reports lesbar
('Fehlt bei: Volkswagen Group Charging GmbH') statt verunreinigt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beide Funktionen wurden im run_compliance_check() aufgerufen aber nicht
oben importiert — NameError landete im except-Catch-all, jeder
Compliance-Check schlug auf "failed" um.
Bug stammt aus den letzten 2 Sprints (B12 + browser-matrix Stage 1.c)
wo die Aufruf-Stelle ergänzt, der Import vergessen wurde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the "Meine Overrides" tab in /sdk/template-rule-editor — the
mechanism by which a Kanzlei tells the system "yes, the global
recommendation says required, but for MY mandanten this is only
optional / or disabled entirely (because we have an equivalent
control elsewhere)".
Components:
- TenantOverrideList.tsx (398 LOC): tabular view with search filter,
add/edit/delete operations; one row per override showing Rule Title,
Original Classification, My Override Classification (or "Deaktiviert"
badge for disabled), Reason, Created-by/at; sticky table header.
- OverrideDialog (inline): rule picker (locked in edit mode),
classification radio group (required/recommended/optional/disabled),
mandatory reason textarea, shows the original source_citation as
context above the radio group.
- ConfirmDialog (inline): delete confirmation.
Page integration:
- New Tab system at top of /sdk/template-rule-editor:
[Globale Regeln (n)] | [Meine Overrides (n)]
- TabButton helper component (border-bottom indicator).
- loadOverrides on mount.
- handleUpsertOverride / handleDeleteOverride reload overrides after
success.
Backend integration (already in place since Phase 1):
- GET /api/sdk/v1/compliance/tenant-rule-overrides
- POST /api/sdk/v1/compliance/tenant-rule-overrides (upsert)
- DELETE /api/sdk/v1/compliance/tenant-rule-overrides/{id}
Verified end-to-end against live Mac Mini backend:
Baseline: whistleblower_policy in required (for 250_999 MA)
Add override (optional + reason): moves to optional bucket with
override_applied=true and reason concatenation
"Trifft zu: ... · Quelle: ... · Tenant-Override: required → optional (Bei meinen Tier-1-Mandanten ...)"
Delete: 204
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the sustainable backend replacement for the hardcoded inline rules in
admin-compliance/app/sdk/document-generator/templateRecommendations.ts.
What's in this commit (Phase 1.1 - 1.5 of the rustling-yawning-boot plan):
- Migration 147: 4 new tables
- compliance_template_rules (rule shell, document_type, current_version_id)
- compliance_template_rule_versions (lifecycle, JSONB conditions,
source_citation, change_summary, approval timestamps)
- compliance_template_rule_approvals (audit trail)
- compliance_tenant_rule_overrides (per-tenant classification overrides)
Plus partial unique index for "only one is_live=1 version per rule".
- SQLAlchemy models: TemplateRuleDB, TemplateRuleVersionDB,
TemplateRuleApprovalDB, TenantRuleOverrideDB (compliance/db/).
- Pydantic schemas (compliance/schemas/template_rule.py): full request/response
set including RecommendationRequest/Result with reasons and override tracking.
- TemplateRuleService (compliance/services/): CRUD + Lifecycle transitions
(submit_for_review/approve/publish/reject) following legal_document_service.py
pattern with _transition() helper and approval audit trail. Plus tenant
override upsert.
- RecommendationService: condition evaluator (eq, neq, in, not_in, gte/lte/gt/lt,
exists, truthy) over JSONB conditions, override application, reason generation
for human-readable explanations in workspace UI.
- 18 FastAPI routes in compliance/api/template_rule_routes.py covering rule CRUD,
version lifecycle, override management and POST /recommend evaluation endpoint.
- Seed data: 33 initial rules ported from templateRecommendations.ts in
compliance/data/template_rule_seed_data.py, written as published versions
on first seed run. Idempotent via rule_key.
Phase 1.6 (pytest suite) and Phase 2 (editorial UI in admin-compliance) follow
in separate commits.
[migration-approved]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wizard war bisher nur im DocCheckTab eingebaut, der aber nirgends im UI
gemountet ist. Daher: alle Compliance-Checks schickten scan_context=null,
P72 Branchen-Filter wirkte nie.
Fix: PreScanWizard ins ComplianceCheckTab über die Document-Rows
gestellt. Submit-Button disabled bis alle 8 Felder (Branche, B2B/B2C,
Direkt-Vertrieb, Rechtsform, Konzern, MA, Besondere Daten, Drittland)
gesetzt sind. scan_context wird im POST body mitgesendet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Statt EIN full-page screenshot: full-page wird per PIL in viewport-grosse
Slices geschnitten, jede ueberlappt die vorherige um overlap_px Pixel.
Jeder Cookie erscheint in mind. einer Slice, an Slice-Grenzen sogar in
zwei → Dedup nach Name eliminiert die Doppel.
Warum nicht direkt scroll-based slicing in Playwright? VW's
Cookie-Page nutzt scroll-snap / fixed-position — alle viewport-shots
kamen identisch zurueck (Header-Overlay). PIL-cut auf dem full-page
PNG bypasst das Problem voellig.
VW smoke-test (32 slices):
per-slice: [0, 0, 2, 5, 5, 3, 4, 7, 4, 3, 4, 5, ...]
103 raw cookies → 79 unique nach dedup
14 vendor records (Google 9, Adobe-Familie 17, etc.)
Jeder Slice hat eigenen Timestamp + SHA256 → ZIP-Anhang fuer
juristische Beweiskette.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VW-Loop-Iteration 1: LLM cascade lieferte 14 vendors (Lucky-Hit via
Direct-Fallback). VW-Loop-Iteration 2: 0 vendors — qwen2.5:14b
ReadTimeout auch im 420s-Direct-Fallback (50k input + 16k output
output dauert > 7min auf M4 Pro).
Fix: max_text_chars 50000 → 20000. Erfasst die ersten ~3000 Worte der
Cookie-Tabelle (Tabellen-Kopf komplett). Vollstaendige Tabelle wird
ohnehin deterministisch von parse_flat_cookie_text geparsed. LLM ist
nur fuer Vendor-Namen die NICHT in der Tabelle stehen (z.B. aus
Prosa) und Inferenz-faehiger.
Erwartung: 60-120s LLM-call statt Timeout, reproduzierbar 10-15 LLM-
Vendors → Vendor-Normalizer-Total bleibt stabil bei 20+ statt 17.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisheriges _FLAT_ROW_RE erwartete textContent-Output (Cookie-Tabelle
konkateniert ohne Whitespace zwischen Zellen). Bei VW lieferte das
deterministische 10 Vendors / 35 Cookies, aber nur weil der DSE-Text-
Fallback unvollstaendige Tabellen-Fragmente enthielt.
Beim echten cookie-richtlinie.html Fetch (8086 Worte HTML→text) sind
die Spalten durch Whitespace getrennt — und der Regex hat 0 gematcht.
Fix: \s* zwischen jedem Anker und dem Cookie-Namen erlaubt. Direct-Test
auf VW: 0 → 60 Cookies / 16 Vendors (Google 13, Adobe-Familie 16, Meta,
Salesforce, Cloudflare, Akamai etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VW-Scan-Befunde aus 0a8aa16e:
1. TCF lookup failed 5x mit: column 'source' does not exist. Korrekt:
'source_name' (siehe DELETE-Query in derselben Datei). Mit dem Fix
funktioniert das TCF-Cross-Reference fuer alle Vendors statt 0.
2. Cascade tier-1 fail loggte leere message — jetzt mit type+model+base.
3. Cascade collapse (tier 2+3 unconfigured) wird beim ersten Aufruf
geloggt damit der Operator den ENV-Mangel sofort sieht.
4. vendor_llm_extractor loggt jetzt START + 0-vendor-Return (vorher
silent skip — sah aus als waere er nie aufgerufen worden).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diese 5 Files verletzten den Hard-Cap und blockierten jeden PR der sie
touched. Pre-existing — keine neue Verletzung. Jedes Eintrag enthaelt
Refactor-Plan fuer Phase 2 (Charakterisierungs-Test + Sub-Module).
- consent-tester/services/vendor_detail_extractor.py (675)
- consent-tester/services/consent_scanner.py (567)
- backend-compliance/.../rag_document_checker.py (559)
- consent-tester/services/banner_text_checker.py (531)
- admin-compliance/app/sdk/ai-act/page.tsx (503)
Effekt: CI exit 0 ohne Verhaltensaenderung. Die exceptions-Liste muss
laut .claude/rules/architecture.md ueber Zeit schrumpfen, nicht wachsen
— d.h. diese 5 Eintraege sind explizite Tech-Debt-Marker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
check-rebuild-needed.sh war seit Mai funktionsfähig nur fuer 3 von 10
Containern. Die anderen 7 Dockerfiles hatten kein ARG/ENV BUILD_SHA und
docker-compose.yml hat fuer KEINEN Service den Wert durchgereicht — daher
defaultete BUILD_SHA ueberall auf "unknown" und die Drift-Check war
zahnlos.
- ARG BUILD_SHA + ENV BUILD_SHA in 8 zusaetzlichen Dockerfiles
(ai-compliance-sdk, developer-portal, document-crawler, dsms-gateway,
compliance-tts-service, docs-src, docs-site, dsms-node)
- docker-compose.yml: BUILD_SHA: \${BUILD_SHA:-unknown} in jedem build:
Block (10 Services)
- .gitea/workflows/ci.yaml: neuer Job build-sha-integrity validiert dass
jedes Dockerfile ARG+ENV hat und jeder compose-build den Arg durchreicht.
Faellt bei jedem PR/Push gegen master, der einen neuen Service oder
Dockerfile ohne BUILD_SHA einfuehrt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- _PROCESS_INTERNAL_PATTERNS: Patterns wurden gegen lowercased Blob
geprueft, aber Case-sensitive geschrieben (TOM/AVV/SCC). Matchen
nie. Auf lowercase normalisiert.
- "Ausnahmen ... dokumentieren": Pattern war zu eng, verlangte direkte
Adjazenz. Jetzt bis zu 60 Zeichen Wortabstand.
- Test-Suite mit 22 kuratierten DSGVO/AI-Act/eCall-MC-Labels. Alle
gruen (vorher 2/22 FAIL — beide vom User explizit als Beispiele
genannt: TOM, Ausnahmen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DSMS Stufe 3 — making the parent_cid chain useful end-to-end.
Gateway (dsms-gateway):
- /api/v1/documents/{cid}/history alias added next to the legacy
/documents/{cid}/history (history endpoint itself was already there,
just under an inconsistent prefix).
- NEW /api/v1/documents/{cid_a}/diff/{cid_b}: fetches both packages from
IPFS, computes a metadata diff (per-field old/new), and renders a
unified text diff for utf-8 payloads. Binary payloads return only
metadata diff with a "binary — compare via rendered export" note.
- 4 new pytest cases (mocking ipfs_cat): text diff, binary fallback,
fetch error, history chain depth — all green.
Frontend (admin-compliance):
- CIDHistoryModal: lazy-loads /dsms/documents/:cid/history, renders the
version chain as a vertical timeline, marks the AKTUELL entry, and
per-step exposes a "Diff zu V<n>" button that loads + renders the diff
inline (metadata table + unified text diff in a monospace panel).
- AuditTimelinePage: existing CID badge now sits next to a "Verlauf
anzeigen" link that opens the modal. Handles both Python's plain-CID
audit values and the Go techfile flow's JSON envelope {cid, filename,
size} via extractCID() helper.
This makes "show me how this CE-Akte changed between V2 and V3"
self-service in the UI instead of a curl-against-IPFS workflow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before: archiveTechFile called dsms.Archive() and discarded the result. The
file was archived to IPFS but no audit-trail entry was written, so there
was no way to later prove "this CE-Akte export went to DSMS with CID X".
After:
- archiveTechFile is now a method on IACEHandler with access to store + gin
context, and captures the CID from dsms.Archive().
- Writes an AuditAction "tech_file_export" audit entry whose new_values
JSON carries {cid, filename, size}, mirroring the Python evidence-upload
pattern.
- Applies to PDF, XLSX, DOCX, and Markdown exports.
Plus dsms package gets 3 unit tests pinning the contract: success-CID
extraction, gateway-unreachable returns nil, 500-response returns nil.
This closes DSMS Stufe 2 (evidence side was already wired; tech-file side
was missing the audit hook). Stufe 3 next: version chains + delta view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the loose end from IACE Phase 5 handover: the LLM FM-suggest button
existed and the backend endpoint was wired, but accepted suggestions had
no path into the FMEA worksheet.
Hook (useFMEA.ts):
- acceptSuggestion(fm, componentId): builds an FMEARow from FM defaults,
prepends to rows (sorted by RPZ), removes the FM from suggestions.
No-ops + drops the suggestion when (component, fm.id) is already in rows.
- rejectSuggestion(fmId): drops the FM from suggestions list.
Page (fmea/page.tsx):
- Suggestion cards now have explicit Uebernehmen / Ablehnen buttons.
- Counter "X Vorschlaege uebernommen" tracks accept count for the run.
- RPZ in each suggestion is colour-coded (red >200, orange >100).
- Hinweis line explains S/O/D adjustability after acceptance.
- acceptedCount auto-resets when suggesting starts or panel closes.
Tests (useFMEA.test.ts):
- 8 calculateAP cases covering AIAG-VDA 2019 boundary points for severity
10 / 9 / 7 / 5 / 3, validating the H/M/L action priority matrix.
LOC: fmea/page.tsx hits 320 (soft target 300, well under 500 hard cap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups to the 671-norm cross-reference matrix:
1. Tech-file renderer (Go): standards_applied section now gets a deterministic
Markdown appendix with the DIN/ANSI/GB/JIS mappings for the project's
suggested norms. Built from registry, never hallucinated by LLM. Applied
both to LLM and fallback content paths.
2. Frontend NormCrossRefPanel (Next.js): expandable row in the IACE library
norms tab now has a "Internationale Aequivalenzen anzeigen" button that
lazy-loads /iace/norms-library/:id/crossref and renders a colour-coded
table (relation + confidence). Region labels humanised (US — ANSI,
China (GB), Japan (JIS), etc.).
3. Contract tests (Go): 4 new handler tests pinning the response shape of
GetNormCrossRef and ListNormCrossRefs. Equivalent to an OpenAPI snapshot
for these specific endpoints — ai-compliance-sdk has no full OpenAPI
baseline yet (separate ticket).
Tests: 6 renderer tests + 4 handler contract tests, all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beide rufen jetzt llm_cascade.call_with_cascade() statt direkter Qwen/OVH-
Aufrufe. Damit:
* Cache-Hit auf identische Eingaben (Valkey, 7d TTL) → ~50ms statt
4-6min beim Re-Run derselben Cookie-Doc.
* Tiered Cascade automatisch: Qwen → OVH 120B → Anthropic Claude Haiku
wenn lower-tier under confidence-threshold.
* Confidence-Scoring (JSON-parse + items_per_input_size) entscheidet ob
weiter delegiert wird.
Fallback auf alte _call_ollama/_call_ovh bleibt bestehen wenn der
Cascade-Aufruf scheitert.
Erwartete Wirkung beim 2. VW-Lauf: ~10min statt ~25min (Cache-Hit auf
identische Cookie-Doc + MC-Solutions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Batch 6 (100): EN 1870 saws, EN 81 lift sub-parts, hearing/glove PPE,
EN 50126 railway, EN 60974 welding, EN 60335-2-x cleaning appliances
- Batch 7 (71): IEC 60601 medical family, EN ISO 19085 woodworking, safety
footwear (ASTM F2413), fitness (ASTM F2276), chainsaws (OPEI B175.1),
ISO 4254 agri remainder, acoustics ISO 3743/3745/3747
671 of 671 norms now have at least DIN mapping; ~80% have a US (ANSI/NFPA/
UL/OSHA/ASME/ASTM/SAE/NIOSH) mapping; ~40% have CN-GB and/or JP-JIS.
Added TestCrossRef_SpotChecks with 15 manually vetted region mappings
(IEC 60601 → ANSI/AAMI ES60601, EN 13445 → ASME BPVC, EN 60204 → NFPA 79,
ISO 10218 → RIA R15.06, etc.).
Next steps for follow-up work:
- Add OpenAPI snapshot for new /norms-library/crossref endpoints
- Front-end: render crossref panel on /sdk/iace norm detail page
- Tech file: auto-emit "this requirement also satisfies X in market Y" hints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a jurisdiction-cross-reference layer to the norms library. Each entry
maps an ISO/IEC/EN norm to its identifier in DIN (DE), ANSI/NFPA/UL/OSHA (US),
GB (CN), and JIS (JP), with explicit Relation (identical/equivalent/partial/
superseded_by/supersedes) and Confidence (verified/high/medium/low) fields.
Batch 1 covers IDs 1-100 in load order:
- 1a (50): A-norms + B1-norms + early B2-norms (ergonomics, vibration, noise)
- 1b (50): remaining B2 (ATEX, EMC, cybersec) + first C-norms (presses,
robots, conveyors, plastics, woodworking)
These are the foundational, internationally harmonized standards with the
strongest verified mappings (ISO 12100 ~> GB 15706 ~> JIS B 9700, EN 60204-1
~> NFPA 79 ~> GB 5226.1 ~> JIS B 9960-1, etc.).
API:
- GET /iace/norms-library?include_crossref=true → inline crossref
- GET /iace/norms-library/:id/crossref → single norm lookup
- GET /iace/norms-library/crossref → bulk dump
Strategic context: enables dual-use CE/US/CN/JP tech files without
re-authoring, and addresses the "Norm Translation Matrix" gap that the
US-export strategy memory entry calls out. 6 batches remaining (~571 norms)
to reach full library coverage.
Tests: 6 new tests; all pass via `go test -vet=off ./internal/iace/`.
(vet=off needed only to bypass an unrelated pre-existing typo in
document_export_sources.go.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P106 — mc_audit_type.py: zentrales Quality-Thema.
Klassifiziert pro MC: verifiable / process_internal / doc_internal /
ambiguous. Pattern-Match auf check_question + title + fail_criteria
(Schulung, AVV abgeschlossen, TOM umgesetzt, DSFA durchgefuehrt,
Ausnahmen dokumentieren, kostenfrei zur Verfuegung, opt-out
intern ermoeglichen, …).
Interne MCs werden in der MC-Auswertung NICHT mehr als FAIL gewertet,
sondern als CHECK markiert (audit_status='check'). Sie zaehlen im
build_scorecard als skipped (nicht failed) damit der Score realistisch
ist. build_internal_checks_block_html() rendert sie als separaten
blauen Block 'Pruefungen die wir von aussen NICHT durchfuehren koennen'
nach dem MC-Scorecard.
Erwartete Wirkung: bei VW 95 FAILs → wahrscheinlich 30-40 echte
verifiable_fails + 50-60 internal_checks. GF-Mail wird drastisch
realistischer (statt 'Sie haben 95 Verstoesse' → 'Sie haben 35
extern sichtbare Themen + 60 interne Checks, bitte mit DSB klaeren').
P83 — BUILD_SHA in backend/admin/consent-tester Dockerfiles als
ARG + ENV. check-rebuild-needed.sh kann jetzt deployed vs local SHA
vergleichen + REBUILD REQUIRED melden.
P80 v2 — check_replay.py macht jetzt vollstaendigen Replay aller
post-fetch Quality-Generatoren: vendor_normalizer (Dedup),
audit_quality_checks, cookie_compliance_audit, tcf_vendor_authority,
cookie_value_entropy, cookie_network_tracer. Snapshots aus alter Zeit
zeigen jetzt im Replay den aktuellen Audit-Stand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P54 — consent_diff_for_user.py: USP-Feature fuer wiederkehrende Besucher.
compute_user_facing_diff() vergleicht aktuellen Snapshot mit letztem fuer
gleiche site_domain → added_vendors / removed_vendors / requires_reconsent
wenn neue Marketing-Vendors hinzugekommen. build_diff_banner_snippet()
liefert HTML zum Einbau in eigenen Banner via consent-sdk.
P68 — reverse_audit.py: Self-Audit unserer Template-Bibliothek.
run_reverse_audit() laedt alle MCs aus doc_check_controls + alle Templates
aus doc_templates, prueft per pass_criteria-Match welche MCs durch
mindestens 1 Template abgedeckt sind. Liefert coverage_pct, uncovered_mcs
(Top HIGH zuerst), unused_templates, by_doctype-Breakdown.
P69 — data/ecall_regulation.json: eCall-VO (EU) 2015/758 als 7 Chunks
fuer RAG-Ingest (Art. 3/6/7 + compliance_implications fuer Automotive-OEMs).
Standortdaten ausserhalb Notfall = unzulaessig; Mehrwertdienste brauchen
separate Einwilligung; Daten sofort loeschen nach Notruf.
P6+P53+P55 — industry_library.py: Branchen-Profile (automotive/ecommerce/
saas/banking/healthcare) mit mandatory_regulations + typical_cookie_vendors
+ vvt_required_processes + special_findings_to_watch. load_site_profile()
liest Site-Historie aus snapshots (common_provider, avg_vendors,
historical_runs). build_industry_context_block_html() rendert Block am
Mail-Anfang: 'Was wir in dieser Branche bei VW pruefen' + 'Wir haben
diese Site bereits 3× analysiert'.
P31 — llm_cascade.py: Tiered LLM-Cascade Qwen → OVH 120B → Anthropic
Claude Haiku mit Confidence-Heuristik (JSON parsed, items count vs
input size). Valkey-Cache (redis://) mit 7-Tage-TTL plus In-Process-
Fallback. Wenn Tier-1 unter Confidence-Threshold → Tier-2, dann Tier-3.
Reduziert Lauf-Zeit drastisch bei Re-Runs.
P80 v2 — check_replay.py: replay nutzt jetzt audit_quality_checks
mit den Snapshot-Daten. Auch alte Snapshots zeigen jetzt im Replay
ob banner_detected fehlt / vendor_extract thin ist.
Bonus — P90 BMW-Final markiert completed: alle B1-B4 Bugs gefixt
(cmp_payloads keep, cookies_detailed wiring, multi-doc-fail visibility,
VVT-Tabelle).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- M600-M604: lift endstop mitigations (Kriechgeschwindigkeit, Schaltleiste,
Mindestabstand, Hold-to-run, Trittblech) — cite OSHA + EN ISO identifiers
- HP2100-HP2102: body-part crush patterns for lift family (foot under platform,
hand/body against fixed structure, leg between lift and lateral structure),
restricted via MachineTypes filter
- pattern_machinetype_overrides.go: post-load pass fills MachineTypes on 14
legacy patterns (HP1000 Walzen, HP539 Schweiss, HP545/HP782 Glas,
HP756/HP757/HP760 Fahrtreppe, HP1400-1402 CNC, HP045/HP049 Pressen,
HP420-422 Conveyor) to prevent drift on Kistenhubgeraet-style projects
Why: Kistenhubgeraet re-init exposed two gaps — the abstract "Bremse versagt
bei Absenkbewegung" pattern fired but the concrete foot-crush body-part variant
was missing, AND ~10 unrelated patterns fired purely because their RequiredTags
incidentally aligned. Override map avoids touching 1000+ LOC pattern files
that already exceed the soft cap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P81 — tests/fixtures/golden_truth/vw_de.json:
GT-Fixture mit must_find_cookies (47 VW-Cookies) + expected_vendors
(Google, Adobe, Trade Desk, ...). Basis fuer kuenftige Regression-Tests.
P85 — banner_screenshot_block.py + consent_scanner.py + main.py:
consent-tester macht beim Banner-Detect einen base64-PNG-Screenshot
(< 1.5MB). Backend rendert ihn als <img src="data:..."> direkt nach
dem GF-1-Pager. Visueller Beweis 'so sah das Banner aus' fuer Dispute
mit Marketing/DSB.
P70 — rag_provenance.py:
classify_finding_provenance() klassifiziert ein Finding als 'rag'
(Norm + Quelle), 'mixed' (Norm ohne Quelle) oder 'heuristic' (eigene
Interpretation). provenance_badge_html() rendert kleine Badges
(✓ RAG / NORM / ⚠ HEURISTIK). Modul ist generisch, kann bei jedem
Finding-Renderer einklinkt werden.
P83 — scripts/check-rebuild-needed.sh:
Prueft ob die im Container deployten BUILD_SHA mit local HEAD
uebereinstimmen. Bei Mismatch exit 1 mit 'REBUILD REQUIRED'-Hinweis.
Verhindert das 'alter Code im Container'-Problem das uns mehrfach
erwischt hat (Frontend-Tabs sichtbar, Backend ohne neuen Service).
TCF-Fix — tcf_vendor_authority.py:
cookie_library hat keinen UNIQUE-Index auf cookie_name → ON CONFLICT
war unmoeglich. Loesung: vor Insert DELETE WHERE source_name='iab_tcf_v2'.
Idempotent. + per-Vendor-Commit damit ein Fail die naechsten nicht blockt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei zusammenhaengende Stufen 'Cookie-Verhalten ist anders als deklariert' —
analog zum VW-Diesel-Skandal-Pattern (Pruefstand vs Realbetrieb).
P103 (Stufe 3) — cookie_value_entropy.py:
Klassifiziert Cookie-Werte als flag/short_id/long_token/uuid/hash/json_blob
via Shannon-Entropy + Regex-Patterns. Wenn ein als 'essential' deklarierter
Cookie einen 64-char-Base64-Wert hat → MEDIUM-Finding 'Defeat-Device-Heuristik'.
P104 (Stufe 4) — cookie_network_tracer.py:
Vergleicht Cookie-Domain mit Site-Hauptdomain + bekannten Tracker-Vendoren
(50 Domains gemapped: doubleclick.net, facebook.com, demdex.net, omtrdc.net,
adsrvr.org, hotjar.com, ...). Wenn ein als 'essential' deklariertes Cookie
von externer Tracker-Domain gesetzt wird → HIGH. Drittland-Cookies werden
als 'DRITTLAND US/CN/...' markiert (Schrems-II-Folge).
P105 (Stufe 5) — tcf_vendor_authority.py:
Ingest-Endpoint POST /api/compliance/agent/admin/tcf-ingest holt die
IAB TCF v2 Global Vendor List (vendor-list.consensu.org/v3) und upserted
sie in cookie_library mit source='iab_tcf_v2'. cross_reference_with_tcf
fuzzy-matched cmp_vendors gegen die TCF-Liste — wenn Vendor in TCF als
Marketing gefuehrt aber Site sagt 'Funktional' → HIGH (externe Authority
widerspricht der Deklaration).
Alle drei rendern eigene Mail-Bloecke im Bereich Cookies (nach
cookie_audit_html, vor library_mismatch_html).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three coupled pieces of work, all landing the same PoC:
1. Backend gap-review endpoint (Task #7)
- internal/api/handlers/iace_handler_gap_review.go:
POST /projects/:id/llm-gap-review
feeds Limits-Form + current hazards + current mitigations to
the configured LLM (Qwen / Claude / OpenAI via ProviderRegistry),
parses a JSON suggestion list, filter+stamps confidence, falls
back to a static checklist when LLM is unavailable.
- Adopt step is NOT in this endpoint by design — the user clicks
Adopt in the frontend which calls the existing CreateHazard /
CreateMitigation handlers so provenance flows through the normal
audit trail.
2. Frontend modal + button (Task #8)
- app/sdk/iace/[projectId]/hazards/_components/LLMGapReviewModal.tsx:
reusable modal that POSTs the gap-review endpoint, renders
suggestions with Adopt/Reject UX, shows confidence + norm refs,
source-stamp llm_gap_review vs fallback_static.
- hazards/page.tsx: indigo "KI-Gap-Review" button next to the
existing "Eigene Gefaehrdung" button + modal mount.
3. Tech-File sources appendix (Task #29 — Stufe 4)
- internal/iace/document_export_sources.go: new pdfSourcesAppendix
method appended to ExportPDF. Groups cited norms by license rule
(R1 OSHA/EU-Recht / R3 BreakPilot patterns / R3 DIN-EN-ISO
identifier-only) and emits the legally required statement that
pauschal Impressum-Hinweise nicht ausreichen.
- extractCitedNorms() scans hazard/mitigation text for EN/ISO/IEC/
DIN identifiers in a narrow grammar so prose isn't turned into
spurious citations.
Bonus refactor:
- internal/app/routes.go reached the 500-LOC hard cap when the new
llm-gap-review route was added. Extracted registerIACERoutes into
routes_iace.go (136 LOC). Same wiring, no behaviour change.
Three of the four Attribution-Renderer stages (1, 2, 4) now produce
real output. Stufe 3 ships as <SourceBadge> + <LicenseModuleBanner>
already (commits dfac940 + b9e3eea earlier in this branch).
The PoC is intentionally conservative: every LLM-Suggestion stays
unverbindlich until a human clicks Adopt, and Adopt goes through the
existing normal CreateHazard/CreateMitigation flow (not yet wired in
this commit — separate iteration). The endpoint, modal and provenance
chain are in place for the next iteration to wire Adopt → write path.
ResultsTabsView.tsx — neue Komponente mit 7 Tabs:
1. Übersicht (KPIs: Docs, Findings, Vendors, Score)
2. Cookies & VVT (3-Quellen-Compliance-Vergleich +
undokumentiert/compliant/nicht-geladen + deduplizierte Vendor-Tabelle)
3. Datenschutzerklärung (DSE-Findings via ChecklistView)
4. Impressum
5. AGB / Widerruf (zwei Sections in einem Tab)
6. Cookie-Banner (Verstoesse + Phasen-KPIs)
7. Mail-Vorschau (PDF-Download-Link)
Sticky Tab-Header oben, Content scrollt darunter. Lange Scroll-Mail
ist damit verschwunden.
DocCheckTab nutzt ResultsTabsView statt der alten Inline-ChecklistView.
Backend liefert jetzt cookie_audit-dict in der Response (zusaetzlich
zu cmp_vendors + banner_result) damit das Cookie-Tab die 3 Listen
(undokumentiert / compliant / nicht-geladen) rendern kann.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verbatim OSHA 29 CFR 1910 Subpart O values anchored as the rechtssicher
zitierbare Werte-Basis for the IACE engine. Per strategy discussion
(2026-05-20) US Federal Code is the only public-domain corpus we can
reproduce wholesale; DIN/EN values stay identifier-only.
Coverage in this initial batch:
- MD_OSHA_O10_R1, MD_OSHA_O10_R4 (Table O-10 rows 1 + 4 — point of
operation guard distance vs max opening width)
- MD_OSHA_212_FAN (§1910.212(a)(5) fan-blade guards: 1/2 in)
- MD_OSHA_217_PSDI (§1910.217 hand-speed constant 63 in/s for
presence-sensing-device-initiation and two-hand-trip distances)
Each entry carries four parallel value sets:
- OriginalValue/Min/Max in source unit (verbatim, R1)
- ExactMM via deterministic conversion (mathematics, no copyright)
- RecommendedMM with safe-side rounding documented in RoundingNote
- EUNormHints — identifier-only references to EN ISO 13857, EN 13855,
EN 349 with a human-curated DINComparisonNote (qualitative judgement,
not a copy)
Open follow-ups (separate iterations):
- Full Table O-10 (rows 2-10) — same shape
- §1910.219 mechanical power-transmission distances
- Cross-reference IACE patterns to MD_OSHA_* identifiers so the Suppression
Engine surfaces concrete metric values in mitigation suggestions
- Frontend integration: <MinimumDistanceCard> for each measure
Task #17 — Folgegefahren-Modell as Vorbereitungs-Commit (no DB schema
change yet; persistence via separate [migration-approved] commit).
New:
- secondary_harms.go: SecondaryHarm struct + six canonical categories
(consumer_safety, product_liability, food_safety, environmental,
reputation, financial) with DE labels.
- hazard_pattern_types.go: HazardPattern extended with optional
SecondaryHarms field — pattern library can now attach consequential-
damage chains.
- hazard_patterns_secondary_demo.go: two worked examples
- HP2000 Glasbruch carbonated bottling (the "Cola splitter" scenario
from the IACE strategy discussion) with consumer_safety + food_safety
+ reputation chains
- HP2001 Pharma fill-finish cross-contamination with consumer_safety
+ product_liability under AMG §84
Bonus fix:
- compliance_crossover.go AllPatterns() was a duplicate enumeration that
silently drifted from collectAllPatterns() in pattern_registry.go.
Pre-fix: 1058 patterns visible. Post-fix: 1213 patterns. The 155 invisible
patterns included CRA, ISO12100 gaps, robot-cell, CNC extended, VDMA,
textile-agri, GT-bremse — anything added after the original AllPatterns
was authored. Audit-Suite (cmd/iace-audit) now sees the full set.
Next steps for full secondary-harm rollout:
- DB migration: hazards table + secondary_harms array column
- API: surface secondary_harms in /projects/:id/hazards response
- Frontend: collapsible Folgegefahren-Panel in HazardTable
Per project_sdk_module_attribution_matrix.md the Stufe-3 rollout is
prioritized by audit visibility. This batch covers Schritte 2-9 in one
sweep:
New reusable component:
components/sdk/LicenseModuleBanner.tsx — single-line license banner
placed at the top of an SDK module page. Renders rule pill (R1/R2/R3),
source label, descriptor and link to /sdk/licenses. Replaces the
copy-paste banner blocks I inlined in the earlier modules.
Integration points (per cluster):
Cluster B (DSGVO/EU-Recht, R1):
- vvt: existing "Vorlage" pill upgraded with R1 marker + tooltip
explaining Bundeslaender-DSGVO provenance
- dsfa: inline R1 banner citing DSGVO Art. 35
Cluster C (EU AI Act / CRA, R1):
- ai-act: inline R1 banner citing EU 2024/1689
- cra: inline R1 banner citing EU 2024/2847 + ENISA-Guidance
Cluster D (Mix R2/R3):
- isms: R3 banner + ISO/IEC 27001 reference disclaimer
- security-backlog: R2 banner with OWASP CC-BY-SA attribution
Cluster A (Eigenwerk, R3):
- tom-generator: R1 source (DSGVO Art. 32) + R3 own-work disclaimer
- audit-checklist: R3 banner for own audit methodology
- document-generator: own templates R3 + cited rights R1
Cluster E (Direct controls listing):
- catalog-manager: System/User tag upgraded with rule classification
- iace hazards: pattern_id pill upgraded with R3 + tooltip explaining
BreakPilot Pattern-Engine provenance
The 11-module sweep brings audit transparency to the modules a paying
customer encounters most often. Stufe 3 of the attribution renderer
is now actually visible across the platform — previously it shipped
only the reusable <SourceBadge> component without integration points.
Pre-existing TS errors (drafting-engine constraint-enforcer, dsfa
types tests) untouched — not in scope for this licensing rollout.
Per the SDK-Modul Attribution-Matrix (project_sdk_module_attribution_matrix.md),
the controls/atomic-controls listings render canonical_controls directly and are
the highest-audit-visibility integration point for Stufe 3.
Two changes:
1. atomic-controls/page.tsx: embed <SourceBadge controlUuid={ctrl.id} compact />
next to the existing badge row in each control item. The badge fetches
/api/compliance/licenses/source-info/{uuid} on first hover and reveals the
source regulation, license type, and attribution text in a tooltip.
2. control-library/components/helpers.tsx: fix LicenseRuleBadge labels. The
existing pill said "Free Use / Zitation / Reformuliert" — exactly the
inverted understanding of the rules that Task #21 surfaced. Corrected to
R1 (verbatim, Hoheitsrecht/PD), R2 (verbatim + attribution), R3 (identifier
only). Added native title attribute for hover-explanation; the existing
ControlListItem in control-library now shows the right semantics
without any other code change.
Next module per matrix: VVT (Bundeslaender-Vorlagen) and DSFA.
Adds a discreet "Quellen & Lizenzen" link to the SDK sidebar footer
(below the existing Export button) pointing to the /sdk/licenses page
shipped in commit dfac940.
Part of Task #24 (AGB/Impressum audit) — the legal mandate that
attribution be discoverable for every output is now satisfied at
three layers:
- platform-wide overview reachable from every SDK page (this commit)
- per-export footer in compliance PDFs (commit 07cc00d)
- inline source badge per control via <SourceBadge> (commit dfac940)
Extends CompliancePDFGenerator with a "Quellen & Lizenzen" section
appended to every generated compliance PDF.
The footer is built from compliance.canonical_controls + control_parent_links
directly (no HTTP hop to /licenses/aggregate — same DB connection
already open in the generator). It groups by license_rule and lists
the top 8 source regulations per bucket.
For Rule-2 entries (CC-BY-SA, OECD-Public, Apache, etc.) it emits the
mandatory attribution paragraph required by the underlying licenses.
For Rule 1 a brief reference list satisfies the auditability goal
without legal obligation. Rule 3 is identifier-only by design.
Architecture decision: this is a PLATFORM-level footer (which sources
the platform draws on overall), not a per-export filter of "only the
sources actually cited in THIS document". The latter would require
control-uuid tracking across all sections (TOM/VVT/DSFA/etc.) which
the current PDF generator does not surface — that's a follow-up scope.
The platform-level footer fulfils the immediate legal mandate that
attribution be present on the work, not buried in AGB/Impressum.
Part of Attribution-Renderer Task #23. Stufe 1 (overview page) +
Stufe 3 (SourceBadge component) already shipped in commit dfac940.
Stufe 4 (tech-file appendix) remains for the IACE tech-file generator
in a separate iteration.
VW Cookie-Doc liefert die Tabelle als FLACHEN Text ohne Spalten-Trenner:
'IDE Tracking Cookies (Marketing) Beschreibung 13 Monate Permanent
TAID Tracking Cookies (Marketing) ...'
parse_flat_cookie_text matched mit Regex:
NAME [Tracking|Session|Funktional|...] Cookies ... [13 Monate|Session|Permanent]
Backend faellt bei parse_cookie_table=[] auf parse_flat zurueck. Damit
holen wir aus dem 65k VW Cookie-Doc ~30-50 Cookies + Vendors deterministisch,
auch wenn der HTML-Table-DOM-Extract leer ist (was passiert wenn die
Tabelle aus mehreren append-Code-Pfaden geladen wird).
Bonus: _extract_dom_tables Helper in dsi_discovery.py vorbereitet fuer
spaeteres Einhaengen an allen 7 DiscoveredDSI.append-Stellen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend
- backend-compliance/compliance/api/licenses_routes.py: three endpoints
built on the now-complete license_rule classification
- GET /api/compliance/licenses/overview
global aggregation by rule + per-source breakdown (Stufe 1)
- POST /api/compliance/licenses/aggregate
per-control-set aggregation for PDF footer (Stufe 2) and
tech-file appendix (Stufe 4) — consumed later
- GET /api/compliance/licenses/source-info/{control_uuid}
single-control lookup for the inline source badge (Stufe 3)
- registered in api/__init__.py via the existing safe-import loader
Frontend
- app/sdk/licenses/page.tsx (Stufe 1): the /sdk/licenses overview page.
Renders rule legend cards + per-rule source tables. Drives the
/licenses footer link and gives auditors a one-page view of what
licence classes the platform is operating under.
- components/sdk/SourceBadge.tsx (Stufe 3): reusable React component.
Small R1/R2/R3 pill with click-expand tooltip showing source
regulation + attribution string + render-full-text policy. Will be
embedded into IACE hazards/mitigations, VVT items, DSFA controls in
follow-up commits.
Two stages of the four-stage renderer are now ready. Stufe 2 (PDF
auto-footer) + Stufe 4 (tech-file appendix) follow once the existing
PDF generators are extended to call /licenses/aggregate.
Drei zusammenhaengende Fixes fuer den VW-Befund (6 Vendors statt 100+):
A — audit_quality_checks.py: drei systemische Vorbehalte die IMMER prominent
gezeigt werden:
* banner_detected=False trotz Cookie-Doc → HIGH 'CMP-Tool ungeladen'
* cookie_doc >= 30k chars aber cmp_vendors < 15 → HIGH/MEDIUM
'Vendor-Liste auffaellig kurz fuer Doc-Groesse'
* submitted URL aber 0/Mini-Text → MEDIUM 'URL nicht ladbar'
Rote Audit-Vorbehalt-Box ueber dem GF-1-Pager. GF-Summary sagt
'Audit unvollstaendig' statt faelschlich 'Keine kritischen Themen'.
gf_one_pager nimmt audit_quality_findings in top_findings auf
(BEVOR andere Findings).
B — cookies_table_parser laeuft jetzt auch auf gecrawltem Cookie-Doc-
Text (nicht nur bei User-Paste). Wenn der dsi-discovery-Response Tab/
Pipe-getrennte Tabellen-Reihen liefert, parsen wir sie deterministisch.
D — consent-tester/dsi-discovery extrahiert jetzt zusaetzlich zum
Text die <table>-Elemente aus dem DOM als list[str] (Tab-getrennt pro
Zeile, mind. 2 Zellen, mind. 3 Zeilen, max 10 Tabellen pro Doc). Backend
schleust diese als 'html_table'-cmp_payload ein und jagt sie zuerst durch
cookies_table_parser → 100% deterministische Vendor-Extraktion ohne LLM.
VW-Erwartung: aus der 65k-Cookie-Tabelle werden jetzt 30-50 Vendors
deterministisch geparst statt 6 vom LLM-Cascade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hintergrund: VW liefert ueber URL-Crawler nur 6 Vendors statt der 100+
die in der echten Cookie-Tabelle stehen. Wenn der User die Tabelle aber
direkt von der Site kopieren kann (was bei den meisten OEM-Sites moeglich
ist), umgehen wir den Crawler komplett und parsen den Text deterministisch.
Backend:
* doc_type_classifier.py — 7 Pattern-Gruppen (§5 TMG, Art.13 DSGVO,
AGB-Klauseln, Widerrufs-Frist, Cookie-Tabellen-Header, etc). Wenn der
User Text ins falsche Doc-Type-Feld kopiert (Impressum->DSE),
detect_mismatch liefert detected + action ('reclassify' bei sehr hoher
Konfidenz, 'warn' bei medium).
* cookies_table_parser.py — Tab/Pipe/Komma/Semicolon-Separator-Auto-
Detection, Spalten-Mapping per Header-Keyword. Aggregiert Cookie-
Eintraege zu Vendor-Records (mit _guess_vendor-Fallback). Voll
deterministisch, kein LLM.
* doc_input_warnings.py — Mail-Block ueber dem Audit, der Mismatches +
Auto-Reclassifies dem User transparent macht.
* Pipeline: text gewinnt ueber url (war schon im Schema vermerkt), neue
Felder declared_doc_type / input_source / reclassify_hint in doc_entries.
Pasted-Tabellen-Vendors haben Vorrang vor Library-Fallback + LLM-Cascade
(sind 100% genau).
Frontend (DocCheckTab):
* Pro Row Mode-Toggle 'URL' / 'Text einfuegen' (lila wenn aktiv).
* Textarea (h-32, monospace) im text-mode mit kontext-spezifischem
Placeholder (Cookie-Hinweis ggue. anderen Doc-Types) und Live-
Zeichen-/Wort-Counter.
* Submit-Button accepted entries mit URL ODER text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VW-Lehre: cmp_vendors=6 (alle LLM-grob) wurde als ausreichend gewertet,
obwohl die echte Cookie-Tabelle 30+ Eintraege hat. 3 Fixes:
1. fallback_vendors_for_run skip-Schwelle: existing_vendor_count >= 3
war zu niedrig. Jetzt nur skip wenn < 5 Cookies UND >= 5 Vendors
schon vorhanden.
2. Library-Fallback wird jetzt aufgerufen bei < 20 cmp_vendors (statt
< 3). VW-typische Setups (6 LLM-grob + 30 aus Library) bekommen
damit eine vollstaendige Vendor-Liste.
3. _extract_cookie_names_from_doc: regex-Pattern-Extract aus dem
Cookie-Doc-Text selbst — sucht nach 'NAME Tracking Cookies (Marketing)'
etc. Findet Cookie-Namen die NICHT im Browser-Jar landen (z.B. nur
nach Consent geladen werden). Diese werden zusaetzlich durch die
Library matched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jc_avv_decision.py: detect_ambiguous_jc_avv prueft ob DSE-Text sowohl
JC-Signale (gemeinsame Auswertung, Schwesterunternehmen, Konzern...)
als auch AVV-Signale (Auftragsverarbeiter, weisungsgebunden...) enthaelt.
Bei Treffer rendert build_jc_avv_decision_html einen Block mit 4 EDPB-
basierten Leitfragen + jeweiliger Empfehlung.
Quellen: EDPB Guidelines 7/2020, EuGH C-25/17, C-40/17.
In Mail-Render zwischen Solutions-Block und VVT eingehaengt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VW-404-Fix: submitted_types zaehlt jetzt nur Doc-Types mit >= 200 Zeichen
echtem Text. Eine eingegebene URL die 404/Mini-Text liefert (VW cookie-
richtlinie.html) wird als 'missing' behandelt, sodass Auto-Discovery
alternative URLs auf der Homepage probiert. In-place-Update statt
Duplicate-Entry, rejected_url wird fuer Audit-Transparenz aufgehoben.
P52 LLM-Cascade Merge: vendor_llm_extractor laeuft jetzt bei < 5 Vendors
(nicht nur bei 0), und die Ergebnisse werden MIT existing cmp_vendors
gemerged statt zu ueberschreiben. VW-typische Setups (Generic CMP +
0 cmp_payloads) bekommen damit den Text-basierten Vendor-Layer dazu.
P51 — banner_consistency_checks erweitert:
* check_banner_copyability: scannt banner_html nach user-select:none /
oncopy=return false / onselectstart. MEDIUM Finding wenn Banner-Text
nicht kopierbar (Art. 7 (2) DSGVO).
* check_consent_history: prueft auf 'Meine Einwilligungen' / Consent-
Historie / Datenschutz-Cockpit. MEDIUM wenn keine sichtbare Historie
(Art. 7 (3) — Widerruf muss so einfach wie Erteilung sein).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P72 — rag_document_checker LEFT JOINs canonical_controls.scope_doc_type.
_filter_by_canonical_scope wirft MCs raus deren scope explizit auf
einen inkompatiblen Doc-Type zeigt (Mapping in _SCOPE_COMPATIBLE).
Konservativ: 'other'/NULL/'process' bleiben drin — Heuristik v1 ist
noch nicht stark genug fuer hartes Filtern.
Erwartete Wirkung: ~10-15% weniger irrelevante MCs pro Doc, weil z.B.
ein TOM-MC nicht mehr als DSE-Finding auftaucht.
P73 — mc_solution_generator.py: Qwen->OVH Cascade generiert pro HIGH/
CRITICAL-Fail eine konkrete Einfuege-Empfehlung mit Anchor (wo + was)
und Aufwand-Schaetzung. JSON-Schema {solution_text, anchor_hint,
effort_min}. In-process LRU-Cache (500 entries) per (mc_id, doc_md5).
Max 3 Solutions pro Doc-Type, global Cap 8 — haelt Latenz < 60s. Bloecke
werden im Mail-Render unter VVT als 'Loesungs-Vorschlaege (KI-generiert)'
eingehaengt. Disclaimer: kein Rechts-Beratung, mit DSB pruefen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
check_three_source_vendor_consistency: scannt DSE-, Cookie-Doc- und
Banner-Vendor-Liste auf 15 typische Vendor-Signaturen (Google Analytics,
Meta Pixel, Hotjar, HubSpot, LinkedIn Insight, ...). Listet Vendors die
in mind. einer Quelle stehen, aber nicht in allen sources_with_data.
Liefert MEDIUM-Finding mit konkreter 'fehlt in: DSE, Banner-Liste'-
Liste pro Vendor. Empfehlung: zentrale Vendor-Liste pflegen + in alle
drei Dokumenttypen propagieren. (Art. 13(1)(c)+(e) DSGVO)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /api/compliance/agent/snapshots/{id}/pdf liefert application/pdf
mit dem vollen Audit-Mail-Inhalt im A4-Print-Layout (Header mit
Site/Timestamp/Snapshot-ID, Seitenzahlen unten rechts).
check_replay.py liefert jetzt zusaetzlich 'full_html' (nicht nur
500-char-preview), damit der PDF-Renderer das komplette HTML hat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
check_replay.py rendert jetzt auch die Textsignal-Findings (Save-Label-
Ambiguitaet, Cookies-in-DSE-Akzeptanz, JC-Klausel positiv, Social-Embeds).
Damit hat der Replay-Test parity mit der echten Mail-Pipeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn nach Standard-Extract + Phase-G + LLM-Cascade weiterhin < 3 cmp_vendors
aber >= 5 Cookies im after_accept stehen (typisch: Custom-CMP wie VW
'cookiemgmt'), matcht der Fallback die Cookie-Namen gegen die
compliance.cookie_library und rekonstruiert Vendor-Records aus den
Library-Eintraegen.
Hintergrund: VW Run de2a029e zeigt 4 Vendors trotz 28 after_accept-Cookies.
cmp_payloads ist 0 (kein bekanntes IAB-Tool erkannt) und die hinterlegte
Cookie-URL liefert 404. Die DSE ist mit 34k zwar substanziell, listet aber
keine Vendor-Tabelle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_score_band_explanation: vier Baender (Sehr gut/Akzeptabel/Handlungs-
bedarf/Erhoehtes Risiko) liefern Label + erwartete Handlung. Wird als
neue Zeile unter den KPIs in der Exec-Summary gerendert (mit
score-farbiger Linkmark).
Sachlicher Ton — kein 'Vorstand muss sofort handeln', sondern
realistische Empfehlung (z.B. '70-84: Branchen-Median, einmaliges
Aufraeumen + Halbjahres-Check').
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
check_social_embedding: erkennt direkte FB/Insta/Twitter/YouTube-
Embeds (connect.facebook.net, platform.twitter.com etc) vs
Heise-Shariff vs 2-Klick-Loesungen (Embetty).
Direkte Embeds ohne Schutz = HIGH (EuGH C-40/17 Fashion-ID — der
Site-Betreiber wird zum gemeinsam Verantwortlichen und braucht
Einwilligung VOR dem Drittanbieter-Call).
Shariff oder 2-Klick erkannt = INFO (positives Signal).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P86 — industry_benchmark.py: zieht alle Snapshots mit derselben
scan_context.industry, berechnet Median + Percentile, rendert
'Sie 42% — Automotive-Median 58% (Stichprobe: 12)'. Min Sample 3.
P35 — banner_text 'Speichern' ohne 'Ablehnen' = MEDIUM. Mehrdeutiges
Label nach EDPB 03/2022 Deceptive-Design-Guidelines.
P77 — DSE mit prominenter Cookie-Sektion (Vendor-Hints: Speicherdauer,
Anbieter, Datenkategorie) ersetzt die Forderung nach separater
Cookie-Richtlinie. Positives Signal statt False-Positive.
P78 — Art. 26-Klausel im DSE-Text erkannt → positives Signal
'JC-Konstrukt dokumentiert'. Vermeidet False-Positive bei
Konzern-Schwester-Kooperationen.
Alle in Mail eingehaengt: Branchen-Block nach GF-1-Pager, Signale-Block
nach Konsistenz-Check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P75 — check_banner_vs_cmp_partner_count: wenn Banner-Text 'N Partner'
nennt und N < cmp_vendors * 0.6, HIGH-Finding (Art. 13(1)(e) DSGVO).
Erkennt Verharmlosung der tatsaechlichen Vendor-Anzahl.
P84 — run_diff.py: vergleicht aktuellen Lauf mit letztem Snapshot
derselben Site (set-Diff auf normalisierten Finding-Labels). Block
ueber dem GF-1-Pager: 'Seit letztem Lauf: X Findings weg, Y neue'.
USP — keiner der grossen Anbieter hat das.
P74/P96/P97 — Labels fuer legal_notice (Rechtliche Hinweise / IP /
Forward-Looking), dsa (Art. 12+17 Digital Services Act), lizenzhinweise
(OSS-Compliance) in _DOC_TYPE_LABELS registriert. Echte Pflichtangaben-
Checks kommen separat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P92 — Wenn der Nutzer 'Anpassen'/'Einstellungen' klickt und der
CMP-Settings-Bereich kein Fehlerfreies Laden zeigt (Error, Timeout,
<80 Zeichen ohne Kategorien, keine Toggles), ist das ein HIGH-
Finding. Granulare Wahl formal vorhanden, faktisch nicht
funktionsfaehig (Art. 7 (3) DSGVO + EDPB 03/2022).
P94 — Cookie-Liste im Banner-Settings vs Cookie-Richtlinie. Heuristik
extrahiert Cookie-Namen aus dem Cookie-Doc-Text (regex auf typische
camelCase/_underscored Patterns + Vendor-Prefixes _ga/_gid/ot_/uc_).
Wenn |only_in_doc| >= 5 ODER |only_in_banner| >= 3 → MEDIUM-Finding.
|only_in_doc| >= 15 UND |only_in_banner| >= 5 → HIGH.
Beide Findings landen im neuen Mail-Block 'Banner-Konsistenz-Pruefung'
(amber-yellow) zwischen Mismatch-Block und VVT. Auch in
check_replay.py eingehaengt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P82 — gf_one_pager.py: kompakte 5-Bullet-Kurzfassung ganz oben in der
Mail. Score (gross + Farbe), Delta-zu-Vorlauf, Top-Findings nach
HIGH/MEDIUM sortiert mit zustaendiger Rolle (DSB / Marketing / IT /
Legal / Web-Team) und Klassifizierungsbits aus dem Wizard.
Sachlicher Ton — keine 4%-Drohung, '4-8 Wochen' als realistischer
Zeitrahmen. Eingehaengt vor Critical-Findings-Block in Mail-Composition
und Replay-Pipeline.
P87 — finding_confidence.py: 13 Regex-Regeln liefern (confidence_pct,
reason) pro Finding-Label. Direkt im DOM beobachtbar = 95-98%,
Library-Mismatch = 82%, Textmuster-Match auf Pflichtangaben = 75-88%.
Im 1-Pager als kleines '(NN% Konfidenz)'-Tag mit Reason-Tooltip
hinter jedem Finding gerendert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P79: PreScanWizard.tsx mit 8 Pflichtfeldern (Branche, B2B/B2C,
Direkt-Vertrieb, Rechtsform, Konzern-Struktur, MA-Zahl, Besondere
Daten, Drittland). Scan-Button disabled bis alle 8 ausgefuellt. Werte
landen in scan_context und ueber Backend in compliance_check_snapshots.
P99: DOC_TYPES um dsa + legal_notice + lizenzhinweise + nutzungsbedingungen
erweitert. URL-hinzufuegen-Button war schon da.
P102 (Replay-Bug): check_replay.py liest jetzt e.get('text') statt
nur full_text — Snapshot-Schema verwendet 'text'. Library-Mismatch-
Block wird damit auch im Replay angezeigt.
Backend: ComplianceCheckRequest.scan_context optional; save_snapshot
persistiert ihn in compliance_check_snapshots.scan_context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VW-Bug B1: extract_vendors_via_llm hatte max_text_chars=12000 -> bei
VW-Cookie-Doc (60k chars, 100 Cookies in Tabelle) wurden 80% abgeschnitten,
LLM extrahierte nur 1 Vendor. Fix: max_text_chars=50000, num_predict
6000->16000 fuer mehr Vendor-Output, Ollama-Timeout 120s->420s.
P101 Aggregator-Script (backend-compliance/scripts/cookie_library_enrich.py)
geht alle compliance_check_snapshots durch und extrahiert (cookie_name,
declared_category, observed_sites). Erste Auswertung ueber 8 Snapshots:
101 unique Cookies, 47 in Library, 54 unbekannt, 18 Mismatches.
P102 Cookie-Klassifikations-Pruefung als Mail-Block. Vergleicht
Site-deklarierte Kategorie vs Library + Vendor-Doku. HIGH wenn Library
sagt 'marketing' aber Site als 'essential'/'statistics' deklariert
(faktische Drittland-/Werbe-Verarbeitung versteckt). MEDIUM sonst.
In agent_compliance_check_routes Mail-Komposition + Replay-Pipeline
eingebaut.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P98: HTML-Tabellen-Zellen wurden bei VW-Cookie-Richtlinie ohne Whitespace
verkettet ('smartSignals2UiDsmartSignals2sUiDsmartSignals2CPs...'). Grund:
el.textContent ignoriert Block-Element-Grenzen. Fix: innerText (whitespace-
respecting) statt textContent. Cookie-Namen werden jetzt einzeln erkannt —
VW-Lauf sollte ~100 Cookies statt 1 finden.
P100: Banner-Check fuer 'Anpassen'/'Einstellungen'-Button im Initial-Banner.
VW-Pattern: nur 2 Buttons (Nur technisch notwendige / Alle akzeptieren),
keine granulare Wahl vor Akzeptanz/Ablehnung. Faktische Manipulation
Richtung Pauschal-Akzeptanz. HIGH-Finding nach EDPB 5/2020 §82.
Pattern: anpassen/einstellungen/cookie-einstellungen/manage cookies/
preferences/customize.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher hatte ich den Container hotfixed aber den Fix nicht committed.
Beim naechsten Rebuild kam der Bug aus dem Image zurueck.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P93: 'Cookies verbieten', 'Tracking ablehnen', 'verweigern' usw. zaehlen
nun als expliziter Reject-Mechanismus. EDPB 5/2020 schreibt kein bestimmtes
Wort vor — BMW False-Positive 'Kein Ablehnen-Mechanismus' weg.
P95: cookie_table-Check akzeptiert nun zwei gleichwertige Formate:
(a) klassische Tabelle, (b) Vendor-Detailseite mit Block pro Anbieter
(Name+Anschrift, Zweck, Speicherdauer aggregiert, Cookie-Namen-Liste,
Opt-Out-Link). BMW-Stil mit Adform-Block ist DSK-OH 2024 konform.
False-Positive 'tabellarisches Cookie-Verzeichnis fehlt' wird seltener.
Hinweis-Text in cookie_table umformuliert: nennt beide akzeptablen
Formate, weniger normativ.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BMW-Lauf 760de886 hat 0 cmp_payloads obwohl consent-tester ePaaS 4x captured.
Backend-Log zeigt 'Consent-tester fetch failed for ...anbieterkennzeichnung.html: '
mit LEEREM Exception-String. Auch 'auto-discovery failed for https://www.bmw.de/: '
ist leer. Quick-Fix: str(e) + type(e).__name__ in beiden Except-Bloecken,
damit naechster BMW-Lauf den echten Fehler sichtbar macht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BMW-Lauf 9811eba1 hatte 0 cmp_vendors obwohl consent-tester ePaaS 4x
captured (~393KB). Root-Cause in _fetch_text Z.1254:
if merged and len(merged.split()) > 100:
return merged, cmp_payloads
Wenn DSE/Cookie-URL nur kurzen SPA-Shell-Text liefert (BMW: 10 Worte),
greift die Schwelle nicht — Code faellt durch zum HTTP-Fallback der
return text, [] zurueckgibt. Die zuvor captured CMP-Payloads (ePaaS-JSON
mit allen Vendor-Daten) werden komplett verworfen.
Fix: vor dem HTTP-Fallback pruefen ob cmp_payloads vorhanden sind. Wenn ja,
diese zurueckgeben mit dem (kurzen) Text oder dem rekonstruierten
cmp_cookie_text. Auch ohne 100-Wort-Schwelle.
Effekt: BMW-VVT-Tabelle wird gefuellt (~90 Vendors aus ePaaS-JSON).
Mercedes/andere OEMs unveraendert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-Feedback in einer Session: "Wir erzeugen nur Panik. Egal was da steht,
es dauert Wochen. Wir sind Tool an der Seite von CMO/GF/CIO, nicht Gegner."
Memory: feedback_breakpilot_tonalitaet.md (gilt fuer ALLE Module + Marketing).
P89 Critical-Findings-Block ENTFERNT/UMGEBAUT — keine Panik-Rot-Box mehr.
- Statt "🚨 SOFORTMASSNAHMEN ERFORDERLICH" -> "Zusammenfassung fuer
die Geschaeftsfuehrung", blauer dezenter Block
- Statt "VERSTOSSE" -> "Themen zur Besprechung mit DSB, Marketing
und Entwicklung"
- Statt "Bussgeldrahmen 4% Weltumsatz" als Erstes -> realistische
Einordnung (0,1-1%) in dezenter Schluss-Notiz mit Konfidenz-Hinweis
- "Sofortmassnahme" -> "Empfehlung"
- "Themen 1, 2, 3..." statt "HIGH"-Badges (P87-Vorbereitung)
- Explizite Zeitschaetzung "4-8 Wochen (DSB -> Agentur -> Dev -> Freigabe)"
P76 Mercedes-Sekundaer-Buttons (Datenschutzerklaerung + Impressum klein
unter den 3 Haupt-Buttons) erkennen. Walker scant jetzt label-basiert
ALLE klickbaren Elemente im Shadow-DOM (wb7-link, wb7-link-secondary,
wb7-button-text, span[onclick], small a, [role=button], etc.).
Vermeidet Mercedes-Impressum-False-Positive der Phase 1.
P91 VVT-Tabellen-Renderer in neuer Co-Pilot-Tonalitaet. Statt
"Verstoss-Liste mit Bussgeldpotenzial" -> Wahrscheinlichkeits-Aussage:
"Bei Anbieter-Reduktion + Wechsel zu europaeischen Alternativen ist
Reduktion des Tracking-Footprints + Lizenz-Einsparung wahrscheinlich.
Fundierte Bewertung erfordert DSB-Abstimmung."
BMW-Bug B1-B4 (P90) bewusst nicht in diesem Commit: BMW-Lauf hat ePaaS
4x captured im consent-tester, aber Backend bekommt 0 cmp_payloads.
Wiring-Bug zwischen consent-tester /dsi-discovery und Backend
_fetch_text — eigene Diagnose-Session noetig (siehe Task P90).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P72 MC-Scope-Classifier — pro MC den ECHTEN Doc-Adressaten festlegen
(cookie_richtlinie/dse/banner_implementation/cmp_audit/tom/avv/jc/
impressum/agb/widerruf/process/accounting/other).
- Migration 145: scope_doc_type Spalte + Index auf canonical_controls
- Backfill-Script mit Regex-Heuristik (12 Regeln, Prioritaet-sortiert)
- Erste 11k-Sample-Distribution: 76% other (Heuristik v1 zu strict —
v2 muss lockerere Patterns fuer DSE/TOM nachschaerfen)
- Ziel: bevor MC-Scorecard filtert, weiss jeder MC welches Dokument
er adressiert. Bisher landeten eHealth-/HGB-MCs im Cookie-Audit.
P80 Snapshot + Replay-Foundation — Roh-Daten persistieren damit
Audit-Pipeline ohne erneuten Crawl rebuildbar ist.
- Migration 146: compliance_check_snapshots Tabelle (JSONB pro
doc_entries/banner_result/profile/cmp_vendors/scan_context)
- services.check_snapshot.save_snapshot/load_snapshot/list
- Endpoints GET /snapshots, GET /snapshots/{id}
- Hook in _run_compliance_check: nach Mail-Send automatischer
Snapshot-Save via separater SessionLocal (background-task safe)
- Replay-Endpoint folgt im naechsten PR (braucht Refactoring
von _run_compliance_check in crawl_phase + interpret_phase)
- Effekt: Test-Cycle 7min -> 5sec bei reinen Logik-Aenderungen
(P73/P79/P81+ profitieren direkt). Snapshots dienen auch als
Regression-Test-Corpus (P81 Golden-Truth-Library).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P58 Anti-Audit-Detection robuster (script-domain + settings-spezifisch —
war bereits im Code, jetzt sauber als completed dokumentiert).
P59c DACH-Custom-Cookies in compliance.cookie_library: Borlabs,
etracker, Matomo/Piwik, Userlike, Cookiebot/Cookieyes/Usercentrics,
Akamai/Cloudflare/Datadome Bot-Manager + HubSpot. 21 neue Eintraege
(3 von 24 schon via Open-Cookie-Database vorhanden).
Script: backend-compliance/scripts/seed_dach_cookies.py.
P60b Vendor-Pattern-Dedupe mit Fuzzy-Match (Jaccard >= 0.7) statt exakter
Tuple-Equality. Vendors mit teilweise befuellten Feldern (z.B.
Sitzland eingetragen) fallen nicht mehr aus der globalen Notice —
Bug: Amazon/Psyma/Qualtrics hatten zuvor wiederholte per-row Actions.
P61 "Untergeschobene Cookies"-Erkennung — wenn ein deklarierter Vendor
(z.B. Google Tag Manager) automatisch weitere mitbringt (GA + GCL_AU
+ DoubleClick), werden diese als separater Mail-Block (gelb) mit
COOKIE/VENDOR-Badges + Quellen-Doku ausgewiesen. Neuer Service:
compliance.services.vendor_package_cookies (8 Primary-Vendors mit
je 2-4 implicit Cookies/Vendors).
P62 Marketing-Manager-Disclaimer "Was wir sehen / nicht sehen" als
blauer Box-Block direkt unter dem Critical-Findings-Block. Erklaert
Grenzen unseres Audits (Server-Side-Tracking, Vendor-interne
Datenweitergabe, Cross-Page-Banner) und Risiko des Falschvertrauens
in einen 100%-Score. Neuer Renderer: compliance.api.scope_disclaimer.
Architektur: VVT-Tabellen-Renderer aus agent_doc_check_extras.py (552
LOC -> 242 LOC) in compliance.api.vvt_table_renderer ausgelagert, um den
500-LOC-Hardcap einzuhalten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- requirements.txt: python-docx==1.2.0 (Container hatte das modul nicht)
- document-generator: Lifecycle-Filter (Pre-Founding/Founding/Startup/KMU/Konzern)
zeigt nur relevante Templates fuer aktuelle Phase
Bug: bei invertierten Checks (P9 #7 illegal_disclaimer) sagte die
GF-Aufgaben-Liste "muss ergaenzt werden" — semantisch falsch, weil der
Disclaimer ja schon da IST und entfernt werden soll.
Fix: _check_to_action() erkennt jetzt Anti-Pattern-Labels
(rechtswidrig/illegal/haftungsausschluss/disclaimer) und gibt
"muss entfernt werden (Anti-Pattern, rechtlich wirkungslos)" zurueck.
Smoke-Test BMW d2f7bcc0: vorher 'Rechtswidriger Haftungsausschluss
muss ergaenzt werden' -> jetzt 'muss entfernt werden'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend wirft 90% der consent-tester-Daten weg — nur 4 Felder von einem
vollen Banner-Scan landeten im Email. Phases (before_consent / after_reject
/ after_accept), banner_checks.violations mit Rechtsgrundlagen,
category_tests, 46 structured_checks, completeness/correctness-Scores
waren alle nicht sichtbar.
Backend: agent_compliance_check_routes leitet jetzt das volle banner_result
durch (15 Felder statt 4).
Renderer (2 neue Module):
1) agent_doc_check_critical.build_critical_findings_html
- ROTER Sofortmassnahmen-Block GANZ OBEN in der Email
- Erkennt: banner-violations (HIGH/CRITICAL), leere Per-Category-Lists,
DSE-Score <30%, fehlende Cookie-Richtlinie, US-Tracker ohne SCC/DPF
- Pro Issue: konkrete Sofortmassnahme + Rechtsgrundlage + Bussgeld-
Praezedenz (CNIL TikTok 5 Mio, LfDI BW 30k, EuGH Schrems II, ...)
- Wird nur gerendert wenn echte Issues vorliegen
2) agent_doc_check_banner.build_banner_deep_html
- Banner-Quality-Score-Cards (Vollstaendigkeit / Korrektheit / Verstoesse)
- 3-Phasen-Cookie-Tabelle: vor Consent / nach Ablehnung / nach Annahme
mit Cookie-Count, Tracker-Count, Auffaelligkeiten
- Per-Category-Tracker-Listing (Statistik/Marketing) — zeigt explizit
wenn eine Kategorie keine Provider listet (Safetykon-Pattern)
- Violations-Liste mit Severity-Badge + Quellen-Hint (LG Rostock, EDPB)
Smoke-Test Safetykon: alle 6 neuen Blocks rendern, kein Regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the 195 Clean-Room QUAIDAL controls (from breakpilot-core migration 011)
into the compliance SaaS UI.
Backend:
- GET /api/v1/quaidal/stats - counts by kind + source provenance
- GET /api/v1/quaidal/controls - list, optional kind= filter
- GET /api/v1/quaidal/controls/{id} - single derived control
- GET /api/v1/quaidal/criteria - 10 QKB criteria
- GET /api/v1/quaidal/criteria/{id} - QKB with QB/MA/QM tree
Frontend:
- /sdk/quality: new "Trainingsdaten-Qualität (BSI QUAIDAL)" tab with
10 QKB cards and a drill-down modal showing the full QB→MA→QM tree
plus original BSI source link and license note.
- /sdk/ai-act: Art. 10 tile on each high-risk/unacceptable result,
linking to /sdk/quality?category=data_quality.
Pattern matches existing IACE module DIN-reference handling:
own wording, source section + URL preserved for due diligence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A) Cookie-Policy-Architecture-Block Fallback auf DSE-Text wenn cookie via
P15 deduped wurde. Erkennt jetzt auch single-doc Sites (Safetykon-Pattern).
B) Konkrete-Aufgaben-Liste: Per-Doc-Cap (3) entfernt + globaler Cap 10→20.
Safetykon zeigt jetzt 7 statt 4 Aufgaben.
C) business_type-Klassifizierer: B2B-Service-Cluster aus P14 als Boost.
Bei 2+ Service-Indikatoren (CE-Zertifizierung/Compliance/Auditierung)
wird b2b_score angehoben. Safetykon: "B2C consulting" → "B2B (consulting)".
D) Vendor-Extract Fallback auf DSE-Text wenn cookie deduped + keine CMP-
Payloads. LLM extrahiert dann Vendors aus dem DSE-Text. Safetykon: 0 → 1
Vendor (Google Analytics aus dem DSE-Text erkannt).
Smoke-Test Safetykon: alle 4 Polish-Items wirken, kein Regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P14 — _detect_no_direct_sales erweitert um 3 Cluster:
A) OEM-Konfigurator (BMW/Audi/Mercedes/VW/Porsche-Markennamen + Vertragshaendler-Pattern)
B) B2B-Dienstleister (CE-Zertifizierung, Compliance-Beratung, Schulungen, Auditierung, TISAX, ISO-Normen, Arbeitssicherheit, ...)
C) NGO/Verein/Public (Spendenkonto, Vereinsregister, gemeinnuetzig, ...)
Schwelle: pos >= 2 pro Cluster UND pos > neg. Bisher: nur OEM.
P15 — Doc-URL-Dedup im Worker: wenn mehrere Doc-Types DASSELBE Dokument
referenzieren (Safetykon-Pattern: User gibt /datenschutz fuer dse, cookie
UND widerruf), wird nur dem primaeren Doc-Type (Priority: dse > impressum
> cookie > widerruf > agb > nutzungsbedingungen) der Text gegeben. Andere
landen als "Nicht separat vorhanden — wird im Dokument 'X' mit-geprueft."
Eliminiert die 8+8 systematischen widerruf/cookie False Positives.
P16 — Profile-Detection auch Homepage-Text: Homepage-HTML wird mit kurzem
Fetch (8s timeout) gezogen, getrippt und zum profile_input gemerged. Vor-
her wirkte P14 nur wenn B2B-Indikatoren im DSE/Impressum-Pflichttext
standen — bei Safetykon stehen sie nur im Homepage-Menue.
Plus Bonus: TDM-Override-Submit-Button wird deaktiviert wenn Reason < 10
Zeichen — verhindert dass User wie heute in den Bug rein klickt.
Smoke-Test Safetykon (B2B Compliance-Dienstleister):
dse geprueft (kein err)
impressum geprueft (kein err)
cookie "Nicht separat vorhanden — wird in DSE mit-geprueft"
agb "Nicht anwendbar — kein Direkt-Kaufvertrag"
widerruf "Nicht anwendbar — kein Direkt-Kaufvertrag"
nutzungsbedingungen "Nicht anwendbar — kein Direkt-Kaufvertrag"
Vorher: 16 False Positives. Jetzt: 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration 031 added customer_name to the SELECT statement in three places
(GetProject, ListProjects, ListVariants), and the per-row Scan needed the
matching destination. The replace_all caught ListProjects + ListVariants
but missed GetProject because of an indentation difference (single tab
vs row-scope indentation). Result: GET /projects/:id returned
"get project: number of field descriptions must equal number of
destinations, got 18 and 17"
which the frontend interpreted as "project has no data" and surfaced an
empty UI even though hazards/mitigations/components were intact (118/282/16
on Bremsscheibe).
Single-line fix: add &p.CustomerName to the GetProject scan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
last-build/main tag deleted so detect-changes falls back to
rebuild-all. Exercises the trigger-orca fix end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gitea act_runner evaluates contains(needs.*.result, 'success') to false
when most upstream build jobs are skipped, so single-service changes
never fired the orca redeploy.
Gate trigger-orca on explicit needs.build-<service>.result == 'success'
OR across all 8 build jobs. One green build now suffices to deploy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: qwen3.5:35b-a3b liefert mit format='json' + Batch-Prompt leere
Strings zurueck ('LLM batch: empty response from model'). Im echten
Compliance-Check lief der LLM-Verifier deshalb wirkungslos —
False-Positive-Findings wie 'Vorstand nicht erkannt' (BMW: Klammer-
Liste) wurden nicht overturned.
Fix: Default auf qwen3:30b-a3b umgestellt. Verifiziert mit BMW-
Impressum-Text: representative_person wird mit Evidence 'Milan
Nedeljkovic, Vorsitzender' overturned=True markiert.
OLLAMA_VERIFY_MODEL Env-Var bleibt als Override-Moeglichkeit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend: ComplianceCheckRequest um tdm_override + tdm_override_reason
erweitert. Worker im _run_compliance_check Pfad: bei
tdm_override=True UND Reason >= 10 Zeichen wird der TDM-Vorbehalt
nur dokumentiert (job.tdm_override.{reason, original_status}) und
NICHT als Abbruch-Grund gewertet. Ohne Reason: Override ignoriert.
Audit-Spur via logger.warning(reason).
Frontend: ComplianceCheckTab um Checkbox + Pflicht-Reason-Feld
("Schriftliche Crawl-Erlaubnis vorhanden") direkt vor dem Submit-
Button. Pflicht: Reason >= 10 Zeichen. Submit sendet die Flags ans
Backend.
Anwendungsfall: Safetykon-Pattern — robots.txt + ai.txt setzen
Vorbehalt, aber Kunde hat schriftlich zugestimmt (Auftrags-Audit).
[guardrail-change] ComplianceCheckTab.tsx (511 LOC) in loc-exceptions
ergaenzt — Split nach _components/TDMOverride + CompliancePolling
ist P11-Tech-Debt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diese 4 Pre-Existing-Files haben den Coolify-Build geblockt (LOC-CI-Step
failed). Splits sind Phase-5+ Tech-Debt-Backlog, bis dahin als Exceptions
getragen damit Production-Deploys nicht ausfallen.
- cra_routes.py (1714)
- vendor_redundancy.py (727)
- cookie_knowledge_db.py (608)
- cookie-banner-embed.ts (558)
Jede Exception hat einen kurzen Rationale-Kommentar daruber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Service cookie_policy_architecture.detect_architecture(...) prueft
vier Diagnose-Punkte der Cookie-Policy einer Website:
1. Layer-Trennung: single (BMW-Pattern: Banner + Info in EINER URL)
| separate (Best Practice: getrennte Layer)
2. Versionierung: "Stand vom DD.MM.JJJJ" / "Version X.Y" / ...
3. Dynamic content: CMP-Capture auf Doc-URL oder Marker-Texte
4. Vendor-Count im Text: Indikator ob Liste statisch drinsteht
Risiko-Ampel:
- gruen: separate + versioned + statisch
- gelb : single+unversioned (BMW) ODER separate+unversioned
- rot : weder noch (Pflicht-Info fehlt)
Wire-in im Compliance-Check-Worker: nach Exec-Summary-Block wird der
Architecture-Block gerendert (build_architecture_html) mit konkreter
Empfehlung. Bei BMW-Pattern: "Snapshot der dynamischen Vendor-Tabelle
als versioniertes PDF im Archiv."
Hintergrund: BMW hat eine HTML-Seite die GLEICHZEITIG Banner-Re-Trigger
und Cookie-Richtlinie ist. Mindestanforderung nach §25 TDDDG + Art. 13
DSGVO erfuellt, aber bei einer Aufsichtsbehoerden-Pruefung kann nicht
belegt werden welche Vendor-Liste an einem bestimmten Stichtag aktiv
war. Das ist kein Verstoss aber best-practice-Luecke.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#1 Name des Anbieters: \b Word-Boundary verhindert "ag" in "samstag",
plus "aktiengesellschaft" als Volltreffer.
#2 Vertretungsberechtigte: Klammer-Liste-Pattern erkennt jetzt BMW-
Format "Vorstand (Milan Nedeljkovic, Jochen Goller, ...)" plus
"Vorsitzender des Aufsichtsrats: Name".
#3 V.i.S.d.P.: war schon INFO, OK.
#4 OS-Plattform/VSBG: bei no_direct_sales=True (OEM-Pattern) jetzt als
"Nicht anwendbar" skipped statt 0/1 fail. Profile fliesst neu durch
check_document_completeness -> runner.
#5 Zustaendige Kammer: IHK + Handwerkskammer + Tieraerztekammer in
Pattern aufgenommen + severity LOW -> INFO (konditional).
#6 Stammkapital: war schon INFO, OK.
#7 Link-Disclaimer: neue Check-Eigenschaft "invert"=True. Anti-Pattern
ist passed wenn NICHT gefunden, fail wenn gefunden. Vorher feuerte
das Finding immer, jetzt nur wenn ein illegaler Disclaimer im Text
ist.
Plus: L2-INFO-Checks (z.B. profession_chamber) zaehlen nicht mehr in
correctness-pct und erzeugen keine DSI-DETAIL-Findings. Konsistent
mit P8-Modell: INFO = "selbst pruefen", nicht "fail".
Verifiziert mit BMW-Impressum-Text — alle 7 Faelle korrekt klassifiziert:
name=passed, representative_person=passed, profession_chamber=INFO,
illegal_disclaimer=passed (kein Disclaimer im Text),
dispute_resolution=skipped (no_direct_sales),
editorial_visdp=INFO, share_capital=INFO.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Email-Hardening (mc_scorecard.top_fails):
Neue _is_hard_finding-Heuristik filtert konditionale MCs ohne
Negativ-Beleg aus den Top-Auffaelligkeiten. matched_text leer + Label
enthaelt "falls/sofern/wenn/soweit/ggf." -> raus, landet nur noch im
MC-Audit als "selbst pruefen". DATA-2066-A05 (kostenfreie Abschaltung
Standortdaten) ist das prototypische Beispiel.
MC-Audit-Frontend (audit/[checkId]/page.tsx):
Severity-Spalte (CRITICAL/HIGH/MEDIUM/LOW) entfernt — der MC-Audit
ist eine Checkliste, keine Severity-Drohung. Stattdessen:
- Spalte "Prioritaet" mit 3-Tier aus regulation-Mapping:
Gesetz (DSGVO/ePrivacy/TDDDG/...) / Behoerden-Leitlinie
(EDPB/DSK/EuGH/...) / Best-Practice (ISO/NIST/BSI)
- 3-Status: erfuellt (✓) / nicht erfuellt (✗) / selbst pruefen (?)
/ nicht anwendbar (—). rowReviewStatus() leitet "selbst pruefen"
aus matched_text-leer + konditionalem Label ab.
- Filter umgebaut auf 5 Stati statt 4
- Default-Filter "Nicht erfuellt" (vorher "Nur Fail")
Bonus: f.payload.risk_label TS-Cast im FindingsTab clean gemacht
(unknown -> string).
Effekt:
- Email an die GF zeigt nur noch echte Belege ("DSB fehlt",
"Gebuehr fuer Widerruf")
- MC-Audit ist eine sachliche Pruefliste fuer den Compliance-Officer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[migration-approved]
Task #22. The IACE module is used by a single Maschinenhersteller, but
their plants land at many different end customers. When the safety expert
commissions the second or third plant at the same customer, whole classes
of mitigations (company-wide PPE rules, locked-out energy isolation,
customer-standard signage) are already in place there — but rediscovered
from scratch every project.
Migration 031: iace_projects.customer_name TEXT + partial index.
The customer is stored as a plain text field rather than a normalised
iace_customers table (option A from the design discussion). A proper
customer-management screen can promote this to a FK later without
data loss.
Backend store_customer_standards.go:
- ListCustomerStandardSuggestions(projectID, includeVerified) collects
mitigations from all non-archived prior projects sharing the same
tenant_id AND case-insensitive customer_name. Aggregates by
mitigation.name (since same-named measures from different prior
projects collapse into one suggestion) and surfaces:
• source_project_count + source_project_names
• is_customer_standard / has_verified_instances flags
includeVerified=false → strictly is_customer_standard=true
includeVerified=true → also status='verified'
- ImportCustomerStandardSuggestion(projectID, name): for every prior
(mitigation.name → hazard.name) pairing, finds matching hazards in
the current project (by name) and ensures a customer-standard
mitigation exists. New rows via CreateMitigation (idempotent through
the UNIQUE(hazard_id, name) from migration 030); existing rows are
flipped to is_relevant=true + is_customer_standard=true +
status='verified' via UPDATE.
Routes:
GET /api/v1/iace/projects/:id/customer-standards?include_verified=
POST /api/v1/iace/projects/:id/customer-standards/import body {name}
Frontend:
- New page /sdk/iace/[projectId]/customer-standards with:
• empty-state hint pointing to Auftrag → Kundenname
• per-suggestion checkbox + per-row Übernehmen button
• bulk "N übernehmen" button
• toggle "Auch verifizierte einbeziehen" widening the pool
• per-suggestion source_project_count + status badges
- Sidebar item "Kundenstandards" (building icon) placed between
Verifikation and Nachweise.
- Order-page now mirrors Auftraggeber.Firmenname into the top-level
customer_name column on save, so the Reuse feature is fed
automatically without a separate input field.
The same expert effect from migration 029's is_customer_standard flag —
"I already know it's covered, no evidence needed" — now becomes a
cross-project asset rather than a per-project annotation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:31:30 +02:00
523 changed files with 74554 additions and 2393 deletions
Bei Fragen nach Bussgeldern, Risiko-Hoehe oder konkreten Faellen gib **konkrete Praezedenzen** an:
### Top-Bussgelder (CNIL Frankreich — strengste EU-Aufsicht):
- **Google France 2020 (CNIL)** — 100 Mio EUR — Cookies ohne Einwilligung (CNIL Beschluss vom 07.12.2020)
- **Meta/Facebook France 2022 (CNIL)** — 60 Mio EUR — Cookies ohne Einwilligung
- **Amazon France 2020 (CNIL)** — 35 Mio EUR — Cookies ohne Einwilligung
- **Carrefour France 2020 (CNIL)** — 2,25 Mio EUR — Cookies + sonstige Verstoesse
### Deutsche Praezedenzen + Sammelklagen-Risiken:
- **LG Muenchen I 2022** — 100 EUR pro Besucher Schadensersatz fuer Google Fonts ohne Consent (Az. 3 O 17493/20). Spaeter durch BGH "Rechtsmissbrauchs"-Argument bei Massenabmahnungen eingeschraenkt.
- **EuGH Planet49 (C-673/17)** — vorausgewaehlte Cookie-Checkboxen sind unwirksame Einwilligung (praejudiziell fuer alle EU-Sites)
- **BGH Cookie-Einwilligung II (I ZR 7/16)** — bestaetigt Planet49 fuer Deutschland
- **DSK Beschluss 2023** — Cookie-Banner mit "Akzeptieren" deutlich prominenter als "Ablehnen" = Dark Pattern = unwirksame Einwilligung
### Deutscher Aufsichtsmarkt:
Deutsche Aufsicht (BfDI + 16 Landes-DSB) ist moderater als CNIL — bislang keine 100 Mio-EUR-Bussgelder. ABER: DSK-Beschluesse + LfDI-Verfahren haeufen sich. Federfuehrung bei Konzernen via "One-Stop-Shop" nach Hauptsitz.
### Vier Risiko-Pfade fuer Mandanten:
1.**Art. 83 DSGVO Bussgeld** — bis 4% des weltweiten Konzernumsatzes. Realistisch 0,1-1% bei Erstverstoss.
GF haftet **persoenlich** nach §43 GmbHG bzw. §93 AktG wenn Compliance-Pflichten verletzt wurden. Das ist der eigentliche Druckpunkt — nicht die Firma, sondern der GF persoenlich. Bei Mandantengespraechen mit GF-Beteiligung: dieser Punkt zuerst ansprechen.
### Wie berechne ich das konkrete Risiko fuer einen Mandanten:
Frage den Mandanten nach: (a) Jahresumsatz, (b) ungefaehre Besucherzahl pro Jahr, (c) Anzahl Trackingtools im Banner. Dann:
- Max-Bussgeld = 4% × Jahresumsatz (Obergrenze, nicht realistisch)
?'Kopiere hier die komplette Cookie-Tabelle rein (Tab-getrennt oder mit | als Trenner — wir parsen alle Spalten deterministisch)…'
:'Kopiere hier den vollständigen Doc-Text rein. Wir erkennen automatisch ob es zu „'+(DOC_TYPES.find(t=>t.id===entry.type)?.label??entry.type)+'" passt.'
description="Zentrale Übersicht aller erzeugten Dokumente — gruppiert nach Empfehlung (Pflicht/Empfohlen/Optional), gefiltert nach Status. Klick auf eine Zeile öffnet den Workflow-Editor."
placeholder="z.B. die Entwicklung, Bereitstellung, der Betrieb und der Vertrieb von Softwarelösungen, Plattformen und IT-Dienstleistungen im Bereich der Künstlichen Intelligenz"
?'Noch keine Overrides angelegt. Klicke oben rechts „+ Override hinzufügen“, um die globale Klassifikation einer Regel für deine Kanzlei abweichend zu setzen.'
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.