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:
Architektur
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).
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
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. |
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
Phase 5 Cleanup → 3.301 Duplikate geloescht, Source normalisiert ✅
Phase 6 Pipeline-Haertung → Source aus REGULATION_LICENSE_MAP ✅
Phase 7.1-7.3 → PDF-Validierung + Enrichment + Lueckenanalyse ✅
Phase 7.4 → 233 neue Controls fuer Luecken generiert ($7,55) ✅
- Pass 0a → Obligation Extraction mit 3-Tier-Klassifizierung (Tests laufen, ~$172)
- Pass 0b → Atomic Control Composition aus validierten Obligations
- Pass 1-5 → Multi-Layer Migration (Code + 500 Tests bereits vorhanden)
- Phase 8 → Qdrant Re-Ingestion (Runtime-Betrieb, ZULETZT)
- needs_review Triage — 838 Controls klassifizieren
- Frontend —
obligation_type (Pflicht/Empfehlung/Kann) + article_type anzeigen