- Python 95.3%
- PowerShell 2.4%
- Shell 2%
- Dockerfile 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Quality / Lint + Typpruefung (push) Successful in 43s
Quality / Tests (Python 3.11) (push) Successful in 2m4s
Quality / Tests (Python 3.12) (push) Successful in 2m19s
Security / Secret Scan (push) Successful in 4s
Security / Dependency Audit (push) Successful in 1m42s
Build / Deploy-Artefakte validieren (push) Successful in 19s
Build / Release-Quality (Python 3.11) (push) Successful in 2m2s
Build / Release-Quality (Python 3.12) (push) Successful in 2m11s
Build / Engine-Image bauen (push) Successful in 3s
Build / Release-Security (push) Successful in 2m18s
Build / Release veröffentlichen (nur vMAJOR.MINOR.PATCH) (push) Successful in 1h6m37s
|
||
| .forgejo | ||
| benchmarks | ||
| cards | ||
| catalog | ||
| corpus | ||
| deploy | ||
| docs | ||
| examples | ||
| fixtures | ||
| release-input | ||
| risk_layer | ||
| scripts | ||
| tests | ||
| tools | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| docker-compose.dev.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Dockerfile.release | ||
| FEATURES.md | ||
| INTEGRATION_TAXTRONIK.md | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| requirements-embedding-linux-cpu-py312-lock.txt | ||
| requirements-embedding-linux-cpu-py312.in | ||
| requirements-embedding-windows-amd-py312-lock.txt | ||
| requirements-embedding-windows-amd-py312.in | ||
| requirements-embedding-windows-cpu-py312-lock.txt | ||
| requirements-embedding-windows-cpu-py312.in | ||
| requirements-lock.txt | ||
| requirements-ner-lock.txt | ||
| requirements-ner.in | ||
| requirements-quality-lock.txt | ||
| requirements-quality.in | ||
| requirements-quanten-linux-py312-lock.txt | ||
| requirements-quanten-linux-py312.in | ||
| requirements-quanten-windows-py312-lock.txt | ||
| requirements-quanten-windows-py312.in | ||
| requirements-runtime-lock.txt | ||
| requirements-runtime.in | ||
| requirements-security-lock.txt | ||
| requirements-security.in | ||
| requirements.in | ||
| signal | ||
| signal.ps1 | ||
Signal — Schlüsselbegriff-Assistenz für die steuerliche Risikosteuerung
Der stabile technische HTTP-/Paketname bleibt aus Bestandsgründen
risk_layer beziehungsweise Risk-Layer-API.
Die Engine liest steuerrechtliche Texte und markiert die unbestimmten Rechtsbegriffe, an denen subsumiert werden muss. Sie beantwortet die Rechtsfrage nicht — sie stellt sicher, dass die Sollbruchstelle gesehen wird und die zugehörige Definitionsquelle einen Klick entfernt liegt.
Das System lenkt Aufmerksamkeit, es übernimmt keine Subsumtion. Die Bewertung schuldet der Berufsträger höchstpersönlich (§§ 33, 57 StBerG).
Umsetzung des Lastenhefts „Schlüsselbegriff-Assistenz" samt Erweiterungen. Mehrere deterministische Schichten erkennen auch umschriebene Sachverhalte ohne LLM (Muster, Trigger), ein semantischer Vorschlags-Modus (BGE-M3-Embedding) fängt Paraphrasen, und Fundstellen werden als Normketten (Kaskaden) geführt.
📋 Vollständiges Feature-Inventar inkl. benannter Grenzen: FEATURES.md.
Leitprinzip aller Schichten: Das System lenkt Aufmerksamkeit, es übernimmt keine Subsumtion. Definitionen und Normzuordnungen stammen aus dem lokalen Korpus/Katalog, nie aus Modellgedächtnis. Das Embedding-Modell liefert nur Vektoren; verglichen wird gegen die eingebetteten Gesetzestexte.
Aktueller Architekturstand: Scope-aware Retrieval
Der semantische Teil des Risk-Layers ist nicht als globaler Flat-Index gedacht. Eval-Läufe haben gezeigt: Werden alle Paraphrasen-Anker ungefiltert in denselben Kandidatenraum gelegt, entstehen cross-law- und intra-law-Verdrängungen. Der produktive Zielpfad ist deshalb:
Scope / Rechtsraum
→ Kandidaten innerhalb des Scopes
→ Reranker
→ Norm-Aggregation
Freeze-Stand: Innerhalb des Scopes rerankt ein Normpaket je Norm (Norm-Titel + §-Textauszug + Top-k Cards) statt einzelner Anker. Das repariert intra-law-Geschwister- kollisionen und signalverteilte Normen — kann aber scharfe Einzelanker verwässern (gemessen netto positiv, Precision-Trade-off offen dokumentiert, s. FEATURES.md). Der Flat-Index ist nur Baseline/Diagnose, nicht der finale Produktivpfad.
Der Scope kann produktiv aus dem Host-Workflow bzw. aus Beraterauswahl stammen. Open-World-Scope-Schätzung aus bloßem Text bleibt ein Fallback-/Forschungsthema.
Kuratierte Cards sind keine Rechtsentscheidung. Sie liefern Evidenz für eine Norm. Bewertet, freigegeben und produktiv übernommen werden sie erst nach fachlicher Kuration durch den Berufsträger.
Was es ist — eine headless Engine
Der Risk-Layer ist eine zustandslose Analyse-Engine mit zwei Zugängen:
- HTTP-Vertrag
/v1/*(web.py+api_v1.py) — stabil, zustandslos, Bearer-Auth. Zum Einbetten in ein Host-System (z. B. TaxTronik): Text rein, Analyse raus, kein Persist. Oberfläche, Mandanten-Persistenz und Audit übernimmt der Host. - CLI (
cli.py) — Analyse, Katalog-/Korpus-/Graph-/Embedding-Build, Zitat-Parsing, Rechtsprechungs-Abgleich. Für Automatisierung (n8n) und Wartung.
Es gibt keine eingebaute GUI und keinen eigenen Audit-Log — beides Aufgabe des Host-Systems. § 203 StGB ist konstitutiv: zur Laufzeit verlässt nichts die Maschine außer dem bewusst freigegebenen Recherche-Export (s. u.).
Schnellstart
Voraussetzung: Python ≥ 3.11. Laufzeitabhängigkeiten: PyYAML (Kern) + numpy
(Schicht 1.7). Nur Standardbibliothek für HTTP/CLI — kein FastAPI, kein Node, kein
Build-Schritt. Optionaler Ausbau (lazy): sentence-transformers (BGE-M3-Semantik),
llama-cpp/llama-server (Schicht 2 LLM), spacy (NER-Anonymisierung +
breitere Lemmatisierung).
Der Checkout hat eine Zero-Config-Oberfläche. Linux verwendet ./signal,
Windows .\signal.ps1. Der erste Aufruf legt .signal/, definitionen/,
Backupziel samt Sentinel und .engine_token lokal an und führt danach das
plattformgerechte Setup aus. Dabei wird ein verwalteter Service eingerichtet
und sofort mit Engine und LLM gestartet:
./signal bootstrap
./signal configure
./signal start
./signal status
./signal backup
./signal update
./signal stop
Dabei ist bootstrap ausschließlich der vollständige Erstaufbau. Ein späteres
update führt git pull --ff-only aus, installiert daraus nur das kleine
Anwendungs-Wheel neu und startet den bereits provisionierten Dienst; Modelle,
Korpusbau und Service-Installation werden nicht wiederholt. Backups werden bewusst separat mit ./signal backup
erstellt. deploy existiert im Checkout nicht und bleibt dem profilierten,
manifestgebundenen Release-Betrieb vorbehalten.
Mit ./signal configure lassen sich Bind-Adresse, Docker-Zugriff,
Implementierung und Service-Unit später interaktiv ändern, ohne Modelle,
Abhängigkeiten oder Korpus erneut aufzubauen.
Betrieb zusammen mit TaxTronik
Für den einfachen Standardbetrieb veröffentlicht jeder stabile Signal-Release
zusätzlich ein versioniertes, self-contained CPU-Image. Es enthält Engine,
Katalog, Graph, Embedding-Runtime, die hash-gepinnte Quantenlos-Runtime und das
manifestierte lokale BGE-M3-Modell in einem read-only Release-Layer. TaxTronik
kann dieses Image bei
SIGNAL_DEPLOYMENT=managed selbst beziehen, Secrets provisionieren und
zusammen mit der Kanzleikonsole aktualisieren; ein Host-Checkout oder separater
Festwissen-Mount ist dafür nicht erforderlich.
Solange noch kein OCI-Release veröffentlicht ist, kann TaxTronik denselben
verwalteten CPU-Pfad mit SIGNAL_DEPLOY_CHANNEL=source direkt aus einem
kontrollierten Git-Checkout bauen. Dafür stellt
scripts/build-managed-image.sh <lokales-image:tag> Runtime einschließlich der
hash-gepinnten Quantenlos-Extras, Graph und das manifestierte lokale Modell
zusammen. Graph und Trigger entstehen aus dem im
selben Commit versionierten Normquellen-Snapshot; der Zielserver muss dafür
nicht auf externe Gesetzesportale zugreifen. Nur der Modellsnapshot benötigt
beim ersten, noch nicht gecachten Source-Build Netz. Der Source-Build berechnet
absichtlich keinen Embedding-Index: Der rechenintensive Aufbau bleibt der
ausdrücklichen Bestätigung in den TaxTronik-Einstellungen vorbehalten.
Native Installationen bleiben der vorgesehene Weg für AMD/ROCm und besondere
GPU-Setups. Sie werden in TaxTronik als SIGNAL_DEPLOYMENT=external angebunden.
Dieser Modus überträgt TaxTronik ausdrücklich keine Lifecycle-Rechte: Setup,
Service und Updates bleiben vollständig beim Signal-Betreiber.
Ein explizites --config beziehungsweise ein checkout-frei installierter
Wrapper schaltet auf den manifestgebundenen Produktionspfad. Dessen Vorlagen
liegen unter
deploy/risk-layer-ops.example.json
(Container/Linux). Native Vorlagen liegen daneben für
Linux und
Windows:
Im profilierten Release-Betrieb werden Backupziel und
.risk-layer-backup-target.json extern provisioniert und genau ein
veröffentlichtes Release unter <release_home>/incoming abgelegt. Diese
Produktionsanforderungen blockieren den lokalen Checkout-Bootstrap nicht.
Das extern bereitgestellte Backupziel wird durch
.risk-layer-backup-target.json an die backup_target_id des Profils gebunden;
doctor --fix erzeugt weder Ziel noch Sentinel. So wird ein fehlender oder
falscher Mount vor Backup, Restore-Vorher-Sicherung und Retention abgelehnt.
Die Arbeitskopien im Repo-Root sind die lokale Operatoroberfläche, nicht selbst
der Release-Vertrauensanker. Im veröffentlichten Paket
werden Wrapper, Profile, Scheduler und Zipapp gemeinsam im bindenden Manifest
gehasht. Checkout-frei wird dieselbe Oberfläche zum Beispiel so aufgerufen:
python3.12 -I risk-layer-release-tools.pyz ops --config PFAD status, oder über
einen stabil installierten Wrapper auf genau diese Zipapp.
Deploy, Update, Backup, Restore und Rollback folgen derselben Form. Der kurze Einstieg steht unter docs/operations/, das technische Detail-Runbook dokumentiert die Recovery-/Expertenebene.
./scripts/setup.sh und .\scripts\setup.ps1 sind lokale Checkout-Bootstraps;
sie sind keine produktiven In-place-Updater. Der lokale
Setup-Pfad provisioniert die NER-Schicht der Anonymisierung (spaCy +
de_core_news_lg) automatisch mit — sie stärkt das §203-Gate. Opt-out für
schlanke/Offline-Installationen: --ohne-ner bzw. -OhneNer; manueller Fallback:
python -m pip install --require-hashes --only-binary=:all: -r requirements-ner-lock.txt. Zur
Laufzeit erzwingt RISK_LAYER_NER=aus den deterministischen Regex-Pfad.
python -m pip install --require-hashes --only-binary=:all: -r requirements-runtime-lock.txt
python -m pip install --no-build-isolation --no-deps -e .
# (a) Headless-Engine: bedient ausschließlich das authentifizierte /v1
python -m risk_layer.web # http://127.0.0.1:8000/v1/
# Fuer Docker-Zugriff auf eine native Host-Engine gezielt eine Docker-Bridge-IP
# binden, nicht 0.0.0.0. Das Deploy-Skript fragt diesen Fall interaktiv ab.
# (b) CLI: Sachverhalt analysieren → Konsolen-Zusammenfassung
python -m risk_layer.cli analyse examples/beispiel_organschaft.txt
# optional: semantische Vorschläge (BGE-M3), Python 3.12
# Linux:
python -m pip install --require-hashes --only-binary=:all: -r requirements-embedding-linux-cpu-py312-lock.txt
# Windows ohne unterstützte GPU: requirements-embedding-windows-cpu-py312-lock.txt
# Windows 11 + unterstützte AMD Radeon (ROCm 7.2.1):
python -m pip install --no-build-isolation --require-hashes -r requirements-embedding-windows-amd-py312-lock.txt
set RISK_LAYER_EMB_DEVICE=cuda
risk-layer embedding build # einmalig — baut den Korpus-Index (+ Paraphrasen-Anker)
Produktion und optionale Backends werden getrennt gelockt: der schlanke
requirements-runtime-lock.txt enthält keine Testwerkzeuge; Embedding und
Quanten besitzen je einen unter Linux beziehungsweise Windows/Python 3.12
erzeugten Hash-Lock. Die Deploy-Skripte lösen keine ML-/Qiskit-Ranges mehr zur
Installationszeit auf. Unter Windows nutzt das Setup für offiziell unterstützte
AMD-Radeon-GPUs den separaten ROCm-7.2.1/PyTorch-2.9.1-Lock; andere Hosts fallen
auf den CPU-Lock zurück. PyTorch adressiert auch ROCm-Geräte über cuda.
Sicherer Autostart
Service-Prozesse dürfen nicht privilegiert aus dem benutzerschreibbaren Checkout
laufen. Der produktive Standardpfad verwendet den im Betreiberprofil
konfigurierten release-aware Dienst beziehungsweise Windows-Task; dessen
einmalige Einrichtung ist im
technischen Detail-Runbook beschrieben.
Der parameterlose Checkout-Pfad ./signal bootstrap ruft unter Linux intern
das Setup mit Service-Provisionierung auf. In einem root-verwalteten Checkout
unter beispielsweise /opt/signal legt Root die System-Unit, das Secret und
das gesperrte Systemkonto risk-layer an; der Engine-Prozess selbst läuft
unprivilegiert unter diesem Konto. Ein Aufruf als normaler Benutzer erzeugt
stattdessen dessen systemd-User-Unit. Danach steuern ./signal start, stop
und restart automatisch den passenden Scope. Für den User-Unit-Start schon vor
der Anmeldung kann ein Administrator einmalig sudo loginctl enable-linger BENUTZER setzen. Beide Units setzen zusätzlich systemd-Sandboxing.
Unter Windows verwendet der Standardpfad .\signal.ps1 bootstrap und richtet
den begrenzten Benutzer-Task ebenfalls automatisch ein; start, stop und
restart steuern ihn anschließend über dieselbe Oberfläche.
Das interne beziehungsweise kompatible .\deploy\install-service.ps1 installiert
einen sofort gestarteten, auf RunLevel Limited begrenzten Task für den
aktuellen Benutzer;
Autostart erfolgt bei dessen Anmeldung und es wird kein Kennwort gespeichert. Der
Unicode-sichere PowerShell-Wrapper verweigert den Start, wenn .engine_token
fehlt, unlesbar oder kürzer als 32 Zeichen ist. Ein Boot-Dienst aus dem
benutzerschreibbaren Checkout wird absichtlich nicht angeboten. Migration einer
älteren Dienst-/SYSTEM-Installation: zuerst aus einer administrativen PowerShell
.\deploy\install-service.ps1 -Remove, danach als normaler Benutzer den begrenzten Task
neu installieren.
Produktive Releases, Container und Rollback
docker-compose.yml ist ein reines Produktions-Deployment: kein lokaler Build,
kein latest, sondern ein zwingender OCI-Digest sowie ein read-only gemountetes,
manifestiertes Festwissen-Release. Lokale Checkout-Builds verwenden ausdrücklich
docker-compose.dev.yml. Das LLM läuft optional im Compose-Profil llm und ist
keine Startabhängigkeit des deterministischen Engine-Kerns.
Der Forgejo-Workflow veröffentlicht nur aus einem annotierten
vMAJOR.MINOR.PATCH-Tag. Das native Bundle bindet Wheel, gehashte Plattform-Locks,
Offline-Wheelhouses für Linux/Windows, Korpus, Graph, Trigger, Katalog und den
produktiven Embedding-Index einschließlich des am festen Commit manifestierten
lokalen BGE-M3-Modells einzeln an release.json; der lokale
Release-Manager baut daraus je Generation eine Offline-Venv und hält einen
atomaren current/previous-Zustand. Der produktive OCI-SemVer-Tag wird erst
nach allen anderen Uploads gesetzt. Normale Bedienung:
Betriebsübersicht; sichere Extraktion und Recovery:
technisches Detail-Runbook.
Als Bibliothek:
from risk_layer import analyse
report = analyse(open("sachverhalt.txt", encoding="utf-8").read())
print(report.zusammenfassung())
for karte in report.luecken:
print("Lücke:", karte.rechercheauftrag)
/v1-Endpunkte (der Integrationsvertrag, Engine-Version 1.4.0)
GET /v1/health · GET /v1/status · POST /v1/analyse · GET /v1/katalog ·
POST /v1/katalog/definiere · POST /v1/katalog/review · GET /v1/radar ·
GET /v1/normgraph/{aufloesen,suche} ·
POST /v1/embedding/suche · GET /v1/embedding/status ·
POST /v1/embedding/{refresh,cancel,schedule} · GET /v1/llm/status ·
POST /v1/llm/start. Details + Beispiel-Antworten:
INTEGRATION_TAXTRONIK.md und
fixtures/.
curl -s http://127.0.0.1:8000/v1/analyse \
-H "Authorization: Bearer $RISK_LAYER_TOKEN" -H "Content-Type: application/json" \
-d '{"text":"Eine Schätzung der Besteuerungsgrundlagen droht wegen § 42 AO.","mitLLM":false}'
POST /v1/analyse liefert stabile Zeichen-Offsets (spans[].start/end) in den
exakt übergebenen Text + text_hash (= sha256). Die schnelle Phase (mitLLM:false)
ist deterministisch — gleicher Text → gleiche Spans. Darüber rendert das
Host-Overlay. Auth: Bearer-Token (RISK_LAYER_TOKEN), konstant-zeitig geprüft;
ein Nicht-Loopback-Bind ohne Token wird abgelehnt.
Degradations-Transparenz (engine_status, seit 1.2.0): GET /v1/status
liefert einen konsistenten Engine-Snapshot (Schichten aktiv/grund,
Katalogversion, Graph-/Index-Fingerprint, index_aktuell, NER-Verfügbarkeit,
LLM-host:port); jede /v1/analyse-Antwort trägt denselben Block plus die
effektiven Optionen des Laufs. Ein aktivierter Embedding-Index aktualisiert nur
diesen Teil atomar; eine laufende Analyse behält ihren vorherigen Snapshot.
Der Host legt ihn mit dem Ergebnis in seiner
Audit-Chain ab — der Degradations-Nachweis je Analyse (deterministisch, keine
Zeitstempel — bricht keine Hash-Ketten).
Env-Variablen: RISK_LAYER_TOKEN (Pflicht-Auth bei Netz-Bind),
RISK_LAYER_OPERATOR_TOKEN (separates Secret, mindestens 32 UTF-8-Bytes, nur
für mutierende Embedding-Operatorrouten), RISK_LAYER_EMBEDDING_DIR
(beschreibbares persistentes Generationen-/Operatorstate-Overlay; ein
manifestierter corpus/embedding bleibt der Legacy-Fallback),
RISK_LAYER_WEBHOOK_ALLOW (Host[:Port]-Allowlist für den Recherche-Webhook;
außerhalb von Loopback ist HTTPS Pflicht, SSRF-/DNS-Rebinding-Schutz),
RISK_LAYER_EMB_DEVICE (Embedding-Encoder-Gerät, Default cuda),
RISK_LAYER_NER (aus ⇒ Anonymisierung rein regex-basiert, ohne spaCy-NER),
RISK_LAYER_RERANK_SCHWELLE (Abstention-Schwelle des Rerankers, Default 0.10),
RISK_LAYER_LLM_BACKEND (auto|cpu|gpu; cpu erzwingt -ngl 0 und meldet
den erwarteten Performance-Bottleneck an das Host-System),
RISK_LAYER_LLM_TIMEOUT (Timeout der Schicht-2-Generierung in Sekunden;
Auflösung: Env-Variable > Benchmark-Kalibrierung .signal/llm_bench.json >
Default 300).
Timeout-Kalibrierung (risk-layer modell bench): Auf langsamer Hardware
(CPU, schwache GPU) reißt der feste Timeout-Default sonst jede valide, nur
späte Schicht-2-Antwort ab. modell bench misst gedrosselt (halbe Threads,
Prozess-Priorität unter normal — die Maschine bleibt bedienbar) die reale
Prompt- und Generierungs-Geschwindigkeit und persistiert einen daraus
berechneten Timeout (Worst-Case-Budget × Sicherheitsfaktor, nie unter 300)
nach .signal/llm_bench.json. Das Setup (setup.sh/setup.ps1, auch via
./signal configure) bietet den Lauf interaktiv an; im verwalteten
Checkout-Betrieb jederzeit nachholbar per ./signal bench. Läuft bereits ein
llama-server, wird gegen diesen gemessen (kein zweites Modell im RAM);
schafft die Hardware nicht einmal den Messlauf, empfiehlt der Benchmark
explizit, mitLLM deaktiviert zu lassen.
Architektur
Eingabetext
│
▼ INGEST ingest.py Normalisierung (offset-erhaltend), Segmente, Hash
│
▼ SCHICHT 1 matcher.py wörtlicher Katalog-Matcher ◄── catalog/begriffe.yaml
▼ SCHICHT 1b muster.py Beschreibungs-/Negativbefund-Muster (Ko-Okkurrenz je Satz)
▼ SCHICHT 1.5 triggerindex.py korpus-trennscharfe Trigger (IDF) → Norm-Kandidaten
▼ SCHICHT 1.7 embedding.py BGE-M3-Semantik → Norm-VORSCHLÄGE (kein hartes Markieren)
▼ SCHICHT 2 (opt.) schicht2.py LLM-Kandidaten + Span-Verifikation
▼ STREIT muster.py Subsumtions-Brennpunkte markieren + benachbarte Marken verstärken
│
▼ AUFLÖSUNG resolver.py Definition NUR aus Quelle, sonst Lücke; Normketten (Kaskaden)
│
▼ ANTWORT web.py/api_v1.py /v1-JSON: spans, karten, risiken, semantik, summary, text_hash
Trennlinie der Zuständigkeit: Schichten 1/1b/1.5 und die Definitionsauflösung
sind reiner, reproduzierbarer Code — trainingsdaten-unabhängig. Das LLM
(Schicht 2) und die Semantik (1.7) dürfen ausschließlich zeigen/vorschlagen, nie
definieren oder entscheiden — erzwungen durch Span-Gate (schicht2.py),
Existenzprüfung im Graphen und den reinen Vorschlags-Modus der Semantik. Alle
Schichten sind additiv (keine überschreibt die vorige).
| Datei | Verantwortung | Lastenheft |
|---|---|---|
risk_layer/ingest.py |
Normalisierung, Segmentierung, Offset-Map, Hash | /LF10/–/LF30/ |
risk_layer/catalog.py |
Katalog laden + validieren + Versionierung | /LF220/–/LF230/ |
risk_layer/matcher.py |
deterministischer Schicht-1-Matcher (wörtlich) | /LF40/–/LF80/ |
risk_layer/muster.py |
Schicht 1b: Beschreibungs-Muster + Streit-Detektor | Erweiterung |
risk_layer/triggerindex.py |
Schicht 1.5: IDF-Trigger-Index (Begriff+Norm, ohne Modell) | Erweiterung |
risk_layer/embedding.py |
Schicht 1.7: BGE-M3-Semantik-Index, Paraphrasen-Anker, Vorschlags-Modus | Erweiterung |
risk_layer/schicht2.py |
LLM-Kandidaten, Schema/GBNF, Span-Gate | /LF90/–/LF140/ |
risk_layer/resolver.py |
Definitionsauflösung / Lücken | /LF150/–/LF160/ |
risk_layer/pipeline.py |
Orchestrierung aller Schichten → RunReport; AnalyseOptionen = Single Source of Truth der Lauf-Optionen (CLI/Web/API-Parität) |
— |
risk_layer/idschema.py |
kanonisches Knoten-ID-Schema (norm/verw/urteil) | Parser-Spez. 1 |
risk_layer/lawmap.py |
Law-Alias-Map (Longest-Match) + Harvest-Hook | Parser-Spez. 2.4 |
risk_layer/zitate.py |
Norm-/Entscheidungszitat-Parser, Kanonisierung | Parser-Spez. 2/3 |
risk_layer/unknown_gate.py |
„Unknown als Risiko"-Gate + offline-Proposer | Erw. §3 /LF270–310/ |
risk_layer/berater.py |
Berater-Definitionen (personal/geteilt, Lebenszyklus) | Erw. §4 /LF320–330/ |
risk_layer/persistenz.py |
geteilte Store-Primitive (Slug, atomares Schreiben) | — |
risk_layer/web.py |
headless /v1-HTTP-Server (stdlib, Bearer-Auth) |
Integration |
risk_layer/api_v1.py |
stabiler, zustandsloser /v1-Vertrag |
Integration |
risk_layer/cli.py |
Kommandozeile (analyse, build, zitat, abgleich) | Stufe 0 |
risk_layer/report.py |
annotierter HTML-/Text-Report (CLI-Ausgabe) | /LF180/–/LF200/ |
risk_layer/anonymize.py |
lokale Abstraktion/Anonymisierung (§ 203) | Geheimnisschutz |
risk_layer/recherche.py |
Rechercheauftrag-Export (n8n → Cloud-LLM), §203-Re-Check | Geheimnisschutz |
risk_layer/rss.py + abgleich.py |
BFH/BMF-RSS + Abgleich-Schleife | Erw. §5 /LF340–370/ |
risk_layer/radar.py |
Norm-Änderungs-Radar (Manifest-Diff → betroffene Festwissens-Anker) | Re-Review |
risk_layer/llm.py |
Schicht-2-Provider (llama-server / llama-cpp) | Stufe 1 /LF110–140/ |
risk_layer/corpus.py |
gii-Fetcher + Korpus-Config (aus Katalog) | Stufe 2 |
risk_layer/gii.py |
gii-XML → Norm-Knoten | Parser-Spez. 4 |
risk_layer/graph.py |
SQLite-Normgraph + Auflösung + Dangling | Stufe 2 /LF170/ |
catalog/begriffe.yaml |
Single Source of Truth (Schicht 1) | /LF220/ |
Deterministische Erkennung über die wörtliche Schicht hinaus
Reale Sachverhalte benennen die Rechtsbegriffe selten — sie umschreiben sie, oft als Negativbefund. Drei zusätzliche, trainingsdaten-unabhängige Schichten schließen diese Lücke vor dem LLM:
Schicht 1b — Beschreibungs-Muster (muster.py). Ko-Okkurrenz je Satz: ein
Muster trifft, wenn aus jeder geforderten Wortgruppe mindestens ein Glied im
selben Satz vorkommt. „Kasse" allein feuert nicht, „Kasse" + „nicht
nachvollziehbar" schon → § 146/158 AO. Der Streit-Detektor markiert zusätzlich
Subsumtions-Brennpunkte („streitig", „hätte … vermeiden müssen", „nachlässig") und
verstärkt die im selben Satz liegenden Begriffsmarken — die FF/IN-Stellen, an
denen die Subsumtion kippt.
Schicht 1.5 — Trigger-Index (triggerindex.py). Über den gesamten Korpus
vorberechnete, IDF-gewichtete Trigger-Wörter → Norm-Kandidaten. Trennscharf
(„Kassenführung" ≠ „Unterstützungskasse"), kuratierbar (geprueft-Flag), ohne
Modell.
risk-layer trigger build # baut den Index aus dem Normgraph
risk-layer trigger lookup "Bareinnahmen fehlen, die Kasse ist nicht nachvollziehbar"
Schicht 1.7 — Semantik / Embedding (embedding.py). BGE-M3 bettet den
Gesetzeskorpus sowie kuratierte Anker ein und findet Paraphrasen ohne geteiltes
Fachwort. Die Semantik liefert Norm-Vorschläge, keine harten Markierungen.
Gemessen gibt es keinen robusten globalen Schwellwert für Auto-Markierungen.
Cards / Anker als Evidenz. Kuratierte Paraphrasen- und Tatbestand-Cards werden als Evidenz für eine Norm geführt. Breite Paragraphen-Anker können bei wachsendem Bestand diffuse Verdrängung erzeugen; für breite Schema-Normen sind deshalb tatbestand- bzw. absatznahe Cards vorzuziehen. Das Modell liefert nur Vektoren; verglichen wird gegen den Korpus — nie Modellgedächtnis.
Scope+Rerank. Der Reranker ist nicht als Reparatur eines beliebig großen Flat-Index gedacht. Der Zielpfad ist scope-aware: Rechtsraum bestimmen, Kandidaten im Scope bilden, dann Reranker und Norm-Aggregation. Oracle-Scope-Läufe zeigen deutlich bessere Stabilität gegen cross-law-Verdrängung; deploybare Scope-Quellen sind Host-Workflow, Beraterauswahl oder später ein lokaler Klassifikator.
risk-layer embedding build # baut den Index (+ Katalog-Paraphrasen-Anker)
risk-layer embedding suche "Der Chef hat die Lohnsteuer trotz Geld nicht abgeführt"
Im eingerichteten lokalen Checkout ist der bevorzugte Betreiberpfad
./signal embedding-refresh beziehungsweise
.\signal.ps1 embedding-refresh. Er baut den BGE-M3-Index isoliert, promotet
ihn atomar und bestätigt die Live-Aktivierung nach einem blockierenden
Dienstneustart. Bei fehlgeschlagener Readiness werden alter Disk-Stand und
Dienst automatisch wiederhergestellt; --no-restart verschiebt die
Aktivierung bis zum nächsten Dienststart. Fehlt die optionale Embedding-Laufzeit, wird sie
einmalig mit ./signal embedding-refresh --install-dependencies aus dem
plattformgebundenen CPU-Hashlock installiert. Profilierte
Produktionsinstanzen halten Graph, Katalog und Modell weiterhin unveränderlich;
die Laufzeit-API kann daraus ausschließlich ein getrenntes regenerierbares
Index-Overlay erzeugen. Setup und Dienststart lassen den Operatorplan
standardmäßig ausgeschaltet und starten keinen Neuaufbau. Ein laufender
API-Auftrag kann mit seiner Job-ID über POST /v1/embedding/cancel kooperativ
zwischen Modell-Batches beendet werden; eine Teilgeneration wird nie aktiviert.
Der Index trägt den Korpus- und Katalogstand sowie seine eindeutige
Buildinput-Identität (graph_fingerprint, catalog_version,
catalog_fingerprint und input_fingerprint in
meta.json). Werden Normgraph oder Katalog danach geändert, deckt
pruefe_aktualitaet
den veralteten Index beim Start auf (Warnung im Log; maschinell sichtbar
als index_aktuell im engine_status) — Abhilfe: embedding build erneut.
Der Index führt die Paragraph-Ebene; tatbestand-/absatznahe Cards für breite Schema-Normen rollen per
para_levelauf ihre §-Zielnorm hoch (📌 s. FEATURES.md §3). Ein lautes Audit meldet beim Build jeden toten Anker (Paraphrase ohne Index-Ziel).
Normketten — Fundstellen als Kaskaden. Eine Fundstelle ist oft eine Kette („§ 14 KStG iVm § 2 Abs. 2 GewStG"). Treffer und Vorschläge liefern die ganze Kaskade — der GewSt-Durchschlag wird sichtbar, nicht nur die Spitze.
Der Begriffskatalog
catalog/begriffe.yaml ist eine versionierte Datendatei, getrennt vom Code
(/LF220/, /NF50/). Eine Katalogänderung erfordert keinen Code-Deploy; sie
wird über Git nachvollzogen (/LF230/). Jede Auswertung führt die verwendete
Katalogversion in der Antwort mit (katalog_version).
- id: kst_finanzielle_eingliederung
begriff_kanonisch: "finanzielle Eingliederung"
aliase: ["Mehrheit der Stimmrechte", "Stimmrechtsmehrheit am Organträger"]
norm_anker: ["§ 14 Abs. 1 Satz 1 Nr. 1 KStG"]
# Fundstelle als KETTE (Kaskade) — der Durchschlag auf die GewSt:
normketten:
- glieder: ["§ 14 KStG", "§ 2 Abs. 2 Satz 2 GewStG"]
verknuepfung: "iVm"
hinweis: "KSt-Organschaft schlägt über § 2 GewStG auf die GewSt durch."
# Beschreibungs-Muster (Schicht 1b): trifft, wenn aus JEDER Gruppe ein Glied im Satz steht
ausloeser:
- gruppen:
- ["mutter", "konzern", "anteilsmehrheit", "stimmenmehrheit"]
- ["beherrscht", "durchregier", "wie eine einheit"]
# Laien-Umschreibungen (Schicht 1.7): werden als Anker mit-eingebettet
paraphrasen:
- "Die Konzernmutter hält die Stimmenmehrheit und kann in der Tochter durchregieren."
definitionsquelle:
- { typ: "Verwaltung", fundstelle: "R 14.2 KStR" }
governance_typ: "FP" # FP formal-periodisch | FF fortlaufend-faktisch | IN inhärent
schadensintensitaet: "hoch" # niedrig | mittel | hoch
kaskadenreichweite: 2 # 1 | 2 | 3
gefaehrdungsnotiz: "Muss vom Beginn des Wirtschaftsjahrs an ununterbrochen bestehen. …"
norm_anker bleibt die flache Liste aller berührten Normen. normketten,
ausloeser und paraphrasen sind optional und additiv — fehlen sie, verhält
sich der Eintrag wie bisher (rückwärtskompatibel).
⚠️ Katalogpflege ist der kritische Pfad (/R10/). Eine nicht eingetragene Entscheidung ist eine unsichtbare Lücke. Norm-Anker und Fundstellen des Seed-Katalogs sind vor produktivem Einsatz vom Berufsträger zu verifizieren.
Ein Eintrag ohne definitionsquelle wird bewusst als Lücke
(Rechercheauftrag) geführt — niemals wird eine Definition erfunden (/LF160/).
Kanonische IDs & Zitat-Parser (das universelle Join)
Der Zitat-Parser hebt jedes Norm-, Verwaltungs- und Entscheidungszitat auf eine kanonische Knoten-ID — die eine Komponente, die Katalog, Gesetze, Verwaltung und Urteile zusammenhält. Reiner Code, ohne Gesetzeskorpus lauffähig.
python -m risk_layer.cli zitat "§ 2 Abs. 2 Nr. 2 UStG"
# norm:UStG:2:abs2:nr2 konfidenz=hoch
python -m risk_layer.cli zitat "§§ 14, 17 KStG" # Liste → mehrere Anker
python -m risk_layer.cli zitat "§ 14 Abs. 1 Nr. 1 bis 3 KStG" # Bereich expandiert
python -m risk_layer.cli zitat --urteil "BFH v. 18.1.2023 – XI R 29/22, BStBl. II 2023, 409"
# urteil:BFH:XI-R-29/22 (Fundstelle als Alias)
| Zitat | Kanonische ID |
|---|---|
§ 2 Abs. 2 Nr. 2 UStG |
norm:UStG:2:abs2:nr2 |
§ 14 Abs. 1 Nr. 3 Satz 4 KStG |
norm:KStG:14:abs1:nr3:s4 |
Art. 11 MwStSystRL |
norm:MwStSystRL:art11 |
Abschn. 2.8 … UStAE |
verw:UStAE:2.8 |
BFH v. 18.1.2023 – XI R 29/22 |
urteil:BFH:XI-R-29/22 |
Listen/Bereiche/ff. werden expandiert bzw. als offener Bereich geflaggt;
relative Verweise („Satz 1") werden bewusst als Risiko markiert (relativ,
niedrige Konfidenz) statt geraten. Nicht eindeutig parsebare Fundstellen liefern
bewusst keine ID (null) — niemals geraten.
„Unknown als Risiko" & Berater-Definitionen (Erweiterung)
Default-Umkehr: nicht „kenne ich nicht → Schweigen", sondern „kann ich nicht
sauber auflösen → Risiko". Zustände je Textstelle: treffer · luecke ·
unknown_risiko · berater_definiert · verworfen.
Das Unknown-Gate kombiniert Signale (nie nur eines): Norm-Nähe (nutzt den Zitat-Scanner), definitorische Form („im Sinne des", „setzt voraus, dass" …), Negativfilter (Stoppwort/Named Entity/Gesetzeskürzel → Veto), Schwellenlogik. Es läuft offline ohne LLM; ein LLM-Signal ist additiv. Recall-first: im Zweifel eskalieren.
# eskaliert nicht-katalogisierte, juristisch aufgeladene Begriffe (offline)
python -m risk_layer.cli analyse sachverhalt.txt --unknown
# Define-on-the-fly: der Katalog wächst durch Nutzung (CLI oder POST /v1/katalog/definiere)
python -m risk_layer.cli definiere "Betriebsaufspaltung" \
--text "Aufteilung in Besitz- und Betriebsunternehmen mit Verflechtung." \
--berater meine_defs --autor rey --norm "§ 15 EStG"
Berater-Definitionen trennen zwei Ebenen hart (Haftung): scope=personal
(nur Autor, frei) und scope=geteilt (für andere; erst nach Freigabe kanonisch,
Lebenszyklus entwurf → geprüft → freigegeben). Über /v1/katalog/definiere
wird vor dem Schreiben gegen identifizierende Angaben geprüft (§ 203) — der
geteilte Katalog trägt kein Geheimnis.
Review-Lebenszyklus über /v1: Statuswechsel geteilter Einträge laufen über
POST /v1/katalog/review (nur Vorwärts-Übergänge; die Antwort nennt
alter_status/neuer_status/pruefer), damit das Host-System die Freigabe in
seiner Audit-Chain protokolliert — die Engine auditiert nicht. Das
CLI-Kommando definiere ist als Nicht-Audit-Pfad gekennzeichnet (lauter
stderr-Hinweis): Operator-Werkzeug, in Produktion gehört der Write durch /v1.
Altbestände aus Versionen ohne hartes Vier-Augen-Gate werden beim Laden einzeln
auf entwurf zurückgestuft, nie automatisch freigegeben und mit Grund sowie
früherem Status in geteilt.yaml markiert. GET /v1/status weist diese
Migrationen unter festwissen_status.review_migrationen pro ID aus; jeder
quarantänisierte Eintrag muss den regulären Review erneut vollständig durchlaufen.
Stufe 2 — Normgraph aus gesetze-im-internet
Der Korpus ist lokal die Quelle der Wahrheit; zur Laufzeit wird nie ins Netz gegriffen. Das Befüllen ist ein getrennter Build-Schritt:
risk-layer korpus init --voll # breiter Steuer-Korpus (~21 Gesetze; ohne --voll nur Katalog-Gesetze)
risk-layer korpus fetch # lädt die xml.zip von gesetze-im-internet.de (einmalig, online)
risk-layer graph build # baut den SQLite-Normgraph (+ rotiert das Norm-Manifest fürs Radar)
risk-layer graph aufloesen "norm:UStG:2:abs2:nr2"
risk-layer graph dangling # Verweise auf fehlende Knoten (/T50/, sichtbares Negativwissen)
risk-layer radar # geänderte/entfallene Normen + betroffene Festwissens-Anker
# (--json maschinenlesbar, --fail-bei-befund für Skripte/CI)
Liegt ein Graph unter corpus/graph.sqlite, hängt die Analyse jedem Treffer den
Gesetzeswortlaut aus dem Korpus an die Karte (/LF170/) — Quelle ist die XML,
niemals das Modell. Granularität §/Absatz/Nummer; Fassungslogik über
gueltig_ab/gueltig_bis (/R30/).
Norm-Änderungs-Radar — Re-Review nach Korpus-Rebuild
Freigegebene Berater-Definitionen veralten sonst still, wenn der Gesetzgeber
einen verankerten Paragraphen ändert — der Review-Lebenszyklus kennt nur
Vorwärts-Schritte. Jeder graph build rotiert deshalb das Norm-Manifest
(norm_manifest.json → norm_manifest.vorher.json); risk-layer radar bzw.
GET /v1/radar vergleichen die Stände und benennen dringlichkeitssortiert
die betroffenen Festwissens-Anker (freigegebene geteilte Definitionen zuerst,
dann Kuratierungen und Katalog-Anker). Die Engine berichtet nur — die
Review-Aufgaben erzeugt das Host-System und protokolliert sie in seiner
Audit-Chain. Eine automatische Status-Rücksetzung gibt es bewusst nicht
(Nur-vorwärts-Invariante): ob die Gesetzesänderung die Definition überhaupt
berührt, entscheidet der Berufsträger (§§ 33, 57 StBerG).
Stufe 1 — lokales LLM (Schicht 2)
Das LLM ist nur ein zusätzliches Signal; die Sicherheit liegt im Code:
Constrained Decoding (JSON-Schema/GBNF) und Span-Verifikation. Jeder
LLM-Kandidat durchläuft danach das Unknown-Gate. Im /v1-Vertrag ist Schicht 2
opt-in (mitLLM:true); die Engine verbindet sich dann zum konfigurierten
--llm-url (server-seitig, nicht vom Aufrufer wählbar — kein SSRF).
risk-layer modell list # Empfehlungen (Default: Granite-4.1-8B Q5_K_M)
risk-layer modell download # lädt das GGUF (resumebar) nach ./models
risk-layer modell engine --backend auto # lädt die fest gepinnte llama.cpp-Engine
risk-layer modell start # startet llama-server (Vulkan, alle Layer auf GPU)
risk-layer analyse sachverhalt.txt --unknown # --no-schicht2 schaltet ab
Ist kein Server erreichbar, läuft die Analyse normal ohne Kandidaten weiter. Modell, Quantisierung, Seed und Temperatur (=0) werden im Provenienzfeld des Reports geführt (das Host-System schreibt daraus sein Audit, /LF140/).
Engine-Supply-Chain: Der automatische Engine-Download unterstützt Windows
x64 und Ubuntu/Linux x64. Er verwendet ausschließlich den im Repository
festgelegten llama.cpp-Release b9986; Assetname, Größe und SHA-256 stehen in
risk_layer/llama_cpp_manifest.py. Es gibt keine Abfrage von latest und keinen
Trust-on-first-use: Ein Archiv wird vor dem Entpacken hart gegen den gepinnten
Digest geprüft und bei Abweichung gelöscht. Andere Plattformen brechen
geschlossen ab. Ein selbst gebautes bzw. separat verwaltetes Binary bleibt per
--llm-bin PFAD oder PATH ausdrücklich nutzbar.
Kontrollierter Engine-Updateprozess:
- Einen konkreten Tag auf der offiziellen llama.cpp-Release-Seite
auswählen und Release/Commit prüfen; niemals eine
latest-URL eintragen. - Die Assetnamen, Größen und
sha256:-Digests über die offizielle GitHub-API für genau diesen Tag (/repos/ggml-org/llama.cpp/releases/tags/<TAG>) holen. Assets ohne SHA-256-Digest werden nicht aufgenommen. - Jedes aufzunehmende Asset von seiner offiziellen Release-URL laden, den
SHA-256 lokal (
sha256sumbzw.Get-FileHash -Algorithm SHA256) neu berechnen und mit dem API-Digest vergleichen. - Tag und Manifest gemeinsam reviewen; danach mindestens
pytest -q tests/test_modell.py,ruff check risk_layer/modell.pyundmypyausführen. Erst dieser Commit autorisiert den neuen Engine-Stand.
Steuerung über /v1: Das Host-System startet den llama-server per
POST /v1/llm/start (idempotent, blockiert nicht — Bereitschaft pollen) und prüft
ihn mit GET /v1/llm/status. Der Status liefert neben der Erreichbarkeit auch die
Queue — aktive/freie Slots bzw. processing/deferred des llama-server —, damit der
Host entscheidet, ob er mitLLM:true jetzt sendet oder wartet. Modell/Binary kommen
aus der Engine-Config (kein Request-Input → kein Injection-Vektor). Ein über
/v1/llm/start gestarteter llama-server bekommt --slots --metrics automatisch; ein
manuell gestarteter braucht diese Flags, sonst bleibt queue null (kein Fehler).
Zusätzlich weist backend=cpu den voll funktionsfähigen, aber erwartbar langsamen
CPU-Pfad als performance_bottleneck=true aus.
LLM-Norm-Einordnung (Vorschlag, kein Mapping): Zu einer erkannten Stelle darf
das Modell eine einschlägige Norm vermuten (norm_vorschlag). Der Normgraph
prüft dann ausschließlich die Existenz des Paragraphen (✓ im Korpus / nicht
verifizierbar) — es wird nie fest gemappt.
Geheimnisschutz — Rechercheauftrag an ein externes LLM (§ 203 StGB)
Markierte Stellen lassen sich an ein großes (Cloud-)LLM weitergeben — aber nur
abstrahiert. Der Ablauf (in anonymize.py + recherche.py, als Bibliothek/CLI
nutzbar) erzwingt den Geheimnisschutz:
- Lokal abstrahieren (
anonymize.py): Firmen, Personen, Beträge, Daten, Steuer-/Konto-Nummern → stabile Platzhalter ([Gesellschaft A]…). Die Rechtsbegriffe bleiben. Die Rückübersetzungstabelle bleibt lokal. - Mensch prüft & schwärzt nach (Pflicht).
- Export nur nach Re-Check: findet die Abstraktion noch Identifizierendes,
wird der Export verweigert (ohne bewusstes Erzwingen). Hinaus geht nur der
abstrahierte Text + die Rechtsbegriffe + die Frage. Der Webhook-Versand ist
default-deny (nur Hosts aus
RISK_LAYER_WEBHOOK_ALLOW, keine Redirects, außerhalb Loopback nur HTTPS); ohne erfolgreich gelaufene lokale NER bleibt der lokale Download möglich, automatischer Webhook-Versand aber gesperrt; ein identifizierender Restbefund geht selbst mitforcenie automatisch hinaus (forceerlaubt dann nur den lokalen Download). Die Rückübersetzung wird nie mitexportiert.
Rechtsprechungs-Monitor — BFH/BMF-Abgleich (Erweiterung §5)
risk-layer abgleich zieht die RSS-Feeds von BFH und BMF, extrahiert über
den Zitat-Parser die berührten Normen und konfrontiert sie mit den im Katalog /
in Berater-Definitionen verankerten Begriffen. Dockt eine neue Entscheidung an
denselben Norm-Anker an, wird der Begriff zur Prüfung vorgelegt — konfrontieren,
nicht entscheiden (/LF370/). Nur der RSS-Abruf geht ins Netz; bereits gesehene
Einträge werden gemerkt.
Sicherheits- und Reproduzierbarkeitsgarantien
| Garantie | Mechanismus | Test |
|---|---|---|
| Schicht-1-Recall = 100 % auf dem Katalog | deterministisches Matching je Oberfläche | test_ab10_* |
| keine erfundenen Treffer | jeder Treffer trägt Katalog-ID + wörtlichen Beleg | test_ab20_* |
| Span-Treue = 100 % (Schicht 2) | Pflicht-Gate verifiziere_span |
test_ab30_* |
| Definition nie aus dem Modell | Resolver greift nur auf Katalogquellen zu | test_ab40_* |
| bit-identische Reproduzierbarkeit | rein deterministische Schicht 1 (gleiche /v1-Antwort) |
test_ab50_* |
| Versions-/Provenienz-Vollständigkeit | Hash + Katalogversion + Schicht-2-Info je Lauf | test_ab60_* |
| Netz-Bind nur authentifiziert | Bearer-Token (hmac.compare_digest); kein Token ⇒ nur Loopback |
test_web_surface |
| kein Pre-Auth-/Body-DoS | Auth vor Body-Read + Body-Cap (8 MiB) + Socket-Timeout | test_web_surface |
| kein Webhook-SSRF | webhook_url default-deny (RISK_LAYER_WEBHOOK_ALLOW), keine Redirects |
test_recherche |
| trainingsdaten-unabhängig | Erkennung ruht auf Korpus/Katalog, nicht Modellwissen | — |
python -m pip install --require-hashes --only-binary=:all: -r requirements-lock.txt
python -m pip install --no-build-isolation --no-deps -e .
python -m pytest -q # vollständige Suite: /AB10/–/AB60/, Parser /T10/–/T40/, /v1-Vertrag +
# Surface/Auth + engine_status, Engine-Analysevertrag,
# AnalyseOptionen, Graph, LLM-Mock + Norm-Vorschlag,
# Muster/Streit, Trigger, Embedding/Paraphrasen-Anker +
# Staleness, Anonymisierung (inkl. NER), Rechercheauftrag
# (SSRF-Allowlist), RSS/Abgleich, Radar, Review, AO
Roadmap
- Erkennungs-Kern — ✅ umgesetzt: Schicht 1 (wörtlich) + 1b (Muster/Streit) +
1.5 (Trigger) + Normketten — deterministisch, ohne LLM, mit den passenden
Norm-Ankern. Vollständig in Pipeline,
/v1, CLI und Tests. - Normgraph (Stufe 2) — ✅ umgesetzt: kanonisches ID-Schema + Zitat-Parser,
gii-Fetcher/-Ingest, SQLite-Normgraph mit
verweist_auf-Kanten + Dangling-Report, Gesetzeswortlaut in den Karten, Fassungslogik-Hook. Reife-Reste: Verwaltungs-/ Urteilskorpus + typisiertewird_ausgelegt_durch-Kanten, Satz-Granularität, Kontext-Stack für relative Verweise. - LLM (Stufe 1) — ✅ umgesetzt: JSON-Schema/GBNF + Span-Gate + Provider (llama-server). Offen: GGUF-Modell auf der Maschine bereitstellen.
- Integration (
/v1) — ✅ umgesetzt: stabiler, zustandsloser Vertrag, Bearer-Auth, headless (kein GUI/Persist/Audit in der Engine — das übernimmt das Host-System). Siehe INTEGRATION_TAXTRONIK.md. - Unknown-als-Risiko + Berater-Definitionen — ✅ umgesetzt (Zustandsmodell,
offline-Gate, Lebenszyklus inkl.
POST /v1/katalog/review, define-on-the-fly) inkl. Abgleich-Schleife (BFH/BMF) und Norm-Änderungs-Radar (Re-Review-Trigger nach Korpus-Rebuild, CLI +GET /v1/radar). - Semantik (Schicht 1.7/1.7b) — 🟡 umgesetzt mit gemessener Architekturgrenze: BGE-M3 + Cards + Reranker liefern Norm-Vorschläge. Der globale Flat-Index ist nicht der finale Produktivpfad; Zielarchitektur ist Scope+Rerank. Tatbestand-granulare Cards heben bekannte Bedarfsfälle hub-bias-frei, sind aber nicht als Generalisierungsbeweis zu verkaufen. Offen (📌): Scope-Quelle im Produkt, Intra-Law-Kollateral bei dichten Card-Beständen und Generalisierung auf ungesehene Gold-Sets. Details: FEATURES.md.
Optionale Robustheit: spaCy-Lemmatisierung (/LF50/)
Der Matcher ist ohne spaCy bereits morphologie-tolerant. Das Deploy-Skript
installiert spaCy + de_core_news_lg standardmäßig mit (Opt-out: --ohne-ner
bzw. -OhneNer); manueller Fallback: python -m pip install --require-hashes --only-binary=:all: -r requirements-ner-lock.txt — ohne das deterministische
Verhalten zu ändern.
Datenschutz (/NF10/, /NF60/)
Sämtliche Verarbeitung läuft lokal, ohne Netzaufruf zur Laufzeit (Ausnahme:
der bewusst freigegebene, abstrahierte Recherche-Export und der RSS-Abgleich). Es
werden keine Mandantendaten an externe Dienste gesendet (§ 203 StGB, AVV). Die
/v1-Engine ist zustandslos — sie persistiert keine Mandantendaten; Speicherung
und Audit liegen beim Host-System. CLI-Ausgaben (out/, *.report.html) und
Secrets (.engine_token, .operator_token, .env) sind in .gitignore und
im Docker-Buildkontext zusaetzlich durch .dockerignore ausgenommen.
Weitere Dokumentation
Lebende Dokumente liegen im Repo-Root (dieses README,
FEATURES.md, INTEGRATION_TAXTRONIK.md
und CHANGELOG.md);
eingefrorene Anforderungs- und Entscheidungsdokumente (Lastenheft,
Spezifikationen, ADRs) sowie Planungsdokumente liegen unter docs/. Der
vollständige Index aller Dokumente mit Zweck und Stand:
docs/README.md.
Für Betrieb, Update, Backup und Restore beginnt die Dokumentation unter docs/operations/README.md.