Files
breakpilot-compliance/docs-src/development/qa-control-quality.md
Benjamin Admin 643b26618f
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 31s
CI/CD / test-python-backend-compliance (push) Successful in 1m35s
CI/CD / test-python-document-crawler (push) Successful in 20s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Has been skipped
feat: Control Library UI, dedup migration, QA tooling, docs
- Control Library: parent control display, ObligationTypeBadge,
  GenerationStrategyBadge variants, evidence string fallback
- API: expose parent_control_uuid/id/title in canonical controls
- Fix: DSFA SQLAlchemy 2.0 Row._mapping compatibility
- Migration 074: control_parent_links + control_dedup_reviews tables
- QA scripts: benchmark, gap analysis, OSCAL import, OWASP cleanup,
  phase5 normalize, phase74 gap fill, sync_db, run_job
- Docs: dedup engine, RAG benchmark, lessons learned, pipeline docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 11:56:08 +01:00

396 lines
16 KiB
Markdown

# 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 <script.py> [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 <script.py>"
# Job stoppen
ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --kill <script.py>"
```
## 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_<N>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