# QA: Control Quality Pipeline ## Übersicht Die Control Quality Pipeline prüft und verbessert die Canonical Controls der Compliance-Bibliothek. Sie nutzt **PDF-basierte Verifizierung** als Ground Truth — jeder Control-Originaltext wird direkt im Quelldokument (PDF) lokalisiert. Alle Scripts liegen in **`scripts/qa/`**. Starten auf dem Mac Mini via Runner-Script: ```bash # Job starten (laedt .env automatisch, PID-Lock, unbuffered output) ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh [args...]" # Status aller Jobs ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --status" # Log ansehen ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --log " # Job stoppen ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --kill " ``` ## Architektur ``` Original-PDFs (~/rag-ingestion/pdfs/) ↓ PDF Text-Extraktion (PyMuPDF) ↓ Artikel-Index aufbauen (Erwägungsgründe → Artikel → Anhänge) ↓ source_original_text im PDF suchen (Substring-Match, normalisiert) ↓ Artikel/§/Section zuordnen + article_type setzen ↓ Duplikat-Erkennung (Preamble vs. Artikel: Artikel hat Vorrang) ``` ## PDF-basierte Artikelzuordnung ### Konzept Jeder Control hat ein Feld `source_original_text` — der Chunk-Text aus dem Quelldokument. Statt über Qdrant-Hashes wird dieser Text **direkt im Original-PDF gesucht**. Die Position im PDF bestimmt den Artikel. ### Dokumenttypen | Typ | Struktur | Beispiel | |-----|----------|---------| | EU-Verordnung | Erwägungsgründe → Artikel → Anhänge | DSGVO, KI-VO, CRA | | Deutsches Gesetz | §-Paragraphen | BDSG, GewO, HGB | | NIST | Control Families (AC-1, SC-7) | SP 800-53, CSF 2.0 | | OWASP | Kategorien (A01:2021, V1.1) | Top 10, ASVS, MASVS | | EDPB/ENISA | Nummerierte Abschnitte | Leitlinien, Guidelines | ### Article Types | article_type | Bedeutung | Beispiel | |---|---|---| | `article` | Gesetzesartikel / Paragraph | Artikel 25 DSGVO | | `preamble` | Erwägungsgrund | Erwägungsgrund (78) | | `annex` | Anhang | Anhang III | | `control` | NIST Control Family | AC-6, SA-7 | | `section` | Nummerierter Abschnitt | Section 2.1 | | `category` | OWASP Kategorie | A01:2021 | | `requirement` | OWASP Requirement | V1.1, MASVS-STORAGE-1 | ### Ergebnisse (Stand 2026-03-20) | Metrik | Wert | |---|---| | Controls mit source_original_text | 5.751 (86%) | | Im PDF lokalisiert | **5.063 (88%)** | | Nicht gefunden | 649 | | Kein PDF vorhanden | 29 | | Recital_suspect markiert | 648 | | 100% Match-Rate | 20+ Regulations (inkl. DSGVO, KI-VO, NIS2, NIST 800-53, Blue Guide) | **Verlauf:** v1 (4.110, 52%) → v2 (6.091, 77%) → v3 (6.259, 79%) → v4 +Blue Guide EN (6.803, 86%) → v5 nach Cleanup (5.063/5.741, 88%) ### Nicht-matchende Controls | Ursache | Controls | Status | |---|---|---| | ~~Blue Guide EN vs. DE PDF~~ | ~~562~~ | ✅ Gelöst — EN-PDF beschafft, 544/544 gematcht | | ~~OWASP Top 10 multilingual~~ | ~~324~~ | ✅ Als duplicate markiert — Übersetzungen ohne Mehrwert | | CRA Encoding | ~76 | PDF-Ligaturen/Sonderzeichen-Differenzen | | CISA Secure by Design | ~113 | Falsches PDF (ENISA statt CISA) | | OWASP ASVS | ~173 | PDF-Matching-Problem (meist EN) | ## Brute-Force-Suche Für Controls mit unbekannter Quelle: Text gegen **alle ~100 PDFs** suchen. Findet: - Korrekte Quelldokument-Zuordnung - Falsche Source-Zuordnungen (44 entdeckt) ## Erwägungsgrund-Controls (Wettbewerbsvorteil) Controls aus Erwägungsgründen (`article_type = preamble`) sind **kein Nachteil**. Sie decken Aspekte ab, die reine Artikel-basierte Compliance-Tools übersehen. **Duplikat-Regel:** Wenn ein Preamble-Control das gleiche Thema wie ein Artikel-Control behandelt (Jaccard-Ähnlichkeit ≥ 0.40), hat der **Artikel Vorrang**. Das Preamble-Control wird als `duplicate` markiert. ### Ergebnis der Preamble-Dedup | Metrik | Wert | |---|---| | Preamble-Controls geprüft | 838 | | Als Duplikat markiert | 190 | | **Unique Preamble-Controls** | **648** | ## Pipeline-Versionen | Version | Controls | Mit Originaltext | Beschreibung | |---|---|---|---| | v1 | 5.332 | 4.137 (78%) | Automatisch + manuell erstellt | | v2 | 2.258 | 2.258 (100%) | Automatisch aus Chunks | | v3 | 1.570 | 1.548 (99%) | Neueste Pipeline | Die 1.195 v1-Controls **ohne** Originaltext sind manuell erstellt (`strategy=ungrouped`) und haben keine Chunk-Referenz. ## OWASP Cleanup (2026-03-20) - **324 OWASP Top 10 multilingual Controls** → `duplicate` markiert (ZH, AR, ID, FR, ES, PT — Übersetzungen derselben 10 Kategorien) - **47 Controls** mit falscher Quellenzuordnung korrigiert (z.B. als "OWASP Top 10" getaggt, aber tatsächlich aus ASVS/SAMM/API/MASVS) - **~200 OWASP ASVS/SAMM/MASVS EN Controls** behalten — unique Content aus GitHub/Website, nicht im PDF auffindbar ## NIST OSCAL Import (2026-03-20) **776 neue Controls** aus NIST SP 800-53 Rev 5 OSCAL (Public Domain, maschinenlesbar): - Quelle: `usnistgov/oscal-content` (JSON Catalog) - Vor allem **Control Enhancements** (z.B. AC-2(3), SC-7(8)) — die atomaren Unteranforderungen - Jeder Control enthält: Statement + Guidance + Assessment-Methoden + Cross-References + Parameters - `pipeline_version = 4`, `generation_strategy = 'oscal_import'` - Kein Pass 0a/0b nötig — Controls sind **bereits atomar** | Metrik | Vorher | Nachher | |---|---|---| | SP 800-53 Controls (aktiv) | 1.107 | **1.883** | | OSCAL-Abdeckung | 238/1.014 (23%) | **1.014/1.014 (100%)** | ## Phase 5: RAG-Deduplizierung + Normalisierung (2026-03-20) ### Durchgeführte Schritte | Schritt | Beschreibung | Controls | |---|---|---| | 5.1 | OSCAL Controls: `source_regulation` in generation_metadata gesetzt | 776 | | 5.2 | v3 Controls ohne Source → `needs_review` mit `missing_source` Flag | 20 | | 5.3 | Leerer Source-Name korrigiert (AT TKG) | 1 | | 5.4 | OWASP regulation_code Fehlzuordnungen korrigiert | 47 | | 5.5 | **duplicate/too_close Controls hart gelöscht** | **3.301** | | 5.6 | Processed Chunks bereinigt (gelöschte Control-IDs entfernt) | 2.520 | ### Ergebnis - **Vorher:** 9.936 Controls (6.635 aktiv, 2.998 duplicate, 303 too_close) - **Nachher:** 6.635 Controls, **alle aktiv** (0 duplicate/too_close) - Alle regulation_codes haben jetzt einheitliche Source-Namen - OWASP-Controls sind korrekt ihren Quellen zugeordnet ## DB-Status (Stand 2026-03-20, nach Phase 7.4) | release_state | Count | |---|---| | draft | ~6.030 | | needs_review | 838 | | **Gesamt** | **6.868** | ## Scripts (`scripts/qa/`) ### Kern-QA (PDF-Matching) | Script | Beschreibung | |---|---| | `pdf_qa_all.py` | **Haupt-QA**: Controls gegen PDFs matchen, Artikel-Index aufbauen. Enthaelt `SOURCE_FILE_MAP`, alle Index-Builder (EU, DE, NIST, OWASP, generic). 526 Zeilen. | | `pdf_qa_inventory.py` | Inventar: Welche Regulations haben Controls, wie viele, welche PDFs existieren | | `apply_pdf_qa_results.py` | Ergebnisse aus `pdf_qa_all.py` in DB schreiben (`article_type`, `recital_suspect`) | | `pdf_article_lookup_poc.py` | POC: Control-Text in PDF lokalisieren, Headings von Cross-Refs unterscheiden | ### Lueckenanalyse + Control-Generierung | Script | Beschreibung | |---|---| | `gap_analysis.py` | **Phase 7.3**: Artikel im PDF vs. Controls in DB vergleichen, Luecken identifizieren | | `phase74_generate_gap_controls.py` | **Phase 7.4**: Neue Controls fuer Luecken via Anthropic API generieren. `pipeline_version=5`. 624 Zeilen. | | `benchmark_llm_controls.py` | LLM-Vergleich: gpt-oss-120b vs. Claude Sonnet fuer Control-Generierung | | `test_pass0a.py` | **Pass 0a Test**: Obligation Extraction + 3-Tier-Klassifizierung (Pflicht/Empfehlung/Kann). Standalone, speichert JSON. | ### Deduplizierung + Normalisierung | Script | Beschreibung | |---|---| | `preamble_dedup.py` | Preamble vs. Artikel Duplikat-Erkennung (Jaccard >= 0.40) | | `qa_dedup_controls.py` | Jaccard-basierte Titel-Deduplizierung | | `qa_apply_and_dedup.py` | Ergebnisse anwenden + Duplikate in einem Schritt markieren | | `qa_normalize_sources.py` | Source-Namen normalisieren (kanonische Namen) | | `phase5_normalize_and_cleanup.py` | **Phase 5**: Normalisierung + 3.301 Duplikate hart loeschen | | `qa_delete_gpsr_dupe.py` | GPSR-Duplikate loeschen | | `delete_gpsr_prod.py` | GPSR-Duplikate aus Production-Qdrant entfernen | ### Quellen-spezifische Scripts | Script | Beschreibung | |---|---| | `blue_guide_en_match.py` | Blue Guide EN-PDF matchen (544/544 Erfolg) | | `owasp_cleanup.py` | OWASP multilingual Cleanup (324 Duplikate) + Source-Fix (47 korrigiert) | | `owasp_github_match.py` | OWASP ASVS/SAMM/MASVS gegen GitHub-Markdown matchen | | `oscal_import.py` | NIST OSCAL Import (776 Controls aus JSON Catalog) | | `oscal_analysis.py` | NIST OSCAL Analyse: Abdeckung, fehlende Controls | ### Diagnose + Utilities | Script | Beschreibung | |---|---| | `db_status.py` | DB-Status: release_state Counts, pipeline_version, source Verteilung | | `debug_low_match.py` | Debugging: Warum matchen Blue Guide / OWASP / CISA schlecht? | | `qa_article_map_all_chunks.py` | Alle Chunks Artikel-Nummern zuordnen (Bulk) | | `backfill_job_66228863.py` | Einmaliger Backfill-Job | | `sync_controls_to_prod.py` | Controls von Dev nach Production synchronisieren | ### Runner | Script | Beschreibung | |---|---| | `run_job.sh` | **Job-Runner**: Laedt `.env`, PID-Lock, Monitoring (`--status`, `--log`, `--kill`) | ## Phase 7: PDF-Validierung + Enrichment (2026-03-20) ### 7.1 + 7.2: Controls gegen PDFs validiert + Ergebnisse angewendet ✅ - 5.063 Controls erfolgreich im Original-PDF lokalisiert (88%) - `article_type` fuer alle gematchten Controls gesetzt - 648 Preamble-Controls als `recital_suspect` in `generation_metadata` markiert - 332 Controls nicht matchbar (OWASP ASVS 132, CISA 72, ENISA 38, OWASP SAMM 31, CRA 28) ### 7.3: Lueckenanalyse ✅ **494 Artikel-Luecken** in 15 Quellen identifiziert. Geschaetzt ~300 davon actionable. | Source | Luecken | Coverage | Bemerkung | |---|---:|---:|---| | AML-Verordnung | 91 | 5% | Kaum ingestiert | | MiCA | 71 | 52% | Grosse Verordnung | | NIST SP 800-53 | 59 | 83% | Meist Section-Header, nur SA-15 fehlt | | OWASP ASVS 4.0 | 47 | 35% | Requirement-Gruppen fehlen | | Batterieverordnung | 41 | 58% | | | DSGVO | 35 | 65% | Einige Governance/Aufsicht-Artikel | | ENISA ICS/SCADA | 34 | 31% | | | ENISA Supply Chain | 26 | 7% | | | CRA | 23 | 68% | | | NIS2 | 16 | 65% | | | KI-Verordnung | 15 | 87% | Fast komplett | | Maschinenverordnung | 5 | 91% | Fast komplett | ### 7.4: Neue Controls fuer Luecken generieren ✅ (2026-03-20) Script: `phase74_generate_gap_controls.py --resume` - **494 Artikel-Luecken** in 15 Quellen → Anthropic Claude Sonnet 4.6 - `pipeline_version = 5`, `generation_strategy = 'phase74_gap_fill'` - Direkt PDF-Text als Input (nicht RAG-Chunks) - Starten via: `run_job.sh phase74_generate_gap_controls.py --resume` **Ergebnis:** | Source | Luecken | Generiert | |---|---:|---:| | AML-Verordnung | 91 | 97 | | MiCA | 71 | 68 | | NIST SP 800-53 | 59 | 19 | | KI-Verordnung | 15 | 15 | | OWASP ASVS 4.0 | 47 | 11 | | Batterieverordnung | 41 | 9 | | DSGVO | 35 | 4 | | OWASP Top 10 | 12 | 3 | | NIS2 | 16 | 3 | | CRA | 23 | 3 | | OECD KI-Empfehlung | 4 | 1 | | **Gesamt** | **494** | **233** | Nicht generiert: 75 zu kurzer Text, 29 NIST-Intros, 11 Parse-Errors, 162 ID-Konflikte (COMP-1000 etc.). API-Kosten: ~$7,55 (109 min Laufzeit). ## Pass 0a: Obligation Extraction — 3-Tier-Klassifizierung ### Konzept Pass 0a zerlegt Rich Controls (~6.000) in **atomare Obligations** per LLM (Claude Sonnet 4.6). Jede Obligation wird durch den **Quality Gate** klassifiziert — nicht gefiltert: | obligation_type | Signal | Beispiel | |---|---|---| | **pflicht** | müssen, muss, ist zu, hat zu, shall, must, required | "Der Betreiber muss alle Daten verschluesseln" | | **empfehlung** | soll, sollen, should, sicherstellen, gewaehrleisten, dokumentieren | "Der Betreiber soll regelmaessige Audits durchfuehren" | | **kann** | kann, koennen, darf, duerfen, may, optional | "Der Betreiber kann zusaetzliche Massnahmen ergreifen" | **Wichtig:** Nichts wird mehr rejected wegen fehlendem normativem Signal. Obligations ohne Signal werden als `empfehlung` klassifiziert. Rejected werden nur noch: Evidence-Only, zu kurz (<20 Zeichen), fehlender Parent-Link. ### Warum auch Empfehlungen behalten? Empfehlungen helfen Firmen, ihre Systeme sicherer zu machen — ueber das Pflichtprogramm hinaus. Im Frontend erhalten Kunden einen Marker, der klar anzeigt: - **Pflicht** = gesetzlich/regulatorisch vorgeschrieben - **Empfehlung** = Best Practice, freiwillig, aber wertvoll - **Kann** = optional, weitergehende Massnahme ### Quality Gate — Kritische Flags | Flag | Kritisch? | Beschreibung | |---|---|---| | `has_normative_signal` | Nein | Informativer Check, kein Ablehnungsgrund | | `obligation_type` | — | Klassifizierung (pflicht/empfehlung/kann) | | `not_evidence_only` | **Ja** | Kein reiner Nachweis-Eintrag | | `min_length` | **Ja** | Mindestens 20 Zeichen | | `has_parent_link` | **Ja** | Verbindung zum Parent-Control | | `single_action` | Nein | Nur ein Hauptverb (heuristisch) | | `not_rationale` | Nein | Keine reine Begruendung | ### Normative Signal Detection — Regex-Tiers ``` Tier 1 (Pflicht): muessen, muss, ist/sind/hat/haben zu + Infinitiv, Compound-Verben (festzustellen, vorzunehmen), Gerundivum (mitzuteilen, bereitzustellen), shall, must, required Tier 2 (Empfehlung): soll, sollen, sollte, sollten, gewaehrleisten, sicherstellen, should, ensure, recommend, dokumentieren, implementieren, ueberpruefen Tier 3 (Kann): kann, koennen, darf, duerfen, may, optional ``` ### Testergebnisse (3 Iterationen, 2026-03-20) | Run | Controls | Obligations | Validated | Rejected | Kosten | |---|---:|---:|---:|---:|---:| | 1 (v0 Regex) | 10 | ~100 | 68% | 32% | $0,28 | | 2 (v1 Regex) | 50 | ~530 | 78% | 22% | $1,43 | | 3 (v2 Regex) | 50 | ~530 | 86% | 14% | $1,44 | | 4 (3-Tier) | 60 | — | — | — | — | Run 4 laeuft mit dem neuen Klassifizierer — statt PASS/REJECT wird jetzt PFLICHT/EMPFEHLUNG/KANN ausgegeben. ### Scripts | Script | Beschreibung | |---|---| | `test_pass0a.py` | **Test-Script**: Standalone (kein SQLAlchemy), psycopg2 + Anthropic API. Speichert Ergebnisse als JSON. | ```bash # Test mit 10 Controls run_job.sh test_pass0a.py --limit 10 # Test mit bestimmter Quelle run_job.sh test_pass0a.py --limit 20 --source "DSGVO" # Ergebnisse: /tmp/pass0a_results_controls.json ``` ### Backend-Code - **Klassifizierung:** `backend-compliance/compliance/services/decomposition_pass.py` - `classify_obligation_type()` — 3-Tier-Klassifizierung - `quality_gate()` — gibt `obligation_type` in Flags zurueck - `passes_quality_gate()` — `has_normative_signal` nicht mehr kritisch - `ObligationCandidate.obligation_type` — neues Feld ### Hochrechnung (basierend auf 50-Control-Runs) | Metrik | Wert | |---|---| | Kosten pro Control | ~$0,029 | | Kosten fuer ~6.000 Controls | **~$172** | | Laufzeit (geschaetzt) | ~25h | | Obligations pro Control | ~10,5 | --- ## Naechste Schritte 1. ~~**Phase 5 Cleanup** → 3.301 Duplikate geloescht, Source normalisiert~~ ✅ 2. ~~**Phase 6 Pipeline-Haertung** → Source aus REGULATION_LICENSE_MAP~~ ✅ 3. ~~**Phase 7.1-7.3** → PDF-Validierung + Enrichment + Lueckenanalyse~~ ✅ 4. ~~**Phase 7.4** → 233 neue Controls fuer Luecken generiert ($7,55)~~ ✅ 5. **Pass 0a** → Obligation Extraction mit 3-Tier-Klassifizierung (Tests laufen, ~$172) 6. **Pass 0b** → Atomic Control Composition aus validierten Obligations 7. **Pass 1-5** → Multi-Layer Migration (Code + 500 Tests bereits vorhanden) 8. **Phase 8** → Qdrant Re-Ingestion (Runtime-Betrieb, ZULETZT) 9. **needs_review Triage** — 838 Controls klassifizieren 10. **Frontend** — `obligation_type` (Pflicht/Empfehlung/Kann) + `article_type` anzeigen