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
- 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>
396 lines
16 KiB
Markdown
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
|