docs/novaberg-pattern-crud-hardening.md
Rama master · réplica de lectura del repositorio original.
# Novaberg — Pattern: CRUD-Haertung (Gehaertete Agent-Transaktionen)
**Projekt:** Novaberg — The Nova Anima Resonance System
**Dokument:** Pattern — Transaktionssicherheit fuer Agent-CRUD-Operationen
**Stand:** 17. April 2026, Chat 52 (Code-Alignment)
**Pfad:** `novaberg/docs/novaberg-pattern-crud-hardening.md`
**Quellen:** nova-14-k.md (allgemeine Abschnitte)
---
## 1. Problem
Agenten arbeiten nach Fire-and-Forget: Der Classify-Node bestimmt eine CRUD-Aktion, die CRUD-Funktion fuehrt sie aus, der Bestaetigungs-Node meldet Erfolg. Zwischen Klassifikation und Ausfuehrung findet keine Validierung statt.
**Beobachtete Fehlerklassen:**
- Halluzinierte Aktionen (Bestaetigung gemeldet, DB nicht geaendert)
- Falsche Klassifikation ("wiederherstellen" → create statt reactivate)
- Schreiben ohne Bestaetigungsmechanismus (unbemerkte Degradierung)
- Konversation als Direktive interpretiert
Ursache: Kein Pruefen des DB-Zustands vor der Aktion, kein Verifizieren nach der Aktion, kein Gate fuer sensible Operationen.
---
## 2. Loesung — Vier-Phasen-Transaktions-Pattern
Jede Agent-Aktion wird eine geprueft Transaktion:
```
ERKENNEN ──> VALIDIEREN ──> AUSFUEHREN ──> VERIFIZIEREN
(LLM+Py) (Python) (Python) (Python)
|
Bei Fehler:
→ Rueckfrage
→ Auto-Korrektur
→ Retry an LLM
|
BESTAETIGUNG ← nur bei sensiblen
(Rueckfrage) Agenten
```
Fundierung durch Industriestandards: Pre-Execution Guard (Pydantic AI), HITL-Gate (OWASP/Microsoft Agent Safety), Full-Lifecycle-Verifikation (CircleCI).
---
## 3. Phase 1: ERKENNEN
Dreistufige Erkennung im Classify-Node:
### Stufe A — Statische Keyword-Hints (Python, vor dem LLM-Call)
```python
KEYWORD_HINTS = {
"wiederherstellen|wieder her|reaktivieren|zurückholen": "reactivate",
"rückgängig|undo|zurücknehmen": "reactivate",
"lösch|entfern|weg damit|streich .*komplett": "delete",
"füge.*hinzu|hinzufügen|aufnehmen|nimm.*auf|ergänz": "add_content",
"streich|nimm.*raus|entferne.*von|runter von": "remove_content",
"leere|alles löschen|komplett leeren": "clear_content",
"ändere|ändern|aktualisier|korrigier": "update",
"verschieb|verleg": "reschedule",
"zeig|was hast|welche|liste|auflisten": "read",
}
```
### Stufe B — Lernende Verb-Mappings (PostgreSQL, pro User)
Bei umgangssprachlichen Ausdruecken ("hau rein", "pack drauf", "schmeiss weg"), die nicht in den statischen Keywords stehen, lernt Nova die Zuordnung durch Bestaetigung. Ab `konfidenz >= 3` (dreimal bestaetigt) wird der Ausdruck wie ein eingebautes Keyword behandelt.
### Stufe C — LLM-Klassifikation mit [ERKENNUNGSHILFE]-Block
Das LLM bekommt statische Hints UND gelernte Verb-Mappings als Kontext:
```
[ERKENNUNGSHILFE]
Schluesselwoerter und gelernte Ausdruecke im Text deuten auf folgende Aktionen:
- create (Gelernt: "hau rein", Konfidenz: 3)
- reactivate (Schluesselwort: "wiederherstellen")
Diese Hinweise sind NICHT bindend, aber beruecksichtige sie.
```
---
## 4. Phase 2: VALIDIEREN
Python prueft die LLM-Klassifikation deterministisch gegen den aktuellen DB-Zustand:
| Aktion | Pruefung | Bei Fehler |
|--------|---------|------------|
| create | Existiert ein aktiver Eintrag mit aehnlichem Inhalt? | → Rueckfrage: "Existiert bereits. Aktualisieren?" |
| create | Existiert ein inaktiver Eintrag? | → Auto-Korrektur zu `reactivate` |
| delete | Existiert das Target? Ist es aktiv? | → Fehler: "Nichts gefunden" |
| delete | Mehrere Treffer? | → Rueckfrage: "Welche meinst du?" |
| update | Existiert das Target? | → Fehler oder create-Vorschlag |
| reactivate | Existiert ein inaktiver Eintrag? | → Fehler: "Nichts zum Wiederherstellen" |
| add_content | Existiert der Container? | → create falls nicht |
| remove_content | Enthaelt der Container das Element? | → Fehler: "Element nicht gefunden" |
**HITL-Gate:** Fuer sensible Agenten (Direktiven, Charakter) ist eine Pflicht-Rueckfrage vor Ausfuehrung erforderlich. Technisch ueber den bestehenden `interrupt()`/Resume-Flow. Ziel: Rueckfrage-Pflicht wieder entfernen, sobald Validierung stabil genug ist.
---
## 5. Phase 3: AUSFUEHREN
Die eigentliche DB-Operation mit Vorher/Nachher-Snapshot:
- **Vorher-Snapshot:** Vor jeder Schreiboperation liest die CRUD-Funktion den aktuellen Zustand
- **Ausfuehrung:** CRUD-Operation (INSERT, UPDATE, Soft-DELETE)
- **Nachher-Snapshot:** Frischer DB-Read nach der Operation
Der Snapshot ermoeglicht Verifikation und potentielles Rollback.
---
## 6. Phase 4: VERIFIZIEREN
Liest den DB-Zustand und vergleicht mit dem erwarteten Ergebnis:
- Nach delete: Eintrag wirklich `aktiv=FALSE`?
- Nach update: Text wirklich geaendert? Neuer Wert == erwarteter Wert?
- Nach create: Neuer Eintrag existiert mit erwarteten Feldern?
- Nach reactivate: Eintrag wieder `aktiv=TRUE`?
~~Bei Fehler: `CrudErgebnis.erfolg` wird auf `False` korrigiert.~~ → **widerlegt am 16.09.2026, behoben am 18.09.2026:** `CrudErgebnis` hatte keinen einzigen Importeur, und der Dienst meldete trotz gescheiterter Verifikation `abgeschlossen` (`DIENST-MELDET-ABGESCHLOSSEN-OHNE-VERIFIKATION`). Seither laeuft jede verifizierende Rueckgabe durch `agents/write_outcome.py::verified_outcome`: nicht bestaetigt heisst Status `fehler` mit Begruendung und ohne Zusage.
---
## 7. Konfidenz-Berechnung
| Situation | Konfidenz | Aktion |
|-----------|-----------|--------|
| Keyword-Hint + LLM stimmen ueberein | hoch | Direkt validieren |
| Verb-Mapping (>=3) + LLM stimmen ueberein | hoch | Direkt validieren |
| Verb-Mapping (<3) + LLM stimmen ueberein | mittel | Direkt validieren, Konfidenz++ |
| Nur LLM, kein Hint | niedrig | Rueckfrage + ggf. Verb lernen |
| Hint und LLM widersprechen sich | konflikt | Rueckfrage |
---
## 8. Verb-Mappings
Lernende Sprachadaption auf der Aktionsebene. Nova lernt, WAS der User mit bestimmten Ausdruecken MEINT — Dialekt und Slang, den kein vorgefertigtes Woerterbuch abdeckt.
```sql
CREATE TABLE IF NOT EXISTS verb_mappings (
id SERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
ausdruck TEXT NOT NULL,
aktion TEXT NOT NULL,
agent TEXT NOT NULL,
konfidenz INTEGER NOT NULL DEFAULT 1,
erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, ausdruck, agent)
);
```
**UPDATE Chat 44:** Mit der Einfuehrung der Domain-Language-Normalisierung ([FACHSPRACHE]-Block) verschieben sich Verb-Mappings zu sekundaerer Konfidenz-Pruefung. Die Domain Language uebernimmt die primaere Erkennung. Verb-Mappings und Keywords dienen als deterministische Gegenprobe zur LLM-Klassifikation.
---
## 9. Datenstrukturen
### KlassifikationsErgebnis (Output des Classify-Node)
```python
@dataclass
class KlassifikationsErgebnis:
aktion: str # aus erweiterter Taxonomie
target: str | None # worauf sich die Aktion bezieht
slots: dict # agent-spezifische Felder
keyword_hints: list[str] # aus Stufe A (statisch) + Stufe B (gelernt)
konfidenz: str # "hoch", "mittel", "niedrig", "konflikt"
```
### ValidationResult
```python
@dataclass
class ValidationResult:
ok: bool
korrektur: str | None # auto-korrigierte Aktion
grund: str # Erklaerung fuer Logs/Rueckfrage
bestaetigung_noetig: bool # HITL-Gate
bestaetigung_text: str | None # "Soll ich X machen?"
```
### CrudErgebnis (mit Vorher/Nachher-Snapshot)
```python
@dataclass
class CrudErgebnis:
erfolg: bool
aktion: str # tatsaechlich ausgefuehrte Aktion
vorher: dict | None # DB-Zustand vor der Aenderung
nachher: dict | None # DB-Zustand nach der Aenderung (frischer DB-Read)
verifiziert: bool # nachher == erwartet?
meldung: str # Menschenlesbare Beschreibung
```
---
## 10. Gemeinsame Infrastruktur
**Datei:** `agents/crud_validation.py`
Enthaelt die gemeinsamen Datenstrukturen (`KlassifikationsErgebnis`, `ValidationResult`, `CrudErgebnis`) sowie die Hilfsfunktionen:
- `keyword_hints_ermitteln(text)` — Stufe A: statische Regex-Keywords
- `verb_mappings_laden(user_id, agent)` — gelernte Verb-Mappings aus PostgreSQL
- `verb_mapping_pruefen(text, user_id, agent)` — Stufe B: dynamische Mapping-Prüfung
- `verb_mapping_lernen(user_id, agent, ausdruck, aktion)` — Konfidenz++ / Insert
- `konfidenz_berechnen(...)` — hoch/mittel/niedrig/konflikt
- `erkennungshilfe_block(...)` — baut den `[ERKENNUNGSHILFE]`-Prompt-Block
Validierung ist keine Agent-Eigenschaft, sondern ein Service. Agenten ohne CRUD (Decay, Recherche) brauchen keine Validierung. `crud_validation.py` ist ein Import, keine Basisklasse.
**Verzeichnisstruktur (Stand Chat 52):**
```
agents/
+-- crud_validation.py # Gemeinsame Datenstrukturen + Hilfsfunktionen
+-- direktiven/
| +-- klassifikation.py # Classify-Node mit Hints + [FACHSPRACHE]
| +-- crud.py # CRUD + inline `validieren_gegen_db()`
| +-- agent.py # LangGraph-Subgraph mit `_db_validieren`-Node
+-- charakter_identitaet/
| +-- klassifikation.py
| +-- crud.py # crud.py enthaelt die validieren_gegen_db-Funktion
| +-- agent.py
+-- notizen/, timeline/
| +-- (analoges Muster, Validation inline in crud.py bzw. agent._db_validieren)
```
Es gibt keine separaten `validation.py`-Dateien in den Agent-Ordnern. Die agent-spezifischen Validierungsregeln leben in `crud.py` (Funktion `validieren_gegen_db()`) bzw. direkt als Graph-Node `_db_validieren()` im `agent.py`.
### Architektur-Prinzipien
- **"Berechnung in Python, nicht im LLM"** — LLM erkennt Absicht, Python prueft und verifiziert
- **"Trust, but verify"** — Jede Phase prueft die Arbeit der vorherigen
- **"Die Sekretaerin diagnostiziert nicht"** — Router entscheidet nur welcher Agent, Taxonomie lebt im Classify-Node
- **"Rueckfrage vor Dummheit"** — Im Zweifel lieber rueckfragen als falsch ausfuehren