# KNOW: Befunde zur Datenhaltung (Analyse vom 13.10.2026) Grundlage sind die Workflow-Definitionen auf n8n.mindboost.app, nur lesend über die n8n-API abgerufen. Ausgewertet wurde der Code von KNOW, KGW und allen aufrufenden Workflows. B11–B30 stammen aus der Vollanalyse in vier Ebenen: 1. Schema: Schlüssel, Indizes, Typen, nachträgliche Spalten. 2. SQL der 80 Operationen: Was wird geschrieben, was bei Konflikten nicht aktualisiert, was nie befüllt. 3. Abgleich mit den Payloads der Aufrufer. 4. Datenablagen außerhalb von KNOW: staticData, direkte Verbindungen, LLM-Aufrufe. Jeder Befund wurde am Code nachgeprüft, Artefakte der automatischen Auswertung sind aussortiert. Inhalte der Datenbank wurden nicht gelesen. Für `CANCEL - Unsubscribe Link` und `KAN-Learn - Kanbau Pattern Learning` liefert die API keine gespeicherten Ausführungen, ein Nachweis aus der Laufzeit fehlt deshalb. - Übersicht der Tabellen: [KNOW-schema.md](KNOW-schema.md) - Interaktive Ansichten (privat): - Datenfluss: https://claude.ai/artifact/GP8FfU8M6Wk9hPRmJuk96h - Ontologie: https://claude.ai/artifact/R7zxBJsCKkkCWdCcztLFUT (Quelle: [ontology/know.yaml](../../ontology/know.yaml)) Status aller Befunde: **offen**. In n8n wurde nichts geändert. | # | Befund | Schwere | |---|---|---| | B1 | CANCEL protokolliert über eine Operation, die es nicht gibt | mittel | | B2 | KAN-Learn speichert über eine Operation, die es nicht gibt | mittel | | B3 | IN-Sync ruft zwei nicht vorhandene Operationen auf (inaktiv) | niedrig | | B4 | CAT schreibt per eigenem SQL an KNOW vorbei | niedrig | | B5 | 12 Tabellen ohne aktiven Schreibweg, mit drei Ursachen | hoch (Datenbasis unklar) | | B6 | KAF-Bestätigung führt bei jedem Aufruf DDL aus und setzt einen festen Bestätiger | mittel | | B7 | Anhang-Extraktionen kommen nur von außen | niedrig | | B8 | KGW erlaubt externen Aufrufern eine Lösch-Operation | niedrig | | B9 | Keine Löschfristen für Klartext-Felder | mittel (Datenschutz) | | B10 | Absender-Report zeigt eingefrorene Statistik | mittel | | B11 | Kein einziger Fremdschlüssel; alle Verweise sind freier Text | hoch | | B12 | Zwei Schlüsselschemata für Kontakte → Dubletten, Join findet nichts | hoch | | B13 | Zwei Projekt-Kennungen, `projects` leer, Projekte kommen live aus KanBau | hoch | | B14 | Zu enge Eindeutigkeit: Ereignisse und Prüfungen werden überschrieben bzw. verworfen | hoch | | B15 | Fehlende Eindeutigkeit: jede Extraktion erzeugt eine neue Zeile | mittel | | B16 | Drei Tabellen ohne Primärschlüssel, doppelte Indizes | niedrig | | B17 | Upserts frieren Fachdaten ein (Kontakte, Ankaufsfälle, Falldokumente) | hoch | | B18 | Gespeicherte abgeleitete Werte veralten (`deadline_is_overdue` u. a.) | mittel | | B19 | Felder, die nie geschrieben werden (`confidence`, `confirmed_by`, `verified_at`) | mittel | | B20 | Kategorien in mehreren Vokabularen; Safety-Floor-Kategorien über CAT unerreichbar | hoch | | B21 | Postfach und Absender uneinheitlich normalisiert | mittel | | B22 | Zwei Listen für Dokumenttypen | niedrig | | B23 | Sechs Statusfelder rund um den Ankaufsfall | mittel | | B24 | Schema-Drift: 45 nachgezogene Spalten, Laufzeit-DDL, keine Versionierung | mittel | | B25 | Uneinheitliche Typen und Benennung (Geld als Text, Cent vs. Euro, deutsch/englisch) | niedrig | | B26 | Fehlende Indizes auf Such- und Join-Spalten | niedrig | | B27 | Ein Datenbankzugang für alles, geteilt mit dem Chat-Gedächtnis | mittel | | B28 | Zustand in staticData von 9 Workflows, außerhalb von KNOW | mittel | | B29 | Browser-Sessions (Cookies, Header) unverschlüsselt gespeichert | hoch (Sicherheit) | | B30 | LLM-Aufrufe am PSE-LiteLLM vorbei, u. a. Kaufvertragstexte | hoch (Datenschutz) | | B31 | Zwei Wörter für „bestätigt“: `verified` vs. `confirmed` | niedrig | | B32 | KAN-Gateway ruft `get_kanbau_project_patterns` auf, die es nicht gibt | hoch | | B33 | MPA speichert jedes Projektereignis als `info`/`normal` | hoch | ## B1 – CANCEL protokolliert ins Leere - **Beleg:** `CANCEL - Unsubscribe Link` (aktiv) ruft in 4 Knoten (`KNOW: Log Erfolg`, `… Fehler`, `… Duplikat-Erfolg`, `… Dry Run`) `log_workflow_decision` auf. Diese Operation steht nicht in der Liste von KNOW, KNOW wirft `Nicht erlaubte KNOW operation`. Alle 4 Knoten haben „Continue on Fail“. - **Folge:** Der Workflow läuft durch, aber kein Abmeldevorgang wird protokolliert, und niemand merkt es. - **Vorschlag:** auf die vorhandene Operation `record_workflow_decision` umstellen. Sie schreibt in `workflow_decisions`, das heute leer bleibt (B5b). Vorher die Felder des Payloads abgleichen. ## B2 – KAN-Learn speichert nicht - **Beleg:** `KAN-Learn - Kanbau Pattern Learning` (aktiv) ruft im Knoten `KNOW: Save Patterns` `upsert_kanbau_project_patterns` auf. Die Operation gibt es in KNOW nicht, der Knoten hat keine Fehlerbehandlung. - **Folge:** Jede Ausführung bricht an dieser Stelle ab. KAN-Learn wird von `KAN - Kanbau API Gateway` aufgerufen. - **Vorschlag:** In der Ausführungsliste von n8n prüfen, ob und wie oft der Workflow läuft. Dann entweder die Operation in KNOW ergänzen (Zieltabelle festlegen) oder den Aufruf entfernen. ## B3 – IN-Sync ruft Operationen auf, die fehlen - **Beleg:** `IN - Sicherheitsregeln Source Sync` (inaktiv) ruft `upsert_input_validation_rule` und `upsert_input_validation_source_snapshot` auf. Beide fehlen in KNOW, ebenso eine passende Tabelle. - **Vorschlag:** vor einer Aktivierung in KNOW ergänzen. ## B4 – CAT schreibt direkt in `workflow_hints` - **Beleg:** Ein Postgres-Knoten in `CAT - Postfach Pipeline` führt `UPDATE knowledge.workflow_hints SET notified_at = now(), pending_notification = false WHERE hint_key = '{{ $json.hint_key }}'` aus. - **Folge:** - KNOW ist nicht mehr der einzige Schreibweg. - Der Wert wird ohne Escaping direkt in das SQL eingesetzt. Ein Hochkomma in `hint_key` bricht die Abfrage. - **Vorschlag:** eine KNOW-Operation dafür anlegen, z. B. `mark_hint_notified`. ## B5 – Tabellen ohne aktiven Schreibweg Kein aktiver Workflow schreibt diese 12 Tabellen über KNOW. Die Ursachen sind verschieden: **a) KNOW hat gar keine Schreib-Operation.** Gefüllt werden können diese Tabellen nur mit direktem Zugang zur Datenbank: | Tabelle | Wer liest | Bemerkung | |---|---|---| | `projects` | `NEX - Teams Chat Agent`, extern über KGW (`find_project_candidates`, `get_sender_profile`) | Ist die Tabelle leer, findet die Projektsuche nur Treffer über `project_aliases`. **Zuerst prüfen**: `SELECT count(*), max(last_synced_at) FROM knowledge.projects;` | | `contact_aliases` | extern über KGW (`find_contact_candidates`) | | | `contact_project_relations` | niemand | | | `agent_capabilities`, `agent_context_documents`, `agent_execution_log` | niemand | vermutlich Altlast oder außerhalb von n8n genutzt | **b) Die Operation gibt es, aber kein Workflow ruft sie auf:** | Tabelle | vorhandene Operationen | Wer liest | |---|---|---| | `property_development_cases` | `upsert_property_development_case`, `sync_property_development_folder_snapshot`, `store_kaf_confirmation_snapshot`, `claim_/complete_kaf_confirmation_create` | extern über KGW (6 Abfragen für Fristen, Angebote, fehlende Unterlagen) | | `property_development_documents` | `upsert_property_development_document` | extern über KGW (`summarize_property_development_case`) | | `project_folders` | `upsert_project_folder` | niemand | | `place_enrichments` | `upsert_place_enrichment` | niemand (`MAP - Maps/Geo Gateway` nutzt KNOW nicht) | | `workflow_decisions` | `record_workflow_decision` | niemand (siehe B1) | Die Abfragen zu Ankaufsfällen über KGW (Hermes) liefern also nur Daten, die einmal anders hineingekommen sind. Über KGW selbst dürfen diese Tabellen nicht beschrieben werden. **c) Gewollt:** `workflow_hint_config` wird von `setup_schema` mit 9 Kategorien befüllt (u. a. `mahnung`, `zahlungsaufforderung`, `newsletter_werbung`) und danach nur noch gelesen. **Nächster Schritt:** einmal die Zeilenzahlen aller Tabellen und das jüngste `updated_at` abfragen. Erst dann ist klar, ob die Tabellen leer sind oder von außerhalb von n8n befüllt werden. ## B6 – KAF-Bestätigung: DDL im Anfragepfad, fester Bestätiger - **Beleg:** Ruft jemand `upsert_property_development_case` mit `quality_status: confirmed` auf, führt KNOW einen Sonderweg aus (`Build KAF Case Confirmation SQL`). Dieser Sonderweg macht Folgendes: - Er führt bei **jedem** Aufruf `ALTER TABLE … ADD COLUMN IF NOT EXISTS confirmed_by / confirmed_at` aus. - Er setzt `confirmed_by` ohne Angabe auf `r.rapp@mygodel.de`. - **Folge:** - Die beiden Spalten fehlen in `setup_schema`. - DDL im Anfragepfad braucht eine exklusive Tabellensperre. - Bestätigungen ohne Absender werden einer bestimmten Person zugeschrieben. - Heute ruft kein Workflow diesen Weg auf (B5b). - **Vorschlag:** die Spalten in `setup_schema` aufnehmen, das `ALTER TABLE` entfernen und `confirmed_by` zur Pflichtangabe machen. ## B7 – `document_extractions` nur von außen - **Beleg:** `store_document_extraction` ruft kein n8n-Workflow auf, auch `DOC - Dokumenten-Analyse` nicht. Geschrieben wird nur extern über KGW. - **Vorschlag:** klären, ob DOC die Extraktionen speichern soll. Sie wären eine gute Quelle für Fälle bzw. Trainingsdaten in knaster. ## B8 – Lösch-Operation über KGW - **Beleg:** Die Freigabeliste von KGW enthält `purge_spf_aliases`, ein `DELETE` auf `project_aliases`. - **Vorschlag:** Prüfen, ob externe Aufrufer (Hermes, Skripte) das wirklich brauchen. Sonst streichen. ## B9 – Löschfristen - **Beleg:** Folgende Felder speichern Klartext, eine Bereinigung ist nicht erkennbar: - `llm_jobs.prompt` und `.result` - `document_extractions.extracted_text` - `property_development_cases.meeting_transcripts` - `agent_context_documents.content` - `mail_events` (Absender, Betreff) `purge_*` gibt es nur für Absender-Beobachtungen und Aliase. `browser_sessions.session_data` ist unverschlüsselt, siehe B29. - **Vorschlag:** Aufbewahrungsfristen festlegen, z. B. `llm_jobs` nach Abschluss nach 30 Tagen leeren. Zu `browser_sessions` siehe B29. ## B10 – Absender-Report zeigt eingefrorene Statistik - **Beleg:** - `ASP - Absender-Profil Backfill` schreibt die Statistik eines Absenders (`mail_count`, `dominant_category`, `avg_confidence` …) per `upsert_contact` in `contacts.raw_evidence_json`. - `upsert_contact` aktualisiert bei einem vorhandenen Kontakt nur `display_name`, `normalized_name`, `last_seen_at` und `updated_at`, **nicht** `raw_evidence_json`. - `ASP - Absender-Report` liest Kategorie und Mail-Anzahl per eigenem SQL aus genau diesem Feld. - **Folge:** Der Report zeigt für jeden Absender die Werte vom ersten Backfill, auch wenn sich die Lage seitdem geändert hat. Die aktuellen Werte stehen in `sender_observations`, dort schreibt derselbe Backfill per `upsert_sender_observation` mit. - **Ursache (ontologisch):** Das Absender-Profil gehört zur Klasse *Absender*, der Kontakt ist das fachliche Gegenüber. Beide sind über die E-Mail-Adresse verbunden. Liegt die Statistik zusätzlich im Kontakt, entsteht eine zweite Quelle, und die veraltet. - **Vorschlag:** Den Report auf `sender_observations` umstellen und den Kontakt nur noch über `email` dazuholen. Die Statistik aus `upsert_contact` herausnehmen. --- # Vollanalyse: B11–B30 ## A. Integrität und Schlüssel ### B11 – Keine Fremdschlüssel - **Beleg:** `setup_schema` enthält kein einziges `REFERENCES`. - Alle Verweise (`project_key`, `contact_key`, `case_key`, `message_id`, `kuerzel`, `mac` …) sind freier Text ohne Prüfung. - Auch Löschungen (`purge_*`, `reject_project_alias`) räumen abhängige Zeilen nicht auf. - **Folge:** Verwaiste und ungültige Verweise sind jederzeit möglich, und es fällt nicht auf. Besonders betroffen ist `project_key`: Es wird in 7 Tabellen benutzt, aber `projects` wird nie befüllt (B13). Kein einziger Projektverweis lässt sich gegen eine Projekttabelle prüfen. - **Vorschlag:** - Für die Kernbezüge Fremdschlüssel einführen, zuerst `NOT VALID`, dann nach einer Bereinigung validieren: Falldokument → Ankaufsfall, Alias → Kontakt, Beteiligung → Kontakt und Projekt. - Für `project_key` erst B13 lösen. ### B12 – Zwei Schlüsselschemata für Kontakte - **Beleg:** - `upsert_contact` bildet den Schlüssel standardmäßig als `normalisierter Name|E-Mail`. Kein Aufrufer übergibt einen eigenen Schlüssel. - `update_sender_relevance` legt Kontakte mit dem Schlüssel `E-Mail` an, mit der E-Mail-Adresse als Anzeigename. - `find_workflow_hints` verknüpft `workflow_hints.pattern_key` (Absenderadresse) mit `contacts.contact_key`. - **Folge:** - Ein Absender kann zweimal als Kontakt existieren. - Der Relevanz-Score landet auf dem Schattenkontakt mit dem E-Mail-Schlüssel. - Die vom Backfill angelegten Kontakte („Name|E-Mail“) findet der Join in `find_workflow_hints` nie. - Ändert sich der Anzeigename, entsteht ein neuer Kontakt. - **Vorschlag:** einen Schlüssel festlegen, z. B. die kleingeschriebene E-Mail für Personen und Absender, Namen nur noch als Attribut bzw. Alias. Bestehende Dubletten über `email` zusammenführen. ### B13 – Zwei Projekt-Kennungen, `projects` leer - **Beleg:** - Verweise nutzen `project_key`. - Der Ankaufsfall speichert dagegen `selected_project_id`, eine `project_id` aus der KAF-Bestätigung. - `projects` und `project_aliases` führen zusätzlich `kanbau_project_id`. - Keine KNOW-Operation schreibt `projects` (B5a). - `PRO - Project Assignment` fragt Projekte live bei `mygodel.kanbau.com` ab. - **Folge:** Es gibt keine Stelle, an der `project_key` und die KanBau-ID verlässlich zusammengehören. Lesende Abfragen auf `projects` (NEX-Teams-Agent, `find_project_candidates`) finden vermutlich nur Treffer über Aliase. - **Vorschlag:** einen Abgleich KanBau → `projects` anlegen (z. B. nachts, über das vorhandene KAN-Gateway) mit `kanbau_project_id` als festem Schlüssel. `project_key` daraus ableiten oder ersetzen. ### B14 – Zu enge Eindeutigkeit - **`project_events`:** `UNIQUE(message_id)` mit `ON CONFLICT (message_id) DO UPDATE SET project_key, category, summary …`. - Meldet eine Mail zwei Ereignisse oder betrifft sie zwei Projekte, überschreibt das zweite das erste. - Vorschlag: Schlüssel `(message_id, project_key, category)`. - **`kaf_review_snapshots`:** `UNIQUE(case_key)` mit Aktualisierung nur bei `review_status='pending'`. - Nach einer Freigabe kann für denselben Fall kein neuer Vertragsstand gespeichert werden. Der Insert läuft ohne Fehler ins Leere. - Vorschlag: eine Version pro Prüfung (`case_key, version`). - **`mail_events`:** `UNIQUE(message_id)` ohne Postfach. - Das ist unkritisch, solange die Graph-IDs pro Postfach verschieden sind. Bei Internet-Message-IDs würde eine Mail an zwei Postfächer zu einer Zeile. - Prüfen, welche ID CAT und MPA übergeben. ### B15 – Fehlende Eindeutigkeit bei Extraktionen - **Beleg:** `document_extractions` hat keinen eindeutigen Schlüssel, `store_document_extraction` macht ein reines `INSERT`. - **Folge:** Jede erneute Verarbeitung desselben Anhangs erzeugt eine weitere Zeile, und es ist nicht festgelegt, welche gilt. - **Vorschlag:** `UNIQUE(message_id, attachment_id)` mit Upsert. ### B16 – Tabellen ohne Primärschlüssel - **Beleg:** `workflow_hints`, `workflow_hint_config` und `kanbau_method_examples` haben nur `UNIQUE`, keinen `PRIMARY KEY`. Auf `hint_key` und `method_key` liegt zusätzlich ein zweiter, überflüssiger Index. - **Vorschlag:** den eindeutigen Schlüssel zum Primärschlüssel machen und die doppelten Indizes entfernen. ## B. Aktualität ### B17 – Upserts frieren Fachdaten ein Bei einem vorhandenen Datensatz aktualisieren mehrere Upserts nur wenige Spalten. Alles andere behält den Wert vom ersten Insert: - **`upsert_contact`:** aktualisiert nur Name, `last_seen_at` und `updated_at`. **E-Mail, Telefon, Website, Steuer-Nr., USt-IdNr., IBAN-Hash, Adresse, Kontakttyp, Qualitätsstatus und Belege** werden nie korrigiert. B10 ist ein Spezialfall davon. - **`upsert_property_development_case`:** aktualisiert nur Name, die drei Statusfelder, `sharepoint_json` und `raw_evidence_json`. **Rund 50 Fachfelder bleiben eingefroren**, darunter Angebotsfrist, Kaufpreis, Fläche, `next_action`, `next_action_due_at`, Risiko, Unterlagen-Flags und Mitschriften. - **`upsert_property_development_document`:** `is_current_version` und `supersedes_document_key` werden nie aktualisiert. Ein altes Dokument bleibt „aktuell“, die **Versionierung funktioniert nicht**. Ebenso bleiben Zusammenfassung, Absender und Dokumentdatum stehen. - **Vorschlag:** In den Upserts alle fachlichen Felder mit `COALESCE(EXCLUDED.x, tabelle.x)` aktualisieren. Felder, die bewusst nur einmal gesetzt werden, im Code kennzeichnen. ### B18 – Gespeicherte abgeleitete Werte veralten - **Beleg:** - `deadline_is_overdue` ist eine gespeicherte Spalte. `find_cases_by_deadline` liest sie, sie lässt sich aber aus `offer_deadline` und dem heutigen Datum berechnen. - `document_count`, `missing_documents` und `detected_documents` werden nur von `sync_property_development_folder_snapshot` gepflegt, und das ruft niemand auf (B5b). - **Folge:** „Überfällig“ und „fehlende Unterlagen“ zeigen den Stand vom Einfügen. - **Vorschlag:** `deadline_is_overdue` zur Laufzeit berechnen (View oder Ausdruck in der Abfrage). Die Zähler aus `property_development_documents` ableiten. ### B19 – Felder, die nie geschrieben werden - **`workflow_hints.confidence`:** Keine Operation schreibt das Feld. - CAT sendet die Konfidenz, aber innerhalb von `suggested_action`. - `find_workflow_hints` sortiert nach der Spalte, die immer 0 ist. Die Reihenfolge der Hinweise ist damit zufällig. - **`workflow_hints.confirmed_by`:** `promote_workflow_hint` setzt Status und Zeit, aber nicht die Person. Wer einen Hinweis über den Bestätigungslink freigegeben hat, wird nicht gespeichert. - **`contacts.verified_at`, `project_aliases.verified_at`:** Keine Operation setzt sie. Das Bestätigungsmodell (`quality_status` + `verified_at`) ist nur halb umgesetzt. - **Vorschlag:** `confidence` aus `suggested_action` in die Spalte übernehmen (auch rückwirkend). `confirmed_by` aus dem Bestätigungs-Webhook mitgeben. ## C. Vokabular und Normalisierung ### B20 – Kategorien in mehreren Vokabularen - **Beleg:** Dieselbe Idee „Kategorie einer Mail“ existiert in mindestens sechs Listen: - **CAT-Zuordnung:** 8 Werte, darunter `bauvorhaben` und `projekt`. - **`workflow_hint_config.config_key`:** 9 Werte, darunter `mahnung`, `zahlungsaufforderung` und `default`. - **`outlook_category`:** Anzeigenamen in Outlook. - **`sender_observations.dominant_category`:** vom Absender-Backfill vergeben. - **`project_events.category`:** 5 feste Werte. - **`mail_events.category`:** frei. MPA schreibt hier den Workflow-Namen `"MPA"`. - **Folge:** - CAT erzeugt `bauvorhaben` und `projekt`, die Konfiguration kennt sie nicht, also greift `default`. - Umgekehrt erzeugt CAT nie `mahnung` oder `zahlungsaufforderung`. **Die Regeln mit Safety-Floor („immer manuell prüfen“) greifen bei Hinweisen aus CAT nie.** - Auswertungen nach Kategorie mischen fachliche Kategorien mit dem Workflow-Namen „MPA“. - **Vorschlag:** - Ein Vokabular als Tabelle bzw. SKOS-Konzeptschema in der Ontologie, mit Abbildung der Outlook-Namen. - CAT um Mahnung und Zahlungsaufforderung erweitern. - MPA schreibt seinen Status in `workflow_status`, nicht in `category`. ### B21 – Postfach und Absender uneinheitlich normalisiert - **Postfach:** CAT setzt in `store_mail_event` fest `info@mygodel.de`, im CAT-Code kommt aber auch `r.rapp@mygodel.de` vor. MPA übergibt den `grant_key`. Der Absender-Backfill schreibt das Postfach kleingeschrieben. - **Absender:** CAT schreibt ihn klein, MPA übergibt ihn unverändert. `sender_observations.email` ist immer klein. - **Folge:** Die weichen Bezüge Mail ↔ Absender ↔ Kontakt (Ontologie) treffen nur teilweise. Steht ein falsches Postfach fest im Code, werden Mails dem falschen Postfach zugeordnet. - **Vorschlag:** E-Mail-Adressen und Postfächer **in KNOW** normalisieren (kleinschreiben, Anzeigename abtrennen), nicht in jedem Aufrufer. In CAT das Postfach aus dem Trigger übernehmen. ### B22 – Zwei Listen für Dokumenttypen - **Beleg:** `property_development_documents.document_type` hat 19 feste Werte. `document_extractions.document_type` ist frei, mit Standardwert `unknown`. - **Vorschlag:** eine gemeinsame Liste (Ontologie-Klasse *Dokumenttyp*). ### B23 – Sechs Statusfelder rund um den Ankaufsfall - **Beleg:** `status` (alt), `pipeline_status`, `outcome_status`, `review_status`, `confirmation_status` und `quality_status`. Dazu kommt die zweite Bestätigungslogik aus B6, die `quality_status='confirmed'` setzt, während der Token-Ablauf `confirmation_status` nutzt. - **Folge:** Es ist nicht festgelegt, welches Feld „bestätigt“ bzw. „abgeschlossen“ bedeutet. - **Vorschlag:** `status` (alt) aus `pipeline_status` und `outcome_status` ableiten oder entfernen. Eine Bestätigungslogik festlegen. ## D. Schema-Pflege ### B24 – Schema-Drift - **Beleg:** - 45 Spalten werden per `ALTER TABLE … ADD COLUMN IF NOT EXISTS` nachgezogen, davon 41 in `property_development_cases`. Bei `kanzlei_field_patterns` doppelt mit unterschiedlichem Standardwert (`'[]'` bzw. `'[]'::jsonb`). - Dazu kommt Laufzeit-DDL in einer Anfrage (B6). - Es gibt keine Schema-Version und keine Migrationshistorie. - **Folge:** Welche Spalten eine Instanz hat, hängt davon ab, wann `setup_schema` zuletzt lief und ob der KAF-Sonderweg je ausgeführt wurde. - **Vorschlag:** versionierte Migrationen (Tabelle `schema_migrations`), `setup_schema` nur noch als Migrationslauf. ### B25 – Uneinheitliche Typen und Benennung - **Geld:** als Text (`commission_including_vat`, `godel_offer`), Beträge mal in Cent (`amount_cents BIGINT`), mal in Euro (`*_eur NUMERIC`). - **Konfidenz:** mal `NUMERIC`, mal `DOUBLE PRECISION`. - **Spaltennamen:** deutsch und englisch gemischt (`kuerzel`, `gesellschaft`, `quelle`, `bestaetigt`, `baustelle`, `absender_typ`, `mail_art`). - **Zeitstempel:** `updated_at` fehlt u. a. in `project_aliases`, `contact_aliases`, `document_extractions` und `mail_events`. - **Vorschlag:** bei der nächsten Migration vereinheitlichen. Niedrige Priorität, aber Voraussetzung für generierte Schemas (Ontologie → JSON-Schema). ### B26 – Fehlende Indizes - **Beleg:** Ohne Index sind u. a.: - `contacts.email` (Abgleich mit Absendern) - `mail_events.sender`, `received_at`, `category`, `assigned_project_key` (`find_mail_events` filtert und sortiert danach) - `document_extractions.message_id` und `project_key` - `sender_observations.email` (`get_sender_profile`) - `contact_project_relations` - **Vorschlag:** Indizes auf diese Spalten. Unkritisch, solange die Tabellen klein sind. ## E. Ablage und Datenfluss außerhalb von KNOW ### B27 – Ein Datenbankzugang für alles - **Beleg:** KNOW, die direkten SQL-Knoten in CAT und ASP, der Postgres-Healthcheck und der Credentials-Watchdog nutzen dasselbe Credential **„n8n Postgres memory account“**. Darüber läuft auch das Chat-Gedächtnis. - **Folge:** Jeder Workflow mit diesem Credential kann jede Tabelle lesen, ändern und löschen. Die Freigabeliste in KNOW und KGW lässt sich so umgehen (B4 ist ein Beispiel). - **Vorschlag:** eigene DB-Rolle für KNOW mit Rechten nur auf `knowledge`, getrennt vom Chat-Gedächtnis. Direkte SQL-Knoten auf eine Rolle nur mit Leserechten umstellen oder durch KNOW-Operationen ersetzen. ### B28 – Zustand in staticData - **Beleg:** 9 Workflows speichern Zustand in n8n-staticData: - CANCEL (`successful_unsubscribes`, wegen B1 die **einzige** Aufzeichnung der Abmeldungen) - BEL (`bel_alerts`, `bel_setup_sent`) - KJW (`nextLink`) - TDO (`manual_enrichment`, `etag`) - MCR (`last_reported_sha`) - MPA (Token-Cache, verschlüsselt, in Ordnung) - ASP-Scheduler, KBT und KBJ (inaktiv) - **Folge:** Dieser Zustand ist in KNOW unsichtbar, hat keine Historie und geht bei einem Neuimport des Workflows verloren. - **Vorschlag:** Fachlich relevante Zustände (CANCEL, BEL) nach KNOW verlagern. Technische Cursor (KJW, MCR) dürfen bleiben. ### B29 – Browser-Sessions unverschlüsselt - **Beleg:** `BSB - Execute Login` speichert `session_data: { cookies, headers }` über `upsert_browser_session` als JSON im Klartext. - **Folge:** Gültige Sitzungs-Cookies (u. a. für KanBau) liegen lesbar in der Datenbank. Jeder mit dem gemeinsamen Credential (B27) kann sie übernehmen. Zum Vergleich: Die Postfach-Tokens sind mit AES-256-GCM verschlüsselt, auch im Cache. - **Vorschlag:** `session_data` genauso verschlüsseln wie `mailbox_grants`. Die Lebensdauer kurz halten (Standard 60 Min. ist gut). ### B30 – LLM-Aufrufe am PSE-LiteLLM vorbei - **Beleg (direkte HTTP-Aufrufe, sicher am LiteLLM vorbei):** - `KAF - Kaufvertrag Erkennung und Extraktion` (aktiv) schickt den **Kaufvertragstext** (`pdf_text`) direkt an `openrouter.ai`, ohne PSE-Aufruf. - Ebenso direkt gehen `KAN - Kanbau API Gateway` (Intent-Erkennung), `BSB - Discover Session` (Login-Seiten an `api.anthropic.com`), `CDW` (Prüfaufruf) und `KSUM` (inaktiv). - **Nicht entscheidbar:** Die OpenAI-Knoten in ASP, DOC, MAIL, CAT und OR nutzen das Credential `openAiApi`. Ob es auf den LiteLLM mit PSE zeigt, ist ohne Einsicht in das Credential nicht erkennbar. ASP ruft dabei kein PSE auf. - **Folge:** Widerspricht der Vorgabe im Aufbauplan („Starke Modelle nur über den LiteLLM mit PSE“). Kaufverträge enthalten Namen, Anschriften und Preise. - **Vorschlag:** KAF, KAN und BSB auf den LiteLLM mit PSE umstellen. Das Ziel des Credentials `openAiApi` prüfen. ## Geprüft, ohne Befund - Postfach-Tokens (`mailbox_grants`) und der Token-Cache in MPA sind mit AES-256-GCM verschlüsselt. - KNOW maskiert alle Werte über `sqlString`/`sqlJson`. Bei den Operationen gibt es keine offene String-Verkettung. Die Ausnahme ist der direkte SQL-Knoten in CAT (B4). - KGW prüft HMAC und Zeitfenster und lässt nur eine feste Liste von Operationen zu. - Die vermeintlich fehlende Spalte `workflow_decisions.decided_by` existiert (per `ALTER` ergänzt). --- # Gefunden mit `knaster ontology check` (13.10.) Diese Befunde hat der automatische Abgleich gefunden, nachdem die Ontologie im Repo als Quelle angelegt war (SPEC-ontology). ### B31 – Zwei Wörter für „bestätigt“ - **Beleg:** `record_workflow_decision` setzt `quality_status` auf `verified`. Alle anderen Tabellen und Operationen nutzen `confirmed`. - **Folge:** Auswertungen über `quality_status = 'confirmed'` übersehen bestätigte Entscheidungen. - **Stand in der Ontologie:** `verified` ist als alternative Schreibweise von `confirmed` im Vokabular *Qualitätsstatus* eingetragen. Vorschlag: in KNOW auf `confirmed` vereinheitlichen. ### B32 – KAN-Gateway ruft eine Operation auf, die es nicht gibt - **Beleg:** `KAN - Kanbau API Gateway` (aktiv) ruft `get_kanbau_project_patterns` auf. Die Operation steht nicht in der Liste von KNOW. Das Gegenstück `upsert_kanbau_project_patterns` in KAN-Learn fehlt ebenso (B2). - **Folge:** Gelernte Projektmuster für KanBau werden weder gespeichert noch gelesen. Je nach Fehlerbehandlung des Knotens bricht das Gateway an dieser Stelle ab. - **Vorschlag:** die beiden Operationen samt Zieltabelle in KNOW anlegen (und dann in der Ontologie aufnehmen) oder die Aufrufe entfernen. ### B33 – Projektereignisse ohne Einordnung - **Beleg:** `MPA - Projekt-Ablage` ruft `upsert_project_event` mit den festen Werten `category: "info"` und `urgency: "normal"` auf. - **Folge:** Die Ereignisarten `verzoegerung`, `mangel`, `termin` und `kosten` (CHECK-Constraint der Tabelle) kommen nie vor, ebenso keine Dringlichkeit `high`. Eine Auswertung „Mängel pro Projekt“ ist mit diesen Daten nicht möglich. - **Vorschlag:** Kategorie und Dringlichkeit aus der Mail-Analyse übernehmen (MAIL bzw. CAT kennen „Mangel“) oder ein kleines Modell bzw. einen knaster-Skill einordnen lassen.