Merge main in plan/aufbau-2026-10-12: KNOW-Analyse, Ontologie, cpu_roles
ci / test (push) Failing after 22s

main (bis 13.10.) enthielt eine parallele Fassung von Task 0.4 (c81e372) sowie
KNOW-Befunde und die Ontologie. Konflikte zugunsten des Plan-Branches gelöst
(ausgerollter Stand); übernommen aus main: Ontologie (core/src/knaster/ontology,
ontology/, Tests, Router-Image), KNOW-Doku, cpu_roles im Placement samt
Kennzeichnung von pve61 und gode-pc-05/06 im Inventar und Test.
infra-stack kopiert jetzt auch ontology/ nach infra (Router-Build braucht es).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Rapp
2026-10-19 19:23:22 +02:00
co-authored by Claude Opus 5.5
34 changed files with 6087 additions and 13 deletions
+3 -1
View File
@@ -18,6 +18,7 @@ Vorgehen pro Baustein: **Spec → Implementierung → Tests → Commit**. Bis zu
| 10 | Plattform | ADR-0001/0002 | OpenTofu (bpg/proxmox), Ansible-Rollen (IOMMU/VFIO, ZFS-Replikation, Treiber-Pinning, Xid-Exporter) | ✅ geschrieben, **nicht** gegen echten Cluster/`tofu validate` geprüft |
| 11 | CI | – | Gitea Actions: Tests, Lint, Konfig-Validierung (Runner `gitrunner-knaster`) | ✅ |
| 12 | TTS + Legacy-GPUs | ADR-0006 | Engine-Grenzen im Katalog, deaktivierte GPUs, geteilte GPU-Rollen, TTS-Slots, Piper-Dienst, TTS-Canary, `knaster bench tts` | ✅ |
| 13 | Ontologie der Datenhaltung | SPEC-ontology | `ontology/know.yaml` als Quelle, Export (Turtle/SKOS, Graph, Ansicht), Abgleich mit n8n/KNOW (nur lesend), Vorschläge `ontology_change` in der Service-Routine, `knaster ontology apply` | ✅ gegen echten n8n-Export geprüft (24 Befunde, 4 Vokabel-Vorschläge) |
## Teststrategie
@@ -30,11 +31,12 @@ Vorgehen pro Baustein: **Spec → Implementierung → Tests → Commit**. Bis zu
| Was | Wie geprüft |
|---|---|
| Kernlogik (Router, Eskalation, Verifier, Pflege-Agent, Healer, Lifecycle, Placement) | 136 Unit- und Integrationstests (`make test`) |
| Kernlogik (Router, Eskalation, Verifier, Pflege-Agent, Healer, Lifecycle, Placement) | 152 Unit- und Integrationstests (`make test`) |
| Konfiguration (Registry ↔ Skills ↔ LiteLLM ↔ Healer ↔ Policy ↔ Katalog) | `knaster validate` |
| Compose-Dateien | `docker compose config` (`make compose-check`) |
| Prometheus/Alertmanager/n8n-JSON | Strukturtests (`core/tests/test_artifacts.py`) |
| n8n, Portainer | nur lesender Erreichbarkeitstest, nichts deployt |
| Ontologie ↔ n8n | `knaster ontology check` gegen einen lesend abgerufenen Export aller Workflows (13.10.); Tests mit synthetischen Workflows |
| OpenTofu, Ansible | nur YAML/Syntax. `tofu validate` und `ansible-lint` stehen noch aus |
## Offene Punkte vor dem Deployment
+338
View File
@@ -0,0 +1,338 @@
# 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.
+569
View File
@@ -0,0 +1,569 @@
# KNOW: Tabellen und Zugriffe (n8n, Schema `knowledge`)
Stand: 13.10.2026. Abgeleitet aus den Workflow-Definitionen auf n8n.mindboost.app, gelesen über die n8n-API. Inhalte der Datenbank wurden nicht gelesen.
Interaktive Ansichten (privat, Freigabe über das Share-Menü):
- Datenfluss: https://claude.ai/artifact/GP8FfU8M6Wk9hPRmJuk96h
- Ontologie: https://claude.ai/artifact/R7zxBJsCKkkCWdCcztLFUT (Quelle: [ontology/know.yaml](../../ontology/know.yaml), Turtle: [ontology/know.ttl](../../ontology/know.ttl))
Befunde mit Belegen, Folgen und Vorschlägen: [KNOW-befunde.md](KNOW-befunde.md) (B1–B33)
## Aufbau
- **KNOW - Knowledge Base Gateway** ist ein Sub-Workflow mit 80 fest benannten Operationen. Jede Operation ist auf eine SQL-Vorlage abgebildet, freies SQL gibt es nicht. Unbekannte Operationen lehnt KNOW mit `Nicht erlaubte KNOW operation` ab. Die Tabellen legt `setup_schema` an (Postgres, Erweiterung `pg_trgm`).
- **KGW - KNOW Query Gateway**: Eingang für externe Aufrufer (Hermes, Skripte) über `POST /webhook/know-query`.
- Authentifizierung: HMAC-SHA256 mit `X-KGW-Timestamp` und `X-KGW-Signature` (Schlüssel in `KGW_HMAC_KEY`), Zeitfenster 5 Min.
- Erlaubt sind 26 Operationen: alle Lese-Operationen plus Lern-Schreibzugriffe.
- Gesperrt sind `setup_schema` sowie die Operationen für BSB, KAF-Bestätigung, Kanzlei, Belege, Mailbox-Grants und LLM-Jobs.
- **Direktes SQL an KNOW vorbei**:
- `CAT - Postfach Pipeline` aktualisiert `workflow_hints` (`notified_at`, `pending_notification`).
- `ASP - Absender-Report` liest `contacts`.
## Auffälligkeiten (Kurzfassung, ausführlich in [KNOW-befunde.md](KNOW-befunde.md))
1. **`CANCEL - Unsubscribe Link`** (aktiv) ruft in 4 Knoten `log_workflow_decision` auf. KNOW kennt diese Operation nicht und lehnt ab. Weil „Continue on Fail“ gesetzt ist, läuft der Workflow weiter, aber es wird nichts protokolliert.
2. **`KAN-Learn - Kanbau Pattern Learning`** (aktiv) ruft `upsert_kanbau_project_patterns` auf. Auch diese Operation kennt KNOW nicht. Der Knoten hat keine Fehlerbehandlung, die Ausführung bricht dort ab.
3. **`IN - Sicherheitsregeln Source Sync`** (inaktiv) ruft `upsert_input_validation_rule` und `upsert_input_validation_source_snapshot` auf. Beide gibt es in KNOW nicht.
4. **Kein aktiver Workflow schreibt über KNOW** in: `contact_aliases`, `contact_project_relations`, `projects`, `project_folders`, `property_development_cases`, `property_development_documents`, `place_enrichments`, `workflow_decisions`, `workflow_hint_config`, `agent_capabilities`, `agent_context_documents`, `agent_execution_log`. Möglich sind Schreibzugriffe von außerhalb von n8n.
5. **`document_extractions`** wird nur von außen über KGW beschrieben.
6. **Löschfristen:** Für Klartext-Felder (`llm_jobs.prompt` und `.result`, `document_extractions.extracted_text`, `property_development_cases.meeting_transcripts`, `agent_context_documents.content`) ist keine Bereinigung erkennbar. Es gibt nur `purge_*`-Operationen für Absender-Beobachtungen und Aliase. Bei `browser_sessions.session_data` ist am Schema nicht erkennbar, ob es verschlüsselt ist.
## Bezug zu knaster
- **Task 6.4:** `find_active_llm_workers` ist über KGW von außen erlaubt. Die Operation liefert `mac, host, model, tier, tier_rank, status, endpoint, last_seen_at, age_seconds` und die Zahl wartender Jobs. Der Router kann aktive Worker damit ohne neuen Webhook lesen, braucht dafür aber den HMAC-Schlüssel.
- **Gleiche Idee wie die Skill-Graduierung:** `workflow_hint_config.min_observations_for_skip_ai` lässt die KI weg, sobald ein Muster oft genug beobachtet wurde. Aus `workflow_decisions` (Vorschlag vs. tatsächliche Entscheidung) ließen sich Präferenzpaare gewinnen.
## Tabellen
### Mail und Absender
#### `mail_events`
Eine Zeile pro verarbeiteter Mail: Kategorie, zugeordnetes Projekt, Status.
**Sensibel:** Absender und Betreff jeder verarbeiteten Mail.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | BCAT - Historische Mail-Analyse (inaktiv): `store_mail_event`<br>CAT - Postfach Pipeline: `store_mail_event`<br>MPA - Projekt-Ablage: `store_mail_event` |
| liest | KGW - KNOW Query Gateway: `find_mail_events, get_mail_events_summary, get_sender_profile`<br>MPA - Projekt-Ablage: `find_mail_events` |
<details><summary>11 Spalten</summary>
`id` BIGSERIAL, `message_id` TEXT, `mailbox` TEXT, `sender` TEXT, `subject` TEXT, `received_at` TIMESTAMPTZ, `category` TEXT, `assigned_project_key` TEXT, `workflow_status` TEXT, `raw_signals` JSONB, `last_processed_at` TIMESTAMPTZ
</details>
#### `sender_observations`
Absender-Profil je Postfach und Zeitfenster.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | ASP - Absender-Profil Backfill: `upsert_sender_observation` |
| liest | KGW - KNOW Query Gateway: `get_sender_profile, list_sender_domains` |
<details><summary>25 Spalten</summary>
`id` BIGSERIAL, `observation_key` TEXT, `mailbox` TEXT, `email` TEXT, `registered_domain` TEXT, `display_name` TEXT, `window_from` TIMESTAMPTZ, `window_to` TIMESTAMPTZ, `mail_count` INTEGER, `junk_count` INTEGER, `category_histogram` JSONB, `dominant_category` TEXT, `dominant_share` NUMERIC, `avg_confidence` NUMERIC, `absender_typ` TEXT, `mail_art` TEXT, `unsubscribe_url` TEXT, `unsubscribe_mailto` TEXT, `unsubscribe_one_click` BOOLEAN, `first_mail_at` TIMESTAMPTZ, `last_mail_at` TIMESTAMPTZ, `subjects_sample` JSONB, `source` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `platform_domains`
Bekannte Plattform-Domains.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | ASP - Absender-Profil Backfill: `upsert_platform_domain` |
| liest | ASP - Absender-Profil Backfill: `find_platform_domain`<br>HMB - Postfach Backfill (inaktiv): `find_platform_domain`<br>KGW - KNOW Query Gateway: `get_sender_profile, list_sender_domains` |
<details><summary>9 Spalten</summary>
`id` BIGSERIAL, `registered_domain` TEXT, `display_name` TEXT, `status` TEXT, `source` TEXT, `evidence_json` JSONB, `first_seen_at` TIMESTAMPTZ, `last_seen_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `mailbox_grants`
Zugriffsrechte auf Postfächer (delegierter Consent).
**Sensibel:** OAuth-Tokens, verschlüsselt (AES-256-GCM).
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | MPA - Delegated Consent: `upsert_mailbox_grant`<br>MPA - Get Mailbox Access: `upsert_mailbox_grant` |
| liest | MPA - Get Mailbox Access: `get_mailbox_grant`<br>MPA - Projekt-Ablage: `list_active_mailbox_grants` |
<details><summary>13 Spalten</summary>
`id` BIGSERIAL, `grant_key` TEXT, `provider` TEXT, `scope` TEXT, `token_ciphertext` TEXT, `token_iv` TEXT, `token_tag` TEXT, `token_alg` TEXT, `expires_hint` TIMESTAMPTZ, `status` TEXT, `source_workflow` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
### Kontakte und Projekte (KanBau)
#### `contacts`
Kontakte (Firmen, Personen).
**Sensibel:** Personenbezogen: E-Mail, Telefon, Steuer-Nr., USt-IdNr. (IBAN nur als Hash).
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | ASP - Absender-Profil Backfill: `upsert_contact`<br>BCAT - Historische Mail-Analyse (inaktiv): `update_sender_relevance`<br>KGW - KNOW Query Gateway: `update_sender_relevance, upsert_contact` |
| liest | ASP - Absender-Report: `SELECT (Report)`<br>CAT - Postfach Pipeline: `find_workflow_hints`<br>KGW - KNOW Query Gateway: `find_contact_candidates, find_workflow_hints, get_sender_profile`<br>OR - PDF and E-Rechnung Extraction (inaktiv): `find_workflow_hints` |
<details><summary>24 Spalten</summary>
`id` BIGSERIAL, `contact_key` TEXT, `contact_type` TEXT, `display_name` TEXT, `legal_name` TEXT, `normalized_name` TEXT, `email` TEXT, `phone` TEXT, `website` TEXT, `tax_id` TEXT, `vat_id` TEXT, `iban_hash` TEXT, `address_json` JSONB, `source` TEXT, `source_confidence` NUMERIC, `kanbau_address_id` TEXT, `raw_evidence_json` JSONB, `quality_status` TEXT, `first_seen_at` TIMESTAMPTZ, `last_seen_at` TIMESTAMPTZ, `verified_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ, `business_relevance_score` NUMERIC
</details>
#### `contact_aliases`
Schreibweisen von Kontakten.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | KGW - KNOW Query Gateway: `find_contact_candidates` |
<details><summary>11 Spalten</summary>
`id` BIGSERIAL, `alias_key` TEXT, `contact_key` TEXT, `alias_type` TEXT, `alias_value` TEXT, `normalized_value` TEXT, `source_workflow` TEXT, `confidence` NUMERIC, `evidence_json` JSONB, `quality_status` TEXT, `created_at` TIMESTAMPTZ
</details>
#### `contact_project_relations`
Kontakt ↔ Projekt.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>12 Spalten</summary>
`id` BIGSERIAL, `relation_key` TEXT, `contact_key` TEXT, `project_key` TEXT, `relation_type` TEXT, `confidence` NUMERIC, `source_workflow` TEXT, `evidence_json` JSONB, `valid_from` DATE, `valid_to` DATE, `quality_status` TEXT, `created_at` TIMESTAMPTZ
</details>
#### `projects`
KanBau-Projekte mit Adresse.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | KGW - KNOW Query Gateway: `find_project_candidates, get_sender_profile`<br>NEX - Teams Chat Agent: `find_project_candidates` |
<details><summary>17 Spalten</summary>
`id` BIGSERIAL, `project_key` TEXT, `kanbau_project_id` TEXT, `project_number` TEXT, `project_name` TEXT, `short_code` TEXT, `company_context` TEXT, `street` TEXT, `zip` TEXT, `city` TEXT, `full_address` TEXT, `normalized_address` TEXT, `status` TEXT, `raw_kanbau_json` JSONB, `last_synced_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `project_aliases`
Aliase und gelernte Tokens zu Projekten.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | KGW - KNOW Query Gateway: `purge_spf_aliases, upsert_project_alias`<br>MPA - Projekt-Ablage: `reject_project_alias, upsert_project_alias, upsert_project_alias_tokens` |
| liest | KGW - KNOW Query Gateway: `find_project_candidates, get_sender_profile, list_sender_domains`<br>NEX - Teams Chat Agent: `find_project_candidates`<br>PRO - Project Assignment: `find_project_by_alias` |
<details><summary>13 Spalten</summary>
`id` BIGSERIAL, `alias_key` TEXT, `project_key` TEXT, `kanbau_project_id` TEXT, `alias_type` TEXT, `alias_value` TEXT, `normalized_value` TEXT, `source_workflow` TEXT, `confidence` NUMERIC, `evidence_json` JSONB, `quality_status` TEXT, `verified_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ
</details>
#### `project_events`
Ereignisse aus Mails (Verzögerung, Mangel, Termin, Kosten).
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | MPA - Projekt-Ablage: `upsert_project_event` |
| liest | – |
<details><summary>15 Spalten</summary>
`id` BIGSERIAL, `message_id` TEXT, `project_key` TEXT, `mailbox` TEXT, `sender` TEXT, `event_date` TIMESTAMPTZ, `category` TEXT, `affected_unit` TEXT, `urgency` TEXT, `summary` TEXT, `sharepoint_path` TEXT, `quality_status` TEXT, `source_workflow` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `project_folders`
SharePoint-Ordner je Projekt.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>10 Spalten</summary>
`id` BIGSERIAL, `project_key` TEXT, `drive_id` TEXT, `folder_item_id` TEXT, `folder_path` TEXT, `folder_name` TEXT, `verified_at` TIMESTAMPTZ, `source_workflow` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `project_companies`
Kürzel → Gesellschaft.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | RAR - Rechnungsanschrift pruefen: `record_invoice_company` |
| liest | RAR - Rechnungsanschrift pruefen: `get_project_company` |
<details><summary>8 Spalten</summary>
`kuerzel` TEXT, `gesellschaft` TEXT, `quelle` TEXT, `bestaetigt` BOOLEAN, `baustelle` TEXT, `evidence_json` JSONB, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `invoice_company_observations`
Gesellschaft aus Rechnungen beobachtet.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | RAR - Rechnungsanschrift pruefen: `record_invoice_company` |
| liest | RAR - Rechnungsanschrift pruefen: `get_project_company` |
<details><summary>8 Spalten</summary>
`message_id` TEXT, `file_name` TEXT, `kuerzel` TEXT, `gesellschaft` TEXT, `mailbox` TEXT, `source_workflow` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
### Dokumente
#### `document_extractions`
Extraktionen aus Anhängen.
**Sensibel:** Extrahierter Volltext von Anhängen.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | KGW - KNOW Query Gateway: `store_document_extraction` |
| liest | – |
<details><summary>17 Spalten</summary>
`id` BIGSERIAL, `request_id` TEXT, `message_id` TEXT, `mailbox` TEXT, `attachment_id` TEXT, `file_name` TEXT, `content_type` TEXT, `extraction_source` TEXT, `document_type` TEXT, `sender_contact_key` TEXT, `project_key` TEXT, `extracted_text` TEXT, `structured_json` JSONB, `detected_addresses_json` JSONB, `detected_contacts_json` JSONB, `confidence` NUMERIC, `created_at` TIMESTAMPTZ
</details>
### Ankauf und Projektentwicklung (KAF)
#### `property_development_cases`
Ankaufsfälle (~70 Felder).
**Sensibel:** Kaufpreise, Provisionen und Besprechungsmitschriften.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | KGW - KNOW Query Gateway: `find_cases_by_deadline, find_cases_missing_documents, find_cases_needing_next_action, find_cases_with_offer_status, find_property_development_candidates, summarize_property_development_case` |
<details><summary>73 Spalten</summary>
`id` BIGSERIAL, `case_key` TEXT, `name` TEXT, `normalized_name` TEXT, `status` TEXT, `pipeline_status` TEXT, `outcome_status` TEXT, `case_code` TEXT, `case_opened_at` DATE, `folder_name` TEXT, `folder_path` TEXT, `folder_status_source` TEXT, `source_channel` TEXT, `location_city` TEXT, `location_district` TEXT, `street_address` TEXT, `assigned_to` TEXT, `asset_type` TEXT, `process_type` TEXT, `notes` TEXT, `property_size_sqm` NUMERIC, `original_purchase_price_eur` NUMERIC, `average_price_per_sqm_eur` NUMERIC, `commission_including_vat` TEXT, `apartment_count` INTEGER, `development_plan_available` BOOLEAN, `godel_internal` BOOLEAN, `contact_broker` TEXT, `start_date` DATE, `offer_deadline` DATE, `offer_submitted_at` DATE, `last_offer_at` DATE, `offer_count` INTEGER, `review_status` TEXT, `review_result` TEXT, `meeting_transcripts` TEXT, `just_one_week_more` BOOLEAN, `deadline_is_overdue` BOOLEAN, `godel_offer` TEXT, `project_lead` TEXT, `planning` TEXT, `nda_present` BOOLEAN, `loi_present` BOOLEAN, `bplan_document_present` BOOLEAN, `wirtschaftlichkeit_present` BOOLEAN, `purchase_offer_present` BOOLEAN, `building_permit_present` BOOLEAN, `risk_summary` TEXT, `missing_documents` JSONB, `next_action` TEXT, `next_action_due_at` DATE, `last_document_at` DATE, `document_count` INTEGER, `case_readiness` TEXT, `detected_documents` JSONB, `risk_level` TEXT, `readiness_json` JSONB, `source_system` TEXT, `sharepoint_json` JSONB, `raw_evidence_json` JSONB, `quality_status` TEXT, `confidence` NUMERIC, `first_seen_at` TIMESTAMPTZ, `last_seen_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ, `confirmation_token_hash` TEXT, `confirmation_status` TEXT, `confirmation_expires_at` TIMESTAMPTZ, `selected_project_id` TEXT, `created_saleagr_id` TEXT, `created_saleagr_url` TEXT, `confirmation_request_id` TEXT, `confirmed_by` TEXT (Laufzeit-DDL, B6), `confirmed_at` TIMESTAMPTZ (Laufzeit-DDL, B6)
</details>
#### `property_development_documents`
Dokumente je Ankaufsfall.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | KGW - KNOW Query Gateway: `summarize_property_development_case` |
<details><summary>20 Spalten</summary>
`id` BIGSERIAL, `case_key` TEXT, `document_key` TEXT, `sharepoint_item_id` TEXT, `file_name` TEXT, `file_path` TEXT, `folder_segment` TEXT, `document_type` TEXT, `document_date` DATE, `received_at` DATE, `author_or_sender` TEXT, `source_party` TEXT, `extracted_summary` TEXT, `extracted_fields_json` JSONB, `confidence` NUMERIC, `is_current_version` BOOLEAN, `supersedes_document_key` TEXT, `processing_status` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `kaf_review_snapshots`
Prüfstände von Kaufverträgen.
**Sensibel:** Kaufvertragsfelder und Prüfer.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | KAF - Bestätigung Webhook: `complete_kaf_review`<br>KAF - Kaufvertrag Erkennung und Extraktion: `store_kaf_review_snapshot` |
| liest | KAF - Bestätigung Webhook: `get_kaf_review_snapshot` |
<details><summary>22 Spalten</summary>
`id` BIGSERIAL, `case_key` TEXT, `review_schema_version` TEXT, `confirmation_token_hash` TEXT, `confirmation_expires_at` TIMESTAMPTZ, `review_status` TEXT, `request_id` TEXT, `review_to` TEXT, `delivery_mode` TEXT, `original_json` JSONB, `distribution_json` JSONB, `fields_json` JSONB, `units_json` JSONB, `artifacts_json` JSONB, `reviewed_values_json` JSONB, `review_comment` TEXT, `reviewed_by` TEXT, `review_identity_source` TEXT, `review_submission_hash` TEXT, `reviewed_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `kanzlei_field_patterns`
Feldmuster je Notarkanzlei.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | KAF - Kaufvertrag Erkennung und Extraktion: `upsert_kanzlei_field_patterns` |
| liest | KAF - Kaufvertrag Erkennung und Extraktion: `get_kanzlei_field_patterns` |
<details><summary>4 Spalten</summary>
`kanzlei_key` TEXT, `kanzlei_display` TEXT, `patterns` JSONB, `updated_at` TIMESTAMPTZ
</details>
#### `kanzlei_project_relations`
Kanzlei ↔ Projekt.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | KAF - Kaufvertrag Erkennung und Extraktion: `upsert_kanzlei_project_relation` |
| liest | – |
<details><summary>6 Spalten</summary>
`relation_key` TEXT, `kanzlei_key` TEXT, `project_key` TEXT, `project_name` TEXT, `notar_name` TEXT, `updated_at` TIMESTAMPTZ
</details>
#### `place_enrichments`
Adresse → Flurstück, Koordinaten.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>15 Spalten</summary>
`id` BIGSERIAL, `place_key` TEXT, `input_address` TEXT, `match_address` TEXT, `flurstueck` TEXT, `lat` DOUBLE, `lon` DOUBLE, `bundesland` TEXT, `enrichment_json` JSONB, `provenance_json` JSONB, `quality_status` TEXT, `confidence` DOUBLE, `source_workflow` TEXT, `created_at` TIMESTAMPTZ, `refreshed_at` TIMESTAMPTZ
</details>
### Buchhaltung
#### `bel_processed_belege`
Verarbeitete SEPA-Belege.
**Sensibel:** Bank, Konto-Referenz und Beträge.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | BEL - SEPA-Beleg-Verteilung: `confirm_beleg_uploaded, reserve_beleg` |
| liest | – |
<details><summary>15 Spalten</summary>
`id` BIGSERIAL, `beleg_hash` TEXT, `status` TEXT, `bank` TEXT, `account_ref` TEXT, `amount_cents` BIGINT, `sharepoint_path` TEXT, `export_ref` TEXT, `request_id` TEXT, `raw_evidence_json` JSONB, `reserved_at` TIMESTAMPTZ, `uploaded_at` TIMESTAMPTZ, `last_seen_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
### Lernen und Workflow-Gedächtnis
#### `workflow_decisions`
Vorschlag vs. tatsächliche Entscheidung.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>13 Spalten</summary>
`id` BIGSERIAL, `request_id` TEXT, `workflow` TEXT, `reference_type` TEXT, `reference_id` TEXT, `proposed_decision` JSONB, `final_decision` JSONB, `confidence` NUMERIC, `correction_reason` TEXT, `quality_status` TEXT, `created_at` TIMESTAMPTZ, `verified_at` TIMESTAMPTZ, `decision_type` TEXT, `decided_by` TEXT, `updated_at` TIMESTAMPTZ
</details>
#### `workflow_hints`
Gelernte Muster mit Bestätigung.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | ASP - Absender-Profil Backfill: `backfill_workflow_hint`<br>CAT - Postfach Pipeline: `UPDATE … SET notified_at, pending_notification, upsert_workflow_hint`<br>KAN - Kanbau API Gateway: `upsert_workflow_hint`<br>KGW - KNOW Query Gateway: `promote_workflow_hint, reject_workflow_hint, upsert_workflow_hint`<br>KNOW - Hint Confirmation Webhook: `promote_workflow_hint, reject_workflow_hint`<br>OR - PDF and E-Rechnung Extraction (inaktiv): `upsert_workflow_hint` |
| liest | CAT - Postfach Pipeline: `find_workflow_hints, get_pending_hint_proposals`<br>KGW - KNOW Query Gateway: `find_workflow_hints, get_pending_hint_proposals, get_sender_profile, summarize_workflow_lessons`<br>OR - PDF and E-Rechnung Extraction (inaktiv): `find_workflow_hints` |
<details><summary>15 Spalten</summary>
`hint_key` TEXT, `workflow` TEXT, `hint_type` TEXT, `pattern_key` TEXT, `pattern_value` TEXT, `suggested_action` JSONB, `observation_count` INTEGER, `confidence` NUMERIC, `quality_status` TEXT, `pending_notification` BOOLEAN, `notified_at` TIMESTAMPTZ, `confirmed_at` TIMESTAMPTZ, `confirmed_by` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `workflow_hint_config`
Ab wann die KI übersprungen wird.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | BCAT - Historische Mail-Analyse (inaktiv): `update_sender_relevance`<br>CAT - Postfach Pipeline: `find_workflow_hints, get_pending_hint_proposals, upsert_workflow_hint`<br>KAN - Kanbau API Gateway: `upsert_workflow_hint`<br>KGW - KNOW Query Gateway: `find_workflow_hints, get_hint_config, get_pending_hint_proposals, summarize_workflow_lessons, update_sender_relevance, upsert_workflow_hint`<br>OR - PDF and E-Rechnung Extraction (inaktiv): `find_workflow_hints, upsert_workflow_hint` |
<details><summary>10 Spalten</summary>
`config_key` TEXT, `min_observations_for_skip_ai` INTEGER, `priority_level` TEXT, `preferred_model` TEXT, `safety_floor` BOOLEAN, `relevance_weight` NUMERIC, `outlook_category` TEXT, `requires_action` BOOLEAN, `description` TEXT, `updated_at` TIMESTAMPTZ
</details>
#### `kanbau_method_examples`
Beispiele je KanBau-API-Methode.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | KAN - Kanbau API Gateway: `upsert_kanbau_method_example` |
| liest | KGW - KNOW Query Gateway: `get_kanbau_method_example` |
<details><summary>7 Spalten</summary>
`method_key` TEXT, `swagger_ref` TEXT, `example_request` JSONB, `example_response` JSONB, `validated_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
### Agent
#### `agent_capabilities`
Fähigkeiten eines Agenten.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>14 Spalten</summary>
`id` BIGSERIAL, `scope_key` TEXT, `capability_key` TEXT, `title` TEXT, `description` TEXT, `intent_examples` JSONB, `workflow_name` TEXT, `workflow_operation` TEXT, `read_or_write` TEXT, `risk_level` TEXT, `requires_approval` BOOLEAN, `enabled` BOOLEAN, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `agent_context_documents`
Kontextdokumente für Agenten.
**Sensibel:** Volltext eingelesener Dokumente.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>8 Spalten</summary>
`id` BIGSERIAL, `scope_key` TEXT, `source_path` TEXT, `document_type` TEXT, `title` TEXT, `content` TEXT, `content_hash` TEXT, `last_ingested_at` TIMESTAMPTZ
</details>
#### `agent_execution_log`
Protokoll der Agent-Aufrufe.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | – |
| liest | – |
<details><summary>10 Spalten</summary>
`id` BIGSERIAL, `request_id` TEXT, `agent_scope` TEXT, `user_request` TEXT, `selected_capability` TEXT, `workflow_called` TEXT, `inputs_json` JSONB, `status` TEXT, `errors_json` JSONB, `created_at` TIMESTAMPTZ
</details>
### Browser-Automation (BSB)
#### `session_providers`
Login-Abläufe für Browser-Automation.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | BSB - Discover Session: `upsert_session_provider` |
| liest | BSB - Get Session: `get_session_provider` |
<details><summary>10 Spalten</summary>
`provider_id` TEXT, `display_name` TEXT, `login_url` TEXT, `auth_type` TEXT, `steps_json` JSONB, `credential_key` TEXT, `max_session_age_minutes` INTEGER, `notes` TEXT, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
#### `browser_sessions`
Gespeicherte Browser-Sessions.
**Sensibel:** Session-Daten als JSON; am Schema nicht erkennbar, ob verschlüsselt.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | BSB - Execute Login: `upsert_browser_session` |
| liest | BSB - Get Session: `get_browser_session` |
<details><summary>5 Spalten</summary>
`provider_id` TEXT, `session_data` JSONB, `expires_at` TIMESTAMPTZ, `created_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
### LLM-Worker
#### `llm_workers`
Registrierte lokale LLM-Worker.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | LLM - Job Poll: `claim_llm_job`<br>LLM - Worker Registry: `update_llm_worker_status, upsert_llm_worker` |
| liest | KGW - KNOW Query Gateway: `find_active_llm_workers`<br>LLM - Router: `find_active_llm_workers` |
<details><summary>12 Spalten</summary>
`id` BIGSERIAL, `mac` TEXT, `host` TEXT, `model` TEXT, `tier` TEXT, `tier_rank` SMALLINT, `status` TEXT, `hardware_json` JSONB, `last_seen_at` TIMESTAMPTZ, `registered_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ, `endpoint` JSONB
</details>
#### `llm_worker_blocklist`
Gesperrte Worker.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | LLM - Worker Registry: `blocklist_llm_worker` |
| liest | – |
<details><summary>6 Spalten</summary>
`id` BIGSERIAL, `mac` TEXT, `host` TEXT, `reason` TEXT, `hardware_json` JSONB, `blocked_at` TIMESTAMPTZ
</details>
#### `llm_jobs`
Job-Board für lokale LLMs.
**Sensibel:** Prompt und Ergebnis im Klartext.
| Zugriff | Workflows und Operationen |
|---|---|
| schreibt | LLM - Job Poll: `claim_llm_job`<br>LLM - Job Submit: `enqueue_llm_job`<br>LLM - Result Handler: `complete_llm_job`<br>LLM - Router: `cancel_llm_job, enqueue_llm_job` |
| liest | KGW - KNOW Query Gateway: `find_active_llm_workers`<br>LLM - Router: `find_active_llm_workers, get_llm_job` |
<details><summary>16 Spalten</summary>
`id` BIGSERIAL, `job_id` TEXT, `prompt` TEXT, `min_tier` TEXT, `min_tier_rank` SMALLINT, `status` TEXT, `assigned_to` TEXT, `model` TEXT, `tier` TEXT, `result` TEXT, `duration_ms` INTEGER, `error_message` TEXT, `created_at` TIMESTAMPTZ, `assigned_at` TIMESTAMPTZ, `completed_at` TIMESTAMPTZ, `updated_at` TIMESTAMPTZ
</details>
+1 -1
View File
@@ -29,7 +29,7 @@ Deterministisch:
- `prefer: largest` wählt die GPU mit dem meisten VRAM (für LLM-Rollen).
- `prefer: smallest_fitting` wählt die kleinste passende GPU (für Embedding und Rerank). So bleiben große Karten frei.
3. Rollen mit `share_with: <rolle>` teilen sich die GPU (bzw. bei `gpu: false` den Node) einer bereits platzierten Rolle. Ein Beispiel ist TTS auf `gpu-flex`.
4. Rollen mit `gpu: false` bzw. `cpu_fallback: true` werden ohne passende GPU den CPU-Nodes zugewiesen, sortiert nach RAM.
4. Rollen mit `gpu: false` bzw. `cpu_fallback: true` werden ohne passende GPU den CPU-Nodes zugewiesen, sortiert nach RAM. Nodes mit `cpu_roles: false` (Arbeitsplätze, Kiosk) sind davon ausgenommen. Dort wird nur die eigene GPU verplant.
5. Pro zugewiesener Rolle werden die Katalogmodelle gelistet, die passen (Engine, VRAM, CC).
6. Ergebnis: `assignments[{role, node, gpu_index?, models_fitting[]}]` und `unplaced[{role, reason}]`.
+100
View File
@@ -0,0 +1,100 @@
# SPEC-ontology: Ontologie der Datenhaltung
Paket: `knaster.ontology`. Quelle: `ontology/know.yaml`. CLI: `knaster ontology …`.
Die Ontologie beschreibt, *was* in der Datenhaltung steht (Projekt, Kontakt, Ankaufsfall, Mail …) und wie die Begriffe zusammenhängen. Tabellen sind nur die Ablage dahinter. Sie ist die eine Quelle der Wahrheit; Turtle, Graph und Ansicht werden daraus erzeugt, nie von Hand gepflegt.
## 1. Drei Ebenen, drei Pflegeregeln
| Ebene | Inhalt in `know.yaml` | Wie sie sich ändert |
|---|---|---|
| Kern | `classes`, `relations`, `modules` | bewusst, per Commit (Review wie Code) |
| Vokabulare | `vocabularies.*.concepts`, `alt`, `rejected` | wachsen über Vorschläge der Service-Routine → Freigabe → `knaster ontology apply` |
| Instanzen | – (stehen in den Daten) | automatisch; unsichere Verknüpfungen später ebenfalls über Vorschläge |
Ohne diese Regeln zerfällt ein Vokabular wieder in mehrere Listen (KNOW-Befund B20). Deshalb legt nie ein Werkzeug oder LLM selbstständig Klassen oder Konzepte an; es schlägt sie vor.
## 2. Modell (`know.yaml`)
```yaml
id: know
version: 3 # steigt bei jeder Übernahme von Entscheidungen
namespace: urn:mygodel:know#
modules: {mail: Mail & Absender, …}
classes:
Mail:
label: Mail
module: mail
table: mail_events # fehlt = implizite Klasse (nur Wert in Spalten) oder externes System
key: message_id
definition: …
sensitive: … # steuert Datenschutz-Regeln (geplant: PSE/lokal)
subclass_of: Dokument # optional
attributes: {message_id: TEXT, …} # Spalten der Tabelle, Abbild des KNOW-Schemas
relations:
- {source: Mail, name: zugeordnetZu, target: Projekt, via: mail_events.assigned_project_key, kind: key}
# kind: key (gemeinsamer Schlüssel) | soft (Wertabgleich) | external (ID in KanBau/SharePoint/M365)
vocabularies:
MailKategorie:
label: Mail-Kategorie
class: MailKategorie # optional: Konzepte sind Ausprägungen dieser Klasse
columns: [mail_events.category, workflow_hints.hint_type, workflow_hint_config.config_key]
concepts:
buchhaltung: {label: Rechnungen, alt: [Buchhaltung]}
default: {label: Standard, technical: true}
rejected: [MPA] # bewusst kein Konzept
ignore: [sql:ASP - Absender-Report:…] # bewusst hingenommene Befunde
```
`Ontology.validate_model()` prüft: Module, Oberklassen und Beziehungsenden existieren; jede Tabelle gehört zu höchstens einer Klasse; Vokabel-Spalten existieren als Attribut; kein Name zeigt auf zwei Konzepte; kein verworfener Wert ist zugleich Konzept. `knaster validate` prüft das in der CI und warnt, wenn `know.ttl` nicht zur YAML passt.
## 3. Abgleich mit n8n (`check`)
Eingabe ist eine Momentaufnahme der n8n-Workflows (`build_snapshot`, nur GET `/api/v1/workflows` bzw. eine JSON-Datei):
- **KNOW-Spezifikation** aus dem Code des KNOW-Workflows: erlaubte Operationen (`const allowed`), Schema (`CREATE TABLE`, `ALTER … ADD COLUMN`), pro Operation geschriebene Spalten und dabei gesetzte Literale, Seeds (reine Literal-INSERTs).
- **Aufrufe** von KNOW mit Operation und Payload-Ausdruck. Wird die Operation erst zur Laufzeit gesetzt, werden Kandidaten aus den Literalen des Workflows abgeleitet (`inferred`).
- **Direktes SQL**: Postgres-Knoten außerhalb von KNOW, die das Schema `knowledge` ansprechen.
Ergebnisse (`OntologyFinding`, sortiert nach Schwere):
| Art | Schwere | Vorschlag? |
|---|---|---|
| `unmapped_table` – Tabelle ohne Klasse | medium | ja: `add_class` |
| `unmapped_column` – Spalte ohne Attribut | low | ja: `add_attribute` |
| `vocab_value` – Wert in einer Vokabel-Spalte, der kein Konzept ist | medium (low, wenn nur aus inaktiven Workflows) | ja: aufnehmen, zuordnen oder verwerfen |
| `unknown_operation` – Aufruf einer Operation, die KNOW nicht kennt | high (aktiv) / low | nein, Befund |
| `direct_sql` – SQL an KNOW vorbei | medium (schreibend) / low | nein |
| `missing_table` / `missing_column` – Ontologie verweist auf etwas, das KNOW nicht anlegt | medium / low | nein |
| `concept_without_producer` – Konzept, das keine *feste* Zuordnung erzeugt | info | nein |
| `no_write_operation` / `no_active_writer` – Klasse ohne Schreibweg | info | nein |
Werte werden nur aus Schlüsseln der obersten Payload-Ebene gelesen (verschachtelte JSON-Felder gehören zu einer JSON-Spalte). „Fest“ heißt: Literal oder Zuordnungstabelle mit Literal-Rückfall; bei freien Werten (LLM-Ausgabe) trifft die Prüfung keine Aussage. Entschiedene Schlüssel (Tabelle `ontology_decisions`) und `ignore` werden übersprungen.
## 4. Service-Routine und Freigabe
Schritt 8 der Routine (`MaintenanceAgent.run_routine`) ruft den Abgleich auf, wenn `ontology_path` existiert und eine Quelle konfiguriert ist (`KNASTER_ONTOLOGY_SNAPSHOT` oder `N8N_HOST` + `N8N_API_KEY`):
- Befunde ohne Vorschlag erscheinen als Zeilen „Ontologie [schwere]: …“ (höchstens `ontology_max_findings`, Standard 15).
- Befunde mit Vorschlag werden zu Vorschlägen der Art `ontology_change` (Richtlinie: immer Freigabe, Risiko niedrig), dedupliziert über `ontology:<key>`.
- `genehmige P-x` → Entscheidung `accept`; `ordne P-x <konzept> zu` → `map` (Wert wird alternative Schreibweise des Konzepts); `lehne P-x ab: …` → `reject` (Wert landet in `rejected` bzw. der Schlüssel in `ignore`).
- Entscheidungen liegen in `ontology_decisions`; ein entschiedener Schlüssel wird nicht erneut vorgeschlagen.
- `knaster ontology apply` schreibt offene Entscheidungen in `know.yaml`, erhöht `version`, prüft das Ergebnis und markiert die Entscheidungen als übernommen. Danach `knaster ontology export --ttl ontology/know.ttl` und committen.
Fällt n8n aus, meldet die Routine „Ontologie-Abgleich nicht möglich“ und läuft weiter.
## 5. CLI
| Befehl | Zweck |
|---|---|
| `knaster ontology validate` | Modell prüfen |
| `knaster ontology export --ttl F --graph F --html F` | Turtle (OWL + SKOS), Graph-JSON, eigenständige Ansicht (`ontology/viewer.html.tmpl`) |
| `knaster ontology snapshot --out F` | Workflows lesen und speichern (enthält Workflow-Code, nicht committen) |
| `knaster ontology check [--snapshot F] [--json]` | Abgleich; Exit 1 bei Befunden der Schwere high |
| `knaster ontology apply [--dry-run]` | freigegebene Entscheidungen übernehmen |
## 6. Grenzen und nächste Schritte
- Die Prüfung liest Code, keine Daten: ob Tabellen tatsächlich leer sind, zeigt erst eine Zählung in der Datenbank.
- Weiche Bezüge (z. B. Absender ↔ Kontakt) werden noch nicht automatisch als Verknüpfungsvorschläge erzeugt; geplant über Embeddings mit Schwelle (Instanz-Ebene).
- Aus der Ontologie sollen erzeugt werden: Sichten pro Klasse („Projekt 360°“), JSON-Schemas für Skill-Ausgaben, Werkzeugkatalog für Hermes/NEX, Datenschutz-Regeln aus `sensitive`.