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

16 KiB

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:

# 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 Controlsduplicate 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.
# 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. Frontendobligation_type (Pflicht/Empfehlung/Kann) + article_type anzeigen