docs(architecture): RAG retrieval engine architecture set (01-09)
CI / detect-changes (push) Successful in 14s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 9s
CI / validate-canonical-controls (push) Successful in 19s
CI / loc-budget (push) Successful in 23s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 14s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 9s
CI / validate-canonical-controls (push) Successful in 19s
CI / loc-budget (push) Successful in 23s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
9 docs + index in docs-src/architecture/ documenting the deterministic retrieval engine: retrieval pipeline, authority rerank, source_class, source_role, control-intent + diversity, assessment, confidence, explainability + supersede, framework_* layer. Each doc carries the exact constants, the rationale behind them, code refs, and the failure class it addresses. Audit/onboarding reference. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# 01 — Retrieval-Pipeline
|
||||
|
||||
**Zweck:** Einen Kandidaten-Pool bauen, der die *richtigen* Quellen enthält (Pflichtquelle **und** Controls) — auch dann, wenn reine Semantik sie verfehlen würde. Re-Ranking (02) kann nur ordnen, was im Pool liegt; deshalb ist der Pool-Aufbau die erste Verteidigungslinie gegen Recall-Lücken.
|
||||
|
||||
## Mechanik
|
||||
|
||||
`searchInternal()` (`legal_rag_client.go`) orchestriert den Pool in fester Reihenfolge — jede Stufe **augmentiert** (ersetzt nie), Fehler degradieren still:
|
||||
|
||||
1. **Embedding** — `bge-m3` (1024-dim) über Ollama, Query auf 2000 Zeichen gekappt.
|
||||
2. **Hybrid (RRF)** — `searchHybrid()`: dense + Volltext via Qdrant Query-API, RRF-Fusion. Fällt bei Fehler auf `searchDense()` (reine Vektorsuche) zurück.
|
||||
3. **Binding-Augmentation** — `searchBinding()`: zieht die Top-`source_class=binding_law`-Treffer dazu, **damit die Pflichtquelle immer Kandidat ist**, auch wenn Guidance semantisch dominiert.
|
||||
4. **Control-Augmentation** — `searchControls()`: nur bei Control-Intent (siehe [05](05-control-intent.md)); tiefer dense-Pull, gefiltert auf Control-Pool-Rollen.
|
||||
5. **Graph-Augmentation** — `expandViaGraph()`: **opt-in**; zieht verbundene Normen über Zitations-Kanten.
|
||||
6. **Merge** — `mergeDedupHits()`: konkateniert, behält die erste Vorkommnis je Punkt-ID, Reihenfolge erhalten.
|
||||
|
||||
Danach: Map auf `LegalSearchResult` → Authority-Rerank (02) → Control-Diversity (05) → Truncate auf `topK`.
|
||||
|
||||
## Konstanten + Warum
|
||||
|
||||
| Konstante | Wert | Warum |
|
||||
|-----------|------|-------|
|
||||
| `prefetchLimit` (hybrid) | `20`, bzw. `topK*4` bei topK>20 | Fusion-Fenster: genug dense-Kontext für RRF, ohne den Volltext-Anteil zu verwässern |
|
||||
| `controlPoolDepth` | `60` | **Gemessen:** für EU-Cyber-Control-Queries liegen die relevanten Control-Quellen (NIST, CRA-Anhang) bei dense-Rang ~8–9 — weit unter dem kleinen top-K. Auf dem größeren (95k) synced Korpus reicht ein fixer Tiefen-Pull von 60, um sie zum Kandidaten zu machen |
|
||||
| `graphSeedCount` | `5` | nur die Top-Hits als Graph-Saat (Begrenzung der Expansion) |
|
||||
| `graphMaxExpand` | `15` | Obergrenze der über Kanten gezogenen Normen |
|
||||
| `graphHopPenalty` | `0.05` | leichte Distanz-Strafe pro Kante (Pool-Expansion, kein Ranking-Hebel) |
|
||||
| `RAG_GRAPH_EXPANSION` | env, default **aus** | **Opt-in:** kein gemessener Rang-Nutzen ggü. der Binding-Augmentation, +1 Qdrant-Call/Suche, Flutungsrisiko über Reverse-Kanten. Bleibt als Recall-Sicherheitsnetz |
|
||||
|
||||
> Forward-Kanten (`references_out`) treiben die Graph-Expansion; Reverse-Kanten (`references_in`) werden **nur als Metadaten** geführt (sonst flutet ein populärer Anhang den Pool).
|
||||
|
||||
## Code
|
||||
|
||||
- `legal_rag_client.go` → `searchInternal()`, `mergeDedupHits()`
|
||||
- `legal_rag_http.go` → `searchHybrid()`, `searchDense()`, `searchBinding()`, `searchControls()`
|
||||
- `legal_rag_graph.go` → `expandViaGraph()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„Pflichtquelle nicht im Pool"** → Binding-Augmentation (Stufe 3) garantiert die `binding_law`-Quelle als Kandidat.
|
||||
- **„Control-Quelle unter top-K"** → Control-Augmentation + `controlPoolDepth` (Stufe 4) holt tiefliegende NIST/CRA-Anhang-Treffer.
|
||||
- **„Recall-Lücke bei Synonymen"** → Hybrid (RRF) deckt lexikalische Treffer ab, die rein semantisch fehlen.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 02 — Authority-Re-Ranking
|
||||
|
||||
**Zweck:** Bindendes Recht der passenden Jurisdiktion/Domäne nach oben, Guidance/Fremdrecht/Off-Domain nach unten — **Reihenfolge only, nichts wird gelöscht**. Der `Score` trägt nach dem Rerank den Authority-Score, damit nachgelagerte Multi-Collection-Merges (Advisor) die Ordnung bewahren.
|
||||
|
||||
## Mechanik
|
||||
|
||||
`authorityScore()` (`authority_rerank.go`) berechnet pro Treffer einen normativen Relevanz-Score aus dem rohen Semantik-Score + gewichteter Autorität + Kontext-Bonus/Penalty:
|
||||
|
||||
```
|
||||
score = rawSemantic
|
||||
+ authorityCoef · weight/100 (Autorität, siehe 03)
|
||||
+ jurisdictionGain (DE/EU-Match)
|
||||
− foreignPenalty (CH bei DE/EU-Frage)
|
||||
− unknownPenalty (unbekannte Klasse)
|
||||
+ domainMatchGain (Chunk-Domäne == Query-Domäne)
|
||||
− offDomainPenalty (bindend, aber off-domain)
|
||||
− scopePenalty (BDSG Teil 3 bei allgemeiner DS-Frage)
|
||||
+ topicGain (bevorzugte kanonische Norm)
|
||||
− supersededPenalty (status="superseded")
|
||||
```
|
||||
|
||||
`rerankByAuthority()` sortiert stabil nach diesem Score und schreibt ihn zurück. `liftAboveBinding()` hebt bei **Auslegungs-Intent** eine semantisch konkurrenzfähige Guidance knapp über das bindende Recht — mit Margin-Guard, damit off-topic-Guidance das Gesetz nicht überholt.
|
||||
|
||||
## Konstanten + Warum
|
||||
|
||||
| Konstante | Wert | Warum |
|
||||
|-----------|------|-------|
|
||||
| `authorityCoef` | `0.40` | Gewicht→Score-Multiplikator. Konservativ kalibriert gegen die Offline-Golden-Harness (Phase A): hoch genug, dass bindendes Recht gewinnt, niedrig genug, dass starke Semantik nicht erschlagen wird |
|
||||
| `jurisdictionGain` | `0.05` | leichter Vorzug für DE/EU-Quellen bei DE/EU-Frage |
|
||||
| `foreignPenalty` | `0.60` | Fremdrecht (CH) bei DE/EU-Frage klar demoten — aber **nicht** entfernen (Vergleichsfälle bleiben auffindbar) |
|
||||
| `unknownPenalty` | `0.08` | unklassifizierte Quellen leicht zurückstufen |
|
||||
| `domainMatchGain` | `0.15` | Domänen-Treffer (data_protection / cyber / ai / product_safety) belohnen |
|
||||
| `offDomainPenalty` | `0.10` | bindende, aber fachfremde Norm demoten (z.B. DSGVO bei reiner Cyber-Frage) |
|
||||
| `scopePenalty` | `0.25` | BDSG §45–84 (Justiz/Strafverfolgung) bei allgemeiner DS-Frage zurückstufen — häufige Scope-Verwechslung |
|
||||
| `topicGain` | `0.18` | Verstärker für bevorzugte kanonische Normen (z.B. Art. 37 DSGVO bei DSB-Fragen) |
|
||||
| `supersededPenalty` | `0.50` | abgelöste Alt-Quelle demoten, „damit Default-Fragen die eu-v1-Norm sehen, History aber auffindbar bleibt" |
|
||||
| `intentLiftGain` | `0.10` | Epsilon-Lift einer Guidance über das beste bindende Recht bei Auslegungs-Intent |
|
||||
| `intentLiftMargin` | `0.05` | Guard: Lift nur, wenn die Semantik innerhalb von 0.05 zum besten bindenden Treffer liegt |
|
||||
|
||||
**Auslegungs-Intent-Signale** (`guidanceIntentSignals`): `edpb`, `dsk`, `enisa`, `bsi`, `leitlinie`, `guideline`, `orientierungshilfe`, `auslegung`, `empfiehlt`, `empfehlung`, `sagt`, `laut`, …
|
||||
|
||||
## Code
|
||||
|
||||
- `authority_rerank.go` → `authorityScore()`, `rerankByAuthority()`, `bestBindingSemantic()`, `liftAboveBinding()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„Guidance verdrängt Gesetz"** → `authorityCoef`·weight hebt bindendes Recht; `liftAboveBinding` nur mit Margin-Guard.
|
||||
- **„Fremdrecht Top-1"** → `foreignPenalty`.
|
||||
- **„Off-Domain-Gesetz dominiert"** → `domainMatchGain` / `offDomainPenalty` / `scopePenalty`.
|
||||
- **„Veraltete Norm gewinnt"** → `supersededPenalty` (siehe [08](08-explainability.md)).
|
||||
@@ -0,0 +1,49 @@
|
||||
# 03 — `source_class` (Rechtsnatur / Autorität)
|
||||
|
||||
**Zweck:** Die Autoritäts-Achse, die den **Rang** bestimmt (siehe [02](02-authority.md)). Deterministisch abgeleitet — der noch nicht re-ingestierte (ungetaggte) Korpus wird trotzdem klassifiziert, ohne Re-Tagging des Bestands.
|
||||
|
||||
## Mechanik
|
||||
|
||||
`classifyAuthority()` (`authority.go`) entscheidet in dieser Reihenfolge:
|
||||
|
||||
1. **Standard-NAME-Override** — erkannter Standard-Name (NIST/OWASP/ISO 27001/CIS/CSA CCM/Grundschutz) erzwingt `technical_standard` (Gewicht 80), **auch wenn die Payload `supervisory_guidance` sagt**. Grund: der Korpus taggt viele Standards mit generischem guidance-`source_class`; der Name ist autoritativer. `binding_law` bleibt unangetastet.
|
||||
2. **Explizite Payload-Werte** — gesetztes `source_class` / `authority_weight` gewinnen.
|
||||
3. **Marker-Fallback** — foreign → standard → guidance → regulation → unknown.
|
||||
|
||||
`inferJurisdiction()`: Fremd-Marker → `CH`; enthält `§` oder DE-Marker → `DE`; sonst → `EU`.
|
||||
|
||||
## Konstanten + Warum
|
||||
|
||||
**Gewichte je Klasse** (`sourceClassFromWeight()`):
|
||||
|
||||
| `source_class` | Gewicht | Schwelle | Bedeutung |
|
||||
|----------------|---------|----------|-----------|
|
||||
| `binding_law` | `100` | w ≥ 100 | bindendes Recht (Gesetz/VO) |
|
||||
| `technical_standard` | `80` | 80 ≤ w < 100 | Best-Practice-Control-Katalog (NIST/OWASP/ISO) |
|
||||
| `supervisory_guidance` | `70` | 70 ≤ w < 80 | Aufsichts-/Auslegungs-Guidance (ENISA/BSI/EDPB) |
|
||||
| `unknown` | `50` | default | unklassifiziert |
|
||||
| `foreign_law` | `0` | w ≤ 0 | Fremdrecht (CH) |
|
||||
|
||||
**Marker-Listen** (Substring-Match):
|
||||
|
||||
| Liste | Einträge (Auszug) | Wirkung |
|
||||
|-------|-------------------|---------|
|
||||
| `standardMarkers` *(vor guidance geprüft)* | NIST, OWASP, Grundschutz, ISO 27001, ISO/IEC 27001, CSA CCM, Cloud Controls Matrix, CIS Benchmark, CIS Control | → `technical_standard` (80) |
|
||||
| `guidanceMarkers` | DSK, EDPB, BfDI, ENISA, BSI, EUCC, Standards Mapping, Orientierungshilfe, Handreichung, Leitlinie, Empfehlung, OECD, CISA, Blue Guide, … | → `supervisory_guidance` (70) |
|
||||
| `foreignMarkers` | RevDSG, fedlex, (CH) | → `foreign_law` (0) |
|
||||
| `deMarkers` | BDSG, DSK, BfDI, BayLfD, BSI | Signal **DE**-Jurisdiktion |
|
||||
|
||||
## Der Standard-Name-Override (Fix 2026-06-25)
|
||||
|
||||
**Problem:** Der CE-Korpus taggt z.B. `NIST SP 800-82r3` als `source_class=supervisory_guidance` (Gewicht 70), **nicht** technical_standard. `classifyAuthority` vertraute dem Payload-Tag → NIST landete als guidance, **kein `control_standard`** im Pool → die Diversity-Regel ([05](05-control-intent.md)) konnte nichts injizieren.
|
||||
|
||||
**Fix:** Erkannter Standard-Name überschreibt ein fehl-getaggtes guidance/unknown-`source_class` → `technical_standard`. Code-Fix, **kein Re-Ingest** nötig. Bindendes Recht bleibt unangetastet (Sanity geprüft: Rechtsfrage liefert weiterhin binding Top-1).
|
||||
|
||||
## Code
|
||||
|
||||
- `authority.go` → `classifyAuthority()`, `sourceClassFromWeight()`, `inferJurisdiction()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„Standard als guidance mistagged → kein control_standard"** → Standard-Name-Override.
|
||||
- **„Fremdrecht falsch eingeordnet"** → `foreignMarkers` + `foreign_law`-Gewicht 0.
|
||||
@@ -0,0 +1,60 @@
|
||||
# 04 — `source_role` (Funktionale Rolle)
|
||||
|
||||
**Zweck:** Die zu `source_class` **orthogonale** Achse: *Was tut die Quelle im Dokument?* Sie bestimmt die **Control-Pool-Zugehörigkeit** bei Umsetzungsfragen — unabhängig von der Rechtsnatur. Deterministisch aus Markern abgeleitet, kein Re-Tagging des Bestands.
|
||||
|
||||
## Die 7 Rollen
|
||||
|
||||
| Konstante | Wert | Definition |
|
||||
|-----------|------|-----------|
|
||||
| `roleObligation` | `obligation` | die abstrakte Pflicht (das WAS) |
|
||||
| `roleOperationalReq` | `operational_requirement` | konkrete bindende Anforderung (z.B. CRA Anhang I) |
|
||||
| `roleProceduralReq` | `procedural_requirement` | Prozess: Meldung/Registrierung/DSFA/Incident |
|
||||
| `roleControlStandard` | `control_standard` | Best-Practice-Katalog (NIST/OWASP/ISO/CIS) |
|
||||
| `roleImplGuidance` | `implementation_guidance` | Umsetzungs-How-to (ENISA Good Practices, BSI) |
|
||||
| `roleInterpretation` | `interpretation` | interpretiert die *Bedeutung* der Norm (EDPB-Leitlinie) |
|
||||
| `roleDefinition` | `definition` | Definitionen / Scope / Recitals |
|
||||
|
||||
**Control-Pool** = `{operational_requirement, procedural_requirement, control_standard, implementation_guidance}` (die vier „wie umsetzen"-Rollen, `isControlPoolRole()`).
|
||||
|
||||
## Mechanik
|
||||
|
||||
`classifyRole()` (`control_role.go`) — Entscheidungsreihenfolge:
|
||||
|
||||
1. `IsRecital` → `definition`
|
||||
2. `source_class == technical_standard` → `control_standard`
|
||||
3. `source_class == supervisory_guidance`:
|
||||
- enthält `implMarker` → `implementation_guidance`
|
||||
- sonst → `interpretation`
|
||||
4. `source_class == binding_law`:
|
||||
- `definitionMarker` → `definition`
|
||||
- `proceduralMarker` → `procedural_requirement`
|
||||
- `annexMarker` **oder** `operationalMarker` → `operational_requirement`
|
||||
- sonst → `obligation`
|
||||
5. default → `obligation`
|
||||
|
||||
`controlRoleOf(payload)` klassifiziert die rohe Qdrant-Payload **vor** dem Mapping — so kann `searchControls` ([01](01-retrieval.md)) seinen tiefen dense-Pull filtern, ohne jeden Treffer voll zu materialisieren.
|
||||
|
||||
## Marker-Listen
|
||||
|
||||
| Liste | Einträge (Auszug) | → Rolle |
|
||||
|-------|-------------------|---------|
|
||||
| `proceduralMarkers` | Meldung, Meldepflicht, Notification, Registrierung, Konformitätserklärung, Incident, Reporting, Folgenabschätzung, DSFA, DPIA, Anzeigepflicht | `procedural_requirement` |
|
||||
| `annexMarkers` | Anhang, Annex, Appendix, Anlage | `operational_requirement` |
|
||||
| `operationalMarkers` | Anforderung, Requirement, essential, wesentliche | `operational_requirement` |
|
||||
| `implMarkers` | Good Practice, Best Practice, Standards Mapping, Umsetzung, Implementation, Handreichung, Maßnahmenkatalog, ICS, SCADA, Technical Guideline, TIG | `implementation_guidance` |
|
||||
| `definitionMarkers` | Begriffsbestimmung, Definition | `definition` |
|
||||
|
||||
## Warum orthogonal zu `source_class`
|
||||
|
||||
`source_class` (Rechtsnatur) und `source_role` (Funktion) sind **zwei Achsen**, nicht eine. ENISA bleibt `supervisory_guidance` (Rechtsnatur) **und** `implementation_guidance` (Funktion) — sie wird **nicht** umgetaggt (fachlich falsch), darf aber bei Umsetzungsfragen in den Control-Pool. So muss der Bestand nicht angefasst werden: `source_role` ist wie `source_class` aus Markern ableitbar.
|
||||
|
||||
`source_role` ist die **Wirbelsäule der Langzeit-Architektur** Regulation → Obligation → Operational Requirement → Control → Evidence ([09](09-framework-layer.md), Prio 4).
|
||||
|
||||
## Code
|
||||
|
||||
- `control_role.go` → `classifyRole()`, `controlRoleOf()`, `isControlPoolRole()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„Controls = nur technical_standard"** → vier Control-Pool-Rollen statt einer.
|
||||
- **„abstrakte Pflicht dominiert Umsetzungsfrage"** → `obligation` ist *nicht* im Control-Pool (siehe [05](05-control-intent.md)).
|
||||
@@ -0,0 +1,51 @@
|
||||
# 05 — Control-Intent + Diversity
|
||||
|
||||
**Zweck:** Bei einer **Umsetzungsfrage** („Welche Controls/Maßnahmen passen?") den Control-Pool ([04](04-source-role.md)) über die abstrakte Pflicht heben — und sicherstellen, dass die Ergebnisliste **verschiedene Quellenarten** zeigt, statt dass eine Rolle sie flutet. Bei einer **Rechtsfrage** bleibt alles beim Authority-Rerank ([02](02-authority.md)).
|
||||
|
||||
## Intent-Erkennung
|
||||
|
||||
`queryWantsControls()` (`authority_rerank.go`) — Keyword-Match (`controlIntentSignals`):
|
||||
|
||||
> control, controls, maßnahme, schutzmaßnahme, best practice, umsetzen, implementier, absicher, härt, hardening, nist, owasp, grundschutz, ccm, iso 27001, isms
|
||||
|
||||
Nur wenn dieser Gate `true` ist, feuern `applyControlRoles()` und `ensureControlDiversity()`.
|
||||
|
||||
## Rollen-Boost (`applyControlRoles`)
|
||||
|
||||
Jeder Control-Pool-Treffer bekommt `controlPoolGain + controlRoleBonus[role]` auf den Score:
|
||||
|
||||
| Größe | Wert | Warum |
|
||||
|-------|------|-------|
|
||||
| `controlPoolGain` | `0.15` | hebt **jede** Control-Pool-Rolle über die Nicht-Control-Rollen (obligation/interpretation/definition) — sonst gewinnt die bindende abstrakte `obligation` per Autorität allein |
|
||||
| `controlRoleBonus[operational_requirement]` | `0.100` | weicher Intra-Pool-Vorrang (User 2026-06-24): op_req zuerst |
|
||||
| `controlRoleBonus[procedural_requirement]` | `0.075` | … dann Prozess-Pflichten |
|
||||
| `controlRoleBonus[control_standard]` | `0.050` | … dann Standard-Kataloge |
|
||||
| `controlRoleBonus[implementation_guidance]` | `0.000` | guidance als Basis, kein Bonus |
|
||||
|
||||
> **Bewusst weich, keine harte Hierarchie:** Eine semantisch dominante `implementation_guidance` (z.B. ENISA bei einer EU-Cyber-Umsetzungsfrage) **darf Top-1 bleiben** — das ist fachlich korrekt. Der Boost demoted nur die abstrakte Pflicht, er erzwingt keine Reihenfolge.
|
||||
|
||||
## Control-Diversity-Regel (`ensureControlDiversity`)
|
||||
|
||||
**Problem:** Selbst mit Boost kann eine dichte Wolke gleicher Rolle (viele ENISA-Chunks) `operational_requirement` und `control_standard` aus der Top-K verdrängen — die Quellenarten werden unsichtbar.
|
||||
|
||||
**Lösung (statt harter `+0.30`-Rollenkeule):** Wenn die Top-K nur `implementation_guidance` enthält, **injiziere** den besten `operational_requirement` + besten `control_standard` aus dem Pool, indem der niedrigst-platzierte redundante guidance-Slot verdrängt wird. Algorithmus:
|
||||
|
||||
1. Rolle jedes Treffers bestimmen (`roleAt`).
|
||||
2. Prüfen, welche Rollen in der Top-K vertreten sind.
|
||||
3. Für jede fehlende Wunsch-Rolle (`operational_requirement`, `control_standard`): besten Treffer dieser Rolle unterhalb der Top-K finden, niedrigste `implementation_guidance` in der Top-K überschreiben.
|
||||
4. Truncate auf `topK` (das ursprüngliche Duplikat fällt im Tail weg).
|
||||
|
||||
**Ergebnis live:** Umsetzungsfrage → `1.–4. ENISA · 5. NIST SP 800-82r3 (control_standard) · 6. MaschinenVO Anhang-III (op_req)`. ENISA behält Top-1, die anderen Quellenarten sind sichtbar.
|
||||
|
||||
> **Prinzip:** Nicht raten, nicht erzwingen, sondern relevante Quellenarten sichtbar machen.
|
||||
|
||||
## Code
|
||||
|
||||
- `authority_rerank.go` → `queryWantsControls()`
|
||||
- `control_role.go` → `applyControlRoles()`, `ensureControlDiversity()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„abstrakte Pflicht dominiert Umsetzungsfrage"** → `controlPoolGain`.
|
||||
- **„eine Rolle flutet die Top-K, Quellenarten unsichtbar"** → `ensureControlDiversity`.
|
||||
- **„harte Tier-Ordnung overfittet auf eine Frage"** → weicher Boost statt Keule.
|
||||
@@ -0,0 +1,45 @@
|
||||
# 06 — Assessment
|
||||
|
||||
**Zweck:** Eine **auditierbare Begründungsschicht** über die gerankten Ergebnisse. Sie macht aus einer Trefferliste eine prüfbare Aussage: *Welche Norm ist primär, welche hängen daran, wie eindeutig ist das, braucht es einen Menschen?*
|
||||
|
||||
## Mechanik
|
||||
|
||||
`Assess()` (`legal_rag_assess.go`) nimmt die bereits gerankten `results []LegalSearchResult` und baut ein `LegalAssessment`:
|
||||
|
||||
| Feld | Inhalt |
|
||||
|------|--------|
|
||||
| `PrimaryNorm` | `CitationUnit` bzw. `ArticleLabel` des Top-Treffers |
|
||||
| `PrimaryRegulation` | `RegulationShort` des Top-Treffers |
|
||||
| `ConnectedNorms` | verbundene Normen (`references_out` + `references_in`), gekappt + dedupliziert |
|
||||
| `CrossRegime` | ob mehrere Regulierungen in den Top-N liegen |
|
||||
| `WinnerMargin` | Score-Abstand Top-1 ↔ Top-2 (Proxy für Eindeutigkeit) |
|
||||
| `HumanReviewFlag` | true bei niedriger Eindeutigkeit |
|
||||
| `ScoreReasoning` | kurze deutsche Begründung |
|
||||
|
||||
## Konstanten + Warum
|
||||
|
||||
| Konstante | Wert | Warum |
|
||||
|-----------|------|-------|
|
||||
| `assessConnectedCap` | `12` | Obergrenze der in der Assessment gezeigten verbundenen Normen — verhindert, dass ein stark vernetzter Artikel die Begründung flutet |
|
||||
| `assessCrossRegimeTopN` | `5` | Fenster, über das „Cross-Regime" (mehrere Regulierungen) beurteilt wird |
|
||||
| `assessReviewMargin` | `0.05` | enger Winner-Abstand → Human-Review-Flag (siehe [07](07-confidence.md)) |
|
||||
|
||||
## Human-Review-Logik
|
||||
|
||||
`HumanReviewFlag` wird `true`, wenn **eine** der Bedingungen gilt:
|
||||
|
||||
- `WinnerMargin < 0.05` — Top-1 und Top-2 liegen zu dicht beieinander (uneindeutig),
|
||||
- `CrossRegime == true` — mehrere Regimes betroffen (z.B. DSGVO + CRA),
|
||||
- der Primär-Treffer ist **nicht** `binding_law` — eine Rechtsaussage ohne bindende Primärquelle.
|
||||
|
||||
> Das ist die deterministische Eskalations-Schwelle: das System sagt von sich aus „hier sollte ein Mensch drauf schauen", statt scheinbare Sicherheit vorzutäuschen.
|
||||
|
||||
## Code
|
||||
|
||||
- `legal_rag_assess.go` → `Assess()`, `primaryLabel()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„uneindeutige Antwort wird als sicher verkauft"** → `WinnerMargin` + `HumanReviewFlag`.
|
||||
- **„Cross-Regime übersehen"** → `CrossRegime` über `assessCrossRegimeTopN`.
|
||||
- **„Rechtsaussage ohne bindende Quelle"** → Flag bei nicht-bindendem Primär-Treffer.
|
||||
@@ -0,0 +1,38 @@
|
||||
# 07 — Confidence
|
||||
|
||||
**Zweck:** Eine ehrliche Aussage über die Verlässlichkeit eines Ergebnisses — ohne einen erfundenen „Confidence: 87 %"-Wert, der Scheinsicherheit suggeriert.
|
||||
|
||||
## Bewusste Entscheidung: kein eigenes Confidence-Feld
|
||||
|
||||
Es gibt **kein** explizites `confidence`-Feld in der Engine. Stattdessen wird Verlässlichkeit aus zwei real berechneten, prüfbaren Größen abgeleitet:
|
||||
|
||||
| Größe | Quelle | Bedeutung |
|
||||
|-------|--------|-----------|
|
||||
| `WinnerMargin` | `LegalAssessment` ([06](06-assessment.md)) | Score-Abstand Top-1 ↔ Top-2 — wie klar „gewinnt" die Primärnorm? |
|
||||
| `HumanReviewFlag` | `LegalAssessment` | deterministische Eskalation: ist die Antwort uneindeutig/grenzwertig? |
|
||||
|
||||
**Warum so?** Ein kalibrierter Wahrscheinlichkeitswert würde eine Genauigkeit vortäuschen, die ein regelbasierter Retriever nicht hat. Der **Abstand** zwischen Top-1 und Top-2 ist dagegen eine *gemessene*, erklärbare Größe: ein großer Margin = eindeutige Norm, ein kleiner Margin = mehrere plausible Quellen → Mensch entscheiden lassen.
|
||||
|
||||
## Schwelle
|
||||
|
||||
| Konstante | Wert | Wirkung |
|
||||
|-----------|------|---------|
|
||||
| `assessReviewMargin` | `0.05` | `WinnerMargin < 0.05` ⇒ `HumanReviewFlag = true` |
|
||||
|
||||
`HumanReviewFlag` feuert zusätzlich bei Cross-Regime und bei nicht-bindender Primärquelle ([06](06-assessment.md)).
|
||||
|
||||
## Verhältnis zur Authority-Schicht
|
||||
|
||||
Der `Score`, auf dem der Margin beruht, ist **nicht** der rohe Semantik-Score, sondern der Authority-Score nach dem Rerank ([02](02-authority.md)). Damit misst der Margin die *normative* Eindeutigkeit (Rechtsnatur + Domäne berücksichtigt), nicht nur die semantische Ähnlichkeit.
|
||||
|
||||
## Code
|
||||
|
||||
- `legal_rag_types.go` → `LegalSearchResult.Score`, `LegalAssessment.WinnerMargin`, `LegalAssessment.HumanReviewFlag`
|
||||
- `legal_rag_assess.go` → Berechnung in `Assess()`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„Scheinsicherheit"** → kein erfundener Prozentwert; Margin + Flag statt Pseudo-Confidence.
|
||||
- **„knappe Entscheidung wird automatisch durchgewinkt"** → `assessReviewMargin`-Eskalation.
|
||||
|
||||
> **Ausbaustufe:** Echte Citation-Gating-Confidence (Finding nur bei Quelle ∧ Scope ∧ Stichtag) gehört in die Authority-/Freshness-Schicht und an Control → Evidence ([09](09-framework-layer.md)), nicht in einen Modell-Score.
|
||||
@@ -0,0 +1,42 @@
|
||||
# 08 — Explainability, Zitate + Supersede
|
||||
|
||||
**Zweck:** Jedes Ergebnis muss sich **belegen** lassen — woher es kommt, womit es verbunden ist, und ob es noch gilt. Das ist die Grundlage für Zitierfähigkeit und für die spätere Citation-Gating-Logik.
|
||||
|
||||
## Zitate + Graph-Kanten
|
||||
|
||||
Aus der Qdrant-Payload geladen (Phase-2-Graph-Metadaten):
|
||||
|
||||
| Feld | Inhalt | Verwendung |
|
||||
|------|--------|-----------|
|
||||
| `CitationUnit` | kanonischer Artikel-/Anhang-Identifier | Dedup, Primärnorm-Label |
|
||||
| `article_label` | menschenlesbare Fundstelle (z.B. „Art. 13 CRA") | Anzeige, Begründung |
|
||||
| `citation_style` | Zitierformat-Marker | Anzeige |
|
||||
| `references_out` | Normen, die dieser Chunk **zitiert** (Forward-Kanten) | Graph-Expansion ([01](01-retrieval.md)) + `ConnectedNorms` |
|
||||
| `references_in` | Normen, die **diesen** Chunk zitieren (Reverse-Kanten) | **nur** Metadaten — nicht expandiert (Flutungsschutz) |
|
||||
|
||||
`Assess()` ([06](06-assessment.md)) verdichtet die Kanten zu `ConnectedNorms` — so wird sichtbar, dass z.B. Art. 13 CRA auf Anhang I verweist (die eigentliche Pflichtquelle).
|
||||
|
||||
## Supersede-Handling
|
||||
|
||||
Recht ändert sich; ein veralteter Stand darf den aktuellen nicht schlagen — aber Übergangs-/History-Fragen müssen ihn noch finden.
|
||||
|
||||
| Mechanik | Wert / Feld | Verhalten |
|
||||
|----------|-------------|-----------|
|
||||
| **Erkennung** | Payload `status == "superseded"` → `Superseded`-Flag | markiert die abgelöste Alt-Quelle |
|
||||
| **Demotion** | `supersededPenalty = 0.50` (`authorityScore`, [02](02-authority.md)) | konsequente Zurückstufung |
|
||||
| **Philosophie** | — | „Alt-Quelle demoted (nicht versteckt) — Default-Fragen sehen die eu-v1-Norm, History bleibt auffindbar" |
|
||||
|
||||
> **Nicht entfernt, nur bestraft:** Eine abgelöste Norm kann bei einer expliziten History-Frage trotzdem hoch ranken — sie wird nur konsistent demoted, nicht ausgeblendet. Das ist dieselbe „Reihenfolge, nichts löschen"-Linie wie beim Authority-Rerank.
|
||||
|
||||
## Code
|
||||
|
||||
- `legal_rag_client.go` → Payload-Mapping (`references_out/in`, `status`)
|
||||
- `legal_rag_graph.go` → Forward-Kanten-Expansion, Reverse-Kanten als Metadaten
|
||||
- `legal_rag_assess.go` → `ConnectedNorms`
|
||||
- `authority_rerank.go` → `supersededPenalty`
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„Aussage ohne Fundstelle"** → `CitationUnit` / `article_label`.
|
||||
- **„Pflichtquelle hinter Verweis versteckt"** → Forward-Kanten-Expansion (Art. 13 → Anhang I).
|
||||
- **„veralteter Rechtsstand gewinnt"** → `supersededPenalty`, aber auffindbar.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 09 — `framework_*`-Layer (Control-Mapping-Brücke)
|
||||
|
||||
**Zweck:** Einen **konkreten Control adressierbar** machen (z.B. `V14.2.4`), damit das System vom „welches Dokument passt?" zum „welcher konkrete Control erfüllt CRA Annex I?" übergeht. Das ist die Brücke zur nächsten Stufe — **Control → Evidence** — und der eigentliche Burggraben.
|
||||
|
||||
> **Ehrlicher Status:** Dieser Layer lebt **heute in der Qdrant-Payload**, nicht im Retrieval-Code. Die `ucca`-Engine liest/routet `framework_*` (noch) nicht — sie ist die **Datengrundlage**, auf der Prio 4 aufsetzt. `framework_control` reist aktuell im Feld `article` mit und ist daher bereits in den Antworten sichtbar.
|
||||
|
||||
## Schema (pro Chunk)
|
||||
|
||||
| Feld | Beispiel (OWASP) | Bedeutung |
|
||||
|------|------------------|-----------|
|
||||
| `framework` | `OWASP ASVS` | Rahmenwerk |
|
||||
| `framework_version` | `5.0` | Version (mit `superseded`-Mechanik historisierbar, [08](08-explainability.md)) |
|
||||
| `framework_section` | `V6` | Kapitel/Sektion |
|
||||
| `framework_control` | `V6.2.4` | konkrete Requirement-ID — der adressierbare Control |
|
||||
| `framework_section_name` | `Password Security` | menschenlesbarer Kontext |
|
||||
| `asvs_level` | `L1`/`L2`/`L3` | (OWASP-spezifisch) Stufe |
|
||||
|
||||
Analog für NIST geplant: `framework="NIST SP 800-53"`, `framework_family="SI"`, `framework_control="SI-2"`, `framework_revision="5"`.
|
||||
|
||||
## OWASP ASVS 5.0 — die erste Referenz (Parser-4-Muster)
|
||||
|
||||
- **Quelle:** `OWASP/ASVS` GitHub, `5.0/docs_en/...flat.json` (345 Requirements). Lizenz **CC-BY-SA-4.0** (zulässig; nur CC-BY-NC ist geblockt), Attribution `OWASP`.
|
||||
- **Ingestion = per-Requirement Direct-Upsert** (nicht der RAG-Chunker, der `framework_control` zerschneiden würde): 1 Qdrant-Punkt pro Requirement, `id = uuid5("owasp_asvs_5.0_"+req_id)` (idempotent), `source_class=technical_standard` / `authority_weight=80`, bge-m3-Vektor.
|
||||
- **Stand:** 345 Punkte auf macmini-qdrant **und** qdrant-dev, live verifiziert (`„OWASP … Authentifizierung"` → Top-OWASP mit `V`-Codes).
|
||||
- **Lehre:** Künftige Standards (NIST-Re-Tag, BSI Grundschutz) **immer** mit `source_class=technical_standard` + `framework_*` direkt setzen — das NIST-Altskript ließ `source_class` leer, daher der guidance-Mistag ([03](03-source-class.md)).
|
||||
|
||||
## Brücke zu Prio 4 — Control → Evidence
|
||||
|
||||
```
|
||||
Regulation
|
||||
↓ (legal obligation layer)
|
||||
Obligation
|
||||
↓ (source_role: operational_requirement)
|
||||
Operational Requirement ── CRA Annex I
|
||||
↓ (Control-Mapping über framework_control)
|
||||
Control ── OWASP V6.x · NIST SI-2 · BSI OPS.1.1
|
||||
↓
|
||||
Evidence ── der Nachweis, den ein Auditor sehen will
|
||||
```
|
||||
|
||||
Der nächste Schritt verdrahtet `framework_control` in eine **Control-Mapping-Tabelle** (welcher konkrete Control erfüllt welche Obligation) und darunter die **Evidence-Schicht**. NIST + BSI ziehen im selben `framework_*`-Muster nach.
|
||||
|
||||
## Code / Daten
|
||||
|
||||
- Daten: Qdrant `bp_compliance_ce` (Payload-Felder oben), Ingestion-Skripte (`ingest_owasp.py` u.a.)
|
||||
- Retrieval-Verdrahtung: **offen** (Prio 4)
|
||||
|
||||
## Adressierte Fehlerklassen
|
||||
|
||||
- **„nur Dokument-Treffer, kein adressierbarer Control"** → `framework_control` pro Chunk.
|
||||
- **„Control-Katalog ohne Stand"** → `framework_version` + Supersede.
|
||||
@@ -0,0 +1,57 @@
|
||||
# RAG-Retrieval-Engine — Architektur
|
||||
|
||||
Diese Sektion dokumentiert die **deterministische, regelbasierte Retrieval-Engine** des Compliance-SDK (`ai-compliance-sdk/internal/ucca/`). Sie beantwortet für jede Nutzerfrage: *Welche Norm/Quelle ist relevant — und warum?*
|
||||
|
||||
> **Warum diese Doku existiert:** Die Engine trifft viele bewusste `+0.05 / +0.10`-Entscheidungen. Jede Konstante kodiert eine **gemessene** Entscheidung (Golden-Harness, Fehlerklasse) — nicht eine willkürliche Stellschraube. Ohne das *Warum* sind sie in sechs Monaten nicht mehr nachvollziehbar; diese Doku ist die Referenz für Wartung, Onboarding und Audit-/Investoren-Nachweis.
|
||||
|
||||
## Leitprinzip
|
||||
|
||||
> **Nicht raten, nicht erzwingen, sondern relevante Quellenarten sichtbar machen.**
|
||||
|
||||
Der LLM entscheidet **nicht**, was Recht ist — nur, wie eine bereits versionierte, zitierte Norm auf einen Sachverhalt gemappt wird. Wo möglich ist die Engine deterministisch (Marker, Gewichte, Schwellen), nicht modellbasiert. Nichts wird *gelöscht* — Re-Ranking ist reine Reihenfolge, alles bleibt auffindbar.
|
||||
|
||||
## Zwei orthogonale Achsen
|
||||
|
||||
Der Kern des Modells: zwei unabhängige Achsen, die in der Literatur meist vermischt werden.
|
||||
|
||||
| Achse | Frage | Wirkung | Doku |
|
||||
|------|-------|---------|------|
|
||||
| **`source_class`** (Rechtsnatur) | Wie bindend ist die Quelle? | bestimmt den **Rang** | [03](03-source-class.md) |
|
||||
| **`source_role`** (Funktion) | Was tut die Quelle im Dokument? | bestimmt die **Control-Pool-Zugehörigkeit** | [04](04-source-role.md) |
|
||||
|
||||
Beispiel: NIST ist `technical_standard` (source_class) **und** `control_standard` (source_role). ENISA-Good-Practices sind `supervisory_guidance` **und** `implementation_guidance` — sie bleiben guidance, dürfen aber bei Umsetzungsfragen in den Control-Pool.
|
||||
|
||||
## Pipeline (Überblick)
|
||||
|
||||
```
|
||||
Query
|
||||
│ bge-m3 Embedding
|
||||
▼
|
||||
Retrieval-Pool ── hybrid (RRF) + binding-Augmentation + control-Augmentation + (graph) → 01
|
||||
▼
|
||||
Authority-Rerank ── source_class → Rang (bindendes Recht der passenden Jurisdiktion oben) → 02, 03
|
||||
▼
|
||||
Control-Intent ── source_role → Control-Pool + Diversity (Quellenarten sichtbar machen) → 04, 05
|
||||
▼
|
||||
Assessment ── PrimaryNorm · ConnectedNorms · WinnerMargin · CrossRegime → 06
|
||||
▼
|
||||
Confidence/Explainability ── HumanReviewFlag · Zitate · Graph-Kanten · Supersede → 07, 08
|
||||
```
|
||||
|
||||
`framework_*` ([09](09-framework-layer.md)) ist die **Daten-Brücke** zur nächsten Stufe (Control → Evidence) — heute in der Qdrant-Payload, noch nicht im Retrieval-Code verdrahtet.
|
||||
|
||||
## Dokumente
|
||||
|
||||
| # | Dokument | Inhalt |
|
||||
|---|----------|--------|
|
||||
| 01 | [Retrieval-Pipeline](01-retrieval.md) | Pool-Aufbau: hybrid + binding + control + graph |
|
||||
| 02 | [Authority-Re-Ranking](02-authority.md) | source_class → Rang, Bonus/Penalty-System |
|
||||
| 03 | [source_class](03-source-class.md) | Rechtsnatur, Gewichte, Marker, Standard-Name-Override |
|
||||
| 04 | [source_role](04-source-role.md) | 7 Rollen, Control-Pool, Klassifikation |
|
||||
| 05 | [Control-Intent + Diversity](05-control-intent.md) | Intent-Erkennung, Rollen-Bonus, Diversity-Regel |
|
||||
| 06 | [Assessment](06-assessment.md) | Auditierbare Begründungsschicht |
|
||||
| 07 | [Confidence](07-confidence.md) | WinnerMargin, HumanReviewFlag |
|
||||
| 08 | [Explainability + Supersede](08-explainability.md) | Zitate, Graph-Kanten, Supersede |
|
||||
| 09 | [framework_*-Layer](09-framework-layer.md) | Control-Mapping-Brücke (CRA Annex → OWASP V6.x) |
|
||||
|
||||
> **Fehlerklassen-These:** Modell und Korpus sind austauschbar; die *Fehlerklassen + Hebel* sind das IP. Jede Konstante unten adressiert eine benannte Fehlerklasse (z.B. „Guidance verdrängt Gesetz", „Standard als guidance mistagged"). Die Kalibrierung ist sublinear: wenige Klassen, viele Module.
|
||||
+11
@@ -56,6 +56,17 @@ markdown_extensions:
|
||||
|
||||
nav:
|
||||
- Start: index.md
|
||||
- Architektur RAG:
|
||||
- Übersicht: architecture/index.md
|
||||
- 01 Retrieval-Pipeline: architecture/01-retrieval.md
|
||||
- 02 Authority-Re-Ranking: architecture/02-authority.md
|
||||
- 03 source_class: architecture/03-source-class.md
|
||||
- 04 source_role: architecture/04-source-role.md
|
||||
- 05 Control-Intent + Diversity: architecture/05-control-intent.md
|
||||
- 06 Assessment: architecture/06-assessment.md
|
||||
- 07 Confidence: architecture/07-confidence.md
|
||||
- 08 Explainability + Supersede: architecture/08-explainability.md
|
||||
- 09 framework_*-Layer: architecture/09-framework-layer.md
|
||||
- Services:
|
||||
- AI Compliance SDK:
|
||||
- Uebersicht: services/ai-compliance-sdk/index.md
|
||||
|
||||
Reference in New Issue
Block a user