Zum Inhalt

Multi-Tenancy-Fundament — Architektur-Konzept (v5.3.4 — Pattern-Reife-Quote-Trendlinie)

Status: Konsolidierter Entwurf, implementierungsreif — alle offenen Punkte geklärt Zielversion: v1.0.0 (Blöcke 1–16 + 18/19/20) + v1.0.1/v1.1.0 (Block 17) Aufwand: ~427h Refined-Boden für v1.0.0 + ~49h für Block 17 (Post-Launch); v1.3.0-Cleanup-Wellen ~13h Real / ~42.5h Refined separat ausgewiesen in §17.5 Vorgänger: v0.8.0 Multi-Product Parallel-Dokumente: Konnektor-Roadmap v2, Knowledge-Hub-Modul-Konzept v2 Letzte Aktualisierung: 2026-05-02 (v5.3.4: Pattern-Reife-Quote-Trendlinie pro Block-Typ in §17.2a Nr. 7 ergänzt; v1.3.0-Welle als §17.5 separat ausgewiesen; §17.2a-Schluss-Satz von „449h" auf „427h" konsolidiert via TODO-Konzept-02-Auflösung)


Änderungshistorie v3 → v4

Änderung Grund Betroffene Abschnitte
Lizenz wird zum Installations-Marker Kein Feature-Gating zwischen luki und AVS nötig, vertraglich geregelt §7, §8, §17
Einstufige Modul-Freischaltung via tenant_packages Wenn luki nicht betreibt, entfällt der globale Enable-Schritt (Stufe 1) §8
Chatbot-Quota nur noch Operator-Entscheidung Keine Lizenz-Obergrenze, AVS hat Vollzugriff §4.4
Vue 3 + Composition API ohne Pinia festgelegt Entscheidung aus Dialog-Block 1 §9
Keycloak-Bootstrap via Init-Scripts Entscheidung aus Dialog-Block 3 §15
Qdrant-Backup Hybrid-Strategie Entscheidung aus Dialog-Block 3 §6.5
Template-Versionierung explizit als Snapshot Entscheidung aus Dialog-Block 3 §5
Evaluation-Questions auf Template-Ebene Entscheidung aus Dialog-Block 3 §3, §5
Konnektor Semver + Auto-Migration Entscheidung aus Dialog-Block 3 §13
Credential-basierter Rate-Limit-Scheduler Entscheidung aus Dialog-Block 3 §13
Lizenz-Server Stufe 2 entfernt Redundant ohne luki als Betreiber, in Backlog verschoben §19
Aufwand: ~400h → ~380h Vereinfachungen aus Feature-Gating-Rückbau §17
Konnektor-API als interner Contract (SDK-Stufe 2) Dialog-Klärung: AVS als designierter Zweit-Entwickler, stabile API, kein öffentliches SDK §13.10 (neu)
Deprecation-Policy: 6 Monate + gestufte Hilfestellung Dialog-Klärung: 6 Monate normal, 30 Tage Notfall, dreistufig §13.11 (neu)
AVS-Realitätsdaten: Confluence bestätigt AVS nutzt Confluence, Annahme → Fakt §19
AVS-Konnektor-Entwickler-Guide Nebeneffekt SDK-Stufe 2: +8h Dokumentations-Aufwand §17
Parallel-Entwicklungs-Strategie (Long-Lived-Branch) Demo läuft auf main, Plattform entsteht auf platform/v1.0.0, paralleles Deployment §15a (neu)
Go-Live mit frischer Leinwand Keine Daten-Migration, nur Provisioning; Widget muss aktualisiert werden §16
Aufwand: ~388h → ~384h +4h Parallel-Dev-Setup, -8h Migration durch frische Leinwand §17

Änderungshistorie v4 → v4.1 (Review-Ergänzungen vor Block 1.1)

Änderung Grund Betroffene Abschnitte
Zyklus chatbot_sourcescredentials via ALTER TABLE aufgelöst credentials kann erst nach chatbot_sources FK-referenziert werden ohne Fehler bei sequentieller CREATE-Reihenfolge §4.1
Denormalisiertes tenant_id auf chatbot_sources und sync_jobs Invariante aus §3 ("tenant_id auf allen Tabellen") konsequent umsetzen; vereinfacht RLS und Tenant-Scans §4.1, §4.6
Neue Tabelle platform_audit_log Gegenstück zu vendor_access_log für Operator- und Tenant-Aktionen (bisher nur in UI §9.2 erwähnt, keine Tabelle definiert) §4.1

Änderungshistorie v4 → v5

Änderung Grund Betroffene Abschnitte
Block 2 Design-Entscheidungen finalisiert Implementierungsreife für Phase A Block 2 §2.6 (neu), §11, §15.4 (neu)
First-Broker-Login-Flow "Create User If Unique" als Sicherheits-Default Verhindert Cross-Tenant-Account-Linking bei Multi-IdP-Setup §2.6, §15.4
Widget-Traffic umgeht Keycloak (Origin-basiert statt Token) Pragmatismus für anonyme End-Nutzer, keine Token-in-HTML §12.4 (neu)
Vendor-Audit via Polling der Keycloak Admin-Events-API Kein Custom-SPI, portabel, ausreichend für "Notfall-Login"-Erkennung §15.4
Block 17 — Tenant-SSO-Self-Service als Post-Launch-Block Tenant kann IdP selbst einrichten; AVS/luki unterstützen als Fallback §17.4 (neu), §18 (Phase D), §21 (neu)
Aufwandsschätzung v1.0.0 unverändert 384h, +49h für Block 17 separat Block 17 fällt nicht in v1.0.0-Zeitbudget §17.4, §21

Änderungshistorie v5 → v5.1

Änderung Grund Betroffene Abschnitte
Container-Strategie als §9.3 expliziert: Monolith mit Embedded Vue-Komponenten Block-2-Retro: die Monolith-Entscheidung lag nur implizit in §9.1, sorgte beim Deployment von Block 2 für Orientierungsbedarf §9.3 (neu)

Änderungshistorie v5.1 → v5.2

Änderung Grund Betroffene Abschnitte
§6a (neu) — Ingestion- & Retrieval-Pipeline mit drei Unter-§§: §6a.1 Docling, §6a.2 BGE-M3 + Hybrid Retrieval, §6a.3 AST-Aware Chunking Qualitäts-Einschübe aus AVS-Chatbot-Evaluation: Docling liefert 97,9 % Tabellen-Extraktions-Genauigkeit, BGE-M3 adressiert exakte Token-Matches (Paragraphen-Nummern, Error-Codes) per Hybrid-Retrieval, AST-Chunking als Enabler für Code-Doku §6a (neu), §17.2 (drei neue Blöcke), §18 (Phase C erweitert)
§13.3a (neu) — Inkrement-Sync via Merkle-Tree-Snapshots ~99,8 % Reduktion der Embedding-Last bei Konnektor-Syncs (Root-Hash-Early-Exit, Diff-basierte Re-Embeddings); Pflicht-Infrastruktur ab v1.0.0 mit Multi-Konnektor-Szenarien §13.3a (neu), §13.2 (Base-Interface um fetch_manifest + content_hash erweitert), §17.2 (Block 13 +9h)
Aufwand: ~384h → ~449h für v1.0.0 Vier neue Einschübe: +9h Block 13 Merkle-Sync, +18h Block 18 Docling, +25h Block 19 BGE-M3, +13h Block 20 AST-Chunking = 65h total §17.2, §18 Phase C, Konzept-Header
Demo-Strang (avs-chatbot) wird nicht mehr weiterentwickelt Strategie-Entscheidung vom 2026-04-23: alle Quality-Einschübe landen direkt in Platform v1.0.0, kein avs-chatbot v0.10.0-Zwischen-Release §15a.6 (implizit: Go-Live-Strategie bleibt unverändert, aber Demo-Evolution ist eingefroren)

Änderungshistorie v5.2 → v5.3

Änderung Grund Betroffene Abschnitte
Block-7-Split: 7a UI-Framework-Scaffolding als Block-5-Dependency vorgezogen Der Template-Update-Dialog in Block 5 braucht den Vue-3-Stack produktiv (Build-System, Auth-Middleware-Integration, Base-Layout); wenn dieses Scaffolding erst in Block 7 entstünde, wäre Block 5 nicht abschließbar. Das Scaffolding wird daher mit Block 5 gekoppelt; Block 7 reduziert sich auf reinen Content (Tenants-/Template-/Modul-/Vendor-Audit-Views) §9.3 (zwei Sätze umformuliert), §17.2 (Block-5-Zeile um 7a-Dependency erweitert, Block-7-Zeile auf 7b-Content reduziert), §18 (Phase-A-Block-5 enthält 7a, Phase B enthält nur noch 7b-Content)
§18 Phasen-Struktur: 4 → 5 Phasen angeglichen an Roadmap Konzept-§18 führte v5.2 vier Phasen (A/B/C/D mit D=Post-Launch), die Roadmap hat fünf (A/B/C/D/E, wobei D=Deployment+Go-Live und E=Post-Launch). Divergenz zwischen Konzept und Roadmap war seit Roadmap-Struktur-Sync (2026-04-23) Review-Aufwand ohne Nutzen. Roadmap ist kanonische Quelle für Phase-Namen und Block-Listen §18 (komplett neu geschnitten), §17.2 (Phase-Verweise aktualisiert)
§17.2 Summen-Audit: Einzelposten ~475h vs. Phasen-Total 449h aufgeschlüsselt Altlast seit v5.1 (dort als "offene Klärung" markiert), in v5.2 durch die Quality-Einschübe sichtbarer geworden. In v5.3 dokumentativ aufgelöst via neuer §17.2a Reconciliation-Abschnitt — ohne korrektive Zahlen-Änderungen an Einzelposten. Die 449h-Phase-Total-Zahl bleibt maßgeblich für Zeitbudget und Fortschrittsmessung §17.2a (neu), §17.2 (Tabelle unverändert ausser 13a-Entfernung in separater Zeile unten)
Block-13-vs-13a-Merge: 13a-Zeile in §17.2 entfernt (war in Upload-Scope von Block 13 doppelt gezählt) Aus Roadmap-Sync-v2-Audit (2026-04-23): die 8h "13a Upload-Konnektor-Refactoring" waren bereits in der Block-13-Zeile "Framework + Upload + Credentials" enthalten. Doppelte Zählung ist eine der identifizierten Überlapp-Quellen der ~475h-vs-449h-Diskrepanz und wird hier aufgelöst §17.2 (13a-Zeile entfernt, Block-13-Zeile klarstellend formuliert), §17.2a (als eine von mehreren identifizierten Überlapp-Quellen benannt)

Änderungshistorie v5.3 → v5.3.1

Änderung Grund Betroffene Abschnitte
MediaWiki-Konnektor firm auf v1.1.0 festgelegt AVS-Realität zu MediaWiki unbestätigt; Confluence hat Priorität; 24h-Aufwand-Einsparung im v1.0.0-Budget. Vorher in Konnektor-Roadmap §2.3 als "v1.0.0 oder v1.1.0 je nach Zeitbudget" geführt — die Entscheidung wird jetzt verbindlich. Die §17.2a-Überlapp-Quelle Nr. 4 (MediaWiki-v1.0.0-vs-v1.1.0-Ambiguität) ist damit aufgelöst §13.6 (Verweis auf Konnektor-Roadmap §2.3 ergänzt); §17.2a (Überlapp-Quelle Nr. 4 als in v5.3.1 aufgelöst markiert); Konnektor-Roadmap §2.3 (firm v1.1.0-Zuordnung, eigenständige Änderungshistorie v2→v2.1)

Änderungshistorie v5.3.1 → v5.3.2

Änderung Grund Betroffene Abschnitte
v1.0.0-Budget von 449h auf 425h reduziert Konsequenz der MediaWiki-v1.1.0-Verschiebung aus v5.3.1: 24h sind aus v1.0.0-Scope raus, das Konzept-Header-Budget spiegelt das jetzt konsistent wider Konzept-Header (Aufwand-Zeile); Roadmap (Fortschritts-Box-Nenner); gleichzeitiger Commit Roadmap-Sync v3

Änderungshistorie v5.3.2 → v5.3.3

Änderung Grund Betroffene Abschnitte
Block 11 Widget: 12h → 14h Refined Auflösung TODO-Konzept-01 (Drift-Status-Audit 2026-04-30): §17.2 führte 12h, frühere Roadmap-Status-Box 16h. Per Lutz-Entscheidung am 2026-05-01 verbindlich auf den Mid-Point 14h Refined konsolidiert. Der +2h-Aufschlag berücksichtigt das Sub-Route-Pattern aus Block 8.6 plus den Widget-Schreib-Pfad-Refactor aus Block-8.7-Discovery (Feedback-Endpoint zeigt nach AVS-Demo-Schema, soll auf Platform). v1.0.0-Budget wandert von ~425h auf ~427h innerhalb der Rundungstoleranz §17.2 Tabellenzeile (12h → 14h); §17.2a Überlapp-Quelle Nr. 6 (neu); Konzept-Header (Aufwand-Zeile); Roadmap (Pfad-A-Header und Block-11-Aufwand-Zeile auf 14h Refined vereinheitlicht); offene-todos.md (TODO-Konzept-01 archiviert)

Änderungshistorie v5.3.3 → v5.3.4

Änderung Grund Betroffene Abschnitte
Pattern-Reife-Quote-Trendlinie pro Block-Typ in §17.2a Nr. 7 TODO-Konzept-02-Auflösung (Mini-Run nach v1.3.0-Tag, 2026-05-02): Pauschalwert „60 % Pattern-Reife-Quote" aus pre-v5.3.4-Erwartung wird durch fünf Datapoints (Block 8 Sub-Welle, Block 11, v1.3.0-D2/D1/E) zu einer differenzierten Quote-Tabelle pro Block-Typ kalibriert. Cleanup-Welle Backend ~25 %, Cleanup-Welle Frontend ~30 %, Foundation-Reuse hoch ~40 %, Foundation-Reuse + neuer Code ~50 %, Foundation-erweiternd 50–80 %. Konsequenz für Block-13-Schätzung sichtbar gemacht. §17.2a Reconciliation Nr. 7 (neu)
v1.3.0-Welle (D2 + D1 + E) als §17.5 separat ausgewiesen Cleanup-Wellen sind systematisch außerhalb des §17.2-Original-Plans entstanden (Wartungsschuld + Customer-Wert-Schluss). Mit ~13h Real / ~42.5h Refined wären sie in §17.2 ein Fremdkörper. Achse-Trennung: §17.2-Refined-Boden bleibt 427h für v1.0.0; v1.3.0-Welle wird in §17.5 separat geführt. Roadmap-Fortschritts-Box rechnet weiter gegen 427h-Hauptachse. §17.5 (neu); §17.2a-Schluss-Satz konsolidiert
§17.2a-Schluss-Satz von „~449h" auf „~427h" konsolidiert Spurenrest aus pre-v5.3.3-Header (vorher hieß es „~449h Phase-Total" als maßgebliche Zahl). v5.3.3-Eintrag Nr. 6 hatte den Header bereits auf 427h gezogen, der Folge-Satz blieb aber auf der Pre-MediaWiki-Verschiebungs-Zahl 449h hängen. Jetzt konsistent mit Header und Roadmap-Fortschritts-Box. §17.2a Schluss-Satz

1. Ziel & Abgrenzung

Was wir bauen

Ein Fundament, das die aktuelle Chatbot-Software zur mandantenfähigen Plattform umbaut — mit drei Rollenschichten (Vendor / Operator / Tenant), sauberer Datenisolation pro Tenant, Chatbots als zentraler Arbeitsentität und Konnektoren als erstklassiger Ingestion-Infrastruktur.

Der Chatbot bleibt Produktkern. Konnektoren erweitern ihn um die Fähigkeit, Wissen aus externen Systemen zu beziehen. Das Fundament ist so gebaut, dass zukünftige Module (Knowledge-Hub, Ticket-Eskalation, weitere Konnektoren) ohne Architektur-Änderung andocken.

Geschäftsmodell-Leitbild

luki liefert AVS die Plattform als Software-Asset ohne technische Drosseln. AVS betreibt und konfiguriert sie für seine Kurverwaltungen selbst. Support-Umfang und Feature-Scope werden vertraglich zwischen luki und AVS geregelt, nicht technisch erzwungen. Die Plattform kennt keine "Lizenz-Barriere" — sie kennt nur die Pakete, die AVS für seine Tenants schnürt.

Drei-Schichten-Modell

┌──────────────────────────────────────────────────────────┐
│  KONSUMIERENDE SCHICHT                                   │
│  Chatbot (heute) • Knowledge-Hub (v2.0.0) • API          │
└──────────────────────────┬───────────────────────────────┘
┌──────────────────────────▼───────────────────────────────┐
│  INDEXIERUNGS-SCHICHT                                    │
│  Qdrant + PostgreSQL + Metadaten mit source_type, author,│
│  updated_at, permissions, tenant_id, chatbot_id, ...     │
└──────────────────────────▲───────────────────────────────┘
┌──────────────────────────┴───────────────────────────────┐
│  INGESTION-SCHICHT (Konnektoren)                         │
│  Upload • Confluence • Wiki • SharePoint • Teams • ...   │
└──────────────────────────────────────────────────────────┘

Jede Schicht ist austauschbar. Ein neuer Konnektor ändert nichts an der konsumierenden Schicht; ein neues UI-Modul ändert nichts an der Ingestion.

Was wir nicht bauen

  • Kein horizontaler SaaS-Pivot (bleibt in Tourismus-/Verwaltungs-Domäne)
  • Keine Cloud-Migration (AVS on-premise)
  • Kein Knowledge-Hub-Modul in v1.0.0 (siehe eigenes Konzept)
  • Keine zweite Konnektor-Welle in v1.0.0 (SharePoint, Teams, Jira kommen in v1.1.0+)
  • Kein Lizenz-Server Stufe 2 (in Backlog, Trigger: externer Kunde außer AVS)
  • Keine Endkunden-Portale für Touristen/Gäste
  • Keine multi-Distributor-Struktur

2. Rollen & Terminologie

2.1 Die drei Rollenebenen

Rolle Wer Verantwortung Sichtbarkeit
Vendor luki-net Software-Entwicklung, Support, Debugging, Tests, Release-Zyklen Systemweit, alle Tenants (auditiert)
Operator AVS Plattform-Betrieb, Tenant-Lebenszyklus, Template-Pflege, Modul- & Paket-Konfiguration Systemweit innerhalb der eigenen Instanz
Tenant Kurverwaltung (Gemeinde-Verwaltungseinheit) Erstellt eigene Chatbots, Wissensquellen, Konfiguration Ausschließlich eigene Daten und Chatbots

2.2 Vendor-Zugänge (luki-net-Systemrecht)

Drei separate Rollen, jede mit eigenem Konto und Audit-Log:

  1. vendor-support — Dauer-Account, Read-Only auf Logs/Metriken. Keine Tenant-Query-Inhalte, nur pseudonymisierte Samples. AVS kann jederzeit deaktivieren.
  2. vendor-breakglass — Notfall-Full-Admin. Jeder Login triggert E-Mail-Audit. Sessions 2h-begrenzt. AVS kann pausieren.
  3. vendor-tunnel — Reverse-Tunnel, nur aktiv solange AVS-Operator ihn explizit freigeschaltet hat.

AVV-Konsequenz: Alle drei Rollen werden im AVV zwischen luki und AVS benannt. Ausgangsbasis ist die Bitkom-Muster-AVV (oder äquivalent VdK-Muster), angepasst um die Rollen-Definition aus diesem Abschnitt als Anlage "Technische und organisatorische Maßnahmen". Audit-Log ist für AVS einsehbar und exportierbar.

2.3 Operator-Zugänge (AVS)

Rolle Scope
operator-admin Voll: Tenants anlegen/löschen, Tenant-Pakete konfigurieren, Templates pflegen, System-Settings
operator-editor Tenant-Betreuung, keine strukturellen Änderungen an Paketen oder Templates
operator-viewer Read-only für Monitoring / Support

2.4 Tenant-Zugänge (Kurverwaltung)

Rolle Scope
tenant-admin Chatbots erstellen/verwalten, Dokumente/Quellen, Branding, Feedback, Settings
tenant-editor Chatbots und Quellen bearbeiten, keine Löschung
tenant-viewer Read-only

2.5 Terminologie im Code

  • tenant = Kurverwaltung (Sicherheitsgrenze)
  • chatbot = Tenant-Instanz, eigener Qdrant-Space (Kern-Entität)
  • chatbot_source = Wissensquelle eines Chatbots (Upload, Confluence-Sync, …)
  • chatbot_template = AVS-gepflegte Voreinstellungen
  • connector = Plugin-Implementierung für eine Quellenart
  • module = Plattform-Erweiterung (Knowledge-Hub, Ticket, STT/TTS, …)
  • tenant_package = Paket-Definition pro Tenant (steuert Module, Limits, Konfiguration) [zentral in v4]

2.6 Keycloak-Realm-Struktur (festgelegt in v5)

Zwei Realms im kora-platform-keycloak:

Realm Zweck Auth-Subdomain-Pfad
kora-platform Vendor + Operator auth.kora.luki-net.org/realms/kora-platform
kora-tenants Tenants (eine Group pro Tenant) auth.kora.luki-net.org/realms/kora-tenants

Rollen (flach, Realm-Level):

  • kora-platform: operator-admin, operator-editor, operator-viewer, vendor-support, vendor-breakglass, vendor-tunnel
  • kora-tenants: tenant-admin, tenant-editor, tenant-viewer

Tenant-Repräsentation: Eine Group pro Tenant unter /tenants/<slug>, Group-Attribut tenant_id=<uuid>. Group-Path kommt als groups-Claim ins Token, Backend resolvet gegen PostgreSQL.

Clients (pro Realm):

Realm Client Zweck
kora-platform kora-api (confidential, PKCE) Standard-OIDC-Flow für Operator und vendor-support
kora-platform kora-api-vendor-breakglass (confidential) Dedicated Client für vendor-breakglass mit 2h Client-Session-Max
kora-platform kora-api-vendor-tunnel (confidential) Dedicated Client für vendor-tunnel mit 2h Client-Session-Max
kora-tenants kora-api (confidential, PKCE) Standard-OIDC-Flow für Tenants

JWT-Claims (minimal, erwartet von API-Middleware):

  • sub, preferred_username, email, iss, aud: "kora-api"
  • realm_access.roles — Liste der zugewiesenen Realm-Rollen
  • groups — nur in kora-tenants-Tokens; Format ["/tenants/<slug>"]

chatbot_id steht NICHT im Token — wird pro Request aus URL/Body gegen chatbots.tenant_id validiert.

Token-/Session-Policies:

Rolle-Kategorie SSO-Session-Max Access-Token Refresh-Token
tenant-* 8h 5min 8h
operator-* 8h 5min 8h
vendor-support 4h 5min 4h
vendor-breakglass 2h (Client-Level) 5min 2h
vendor-tunnel 2h (Client-Level) 5min 2h

Remember-Me aus für alle Rollen (B2B-Admin-App).

Password-Policy (NIST 800-63B-konform): Mindestlänge 12, keine forcierte Rotation, keine Komplexitätsregeln. AVS kann via LDAP-Federation auf AD-Corporate-Policy umstellen.


3. Entitätsmodell & Hierarchie

             ┌──────────────────┐
             │     tenant       │
             └────────┬─────────┘
                      │ 1:1
             ┌──────────────────────┐
             │   tenant_package     │
             │   (Paket-Definition) │
             └────────┬─────────────┘
                      │ freigegebene Module
                      │ Limits (max_chatbots, …)
             ┌──────────────────┐       ┌──────────────────────┐
             │     chatbot      │◀──N:1─│  chatbot_template    │
             └────────┬─────────┘       │  (optional)          │
                      │ 1:N             └──────────────────────┘
             ┌────────▼─────────────────────────────────────┐
             │          chatbot_source                      │
             │   type: upload | confluence | wiki | ...     │
             └────────┬─────────────────────────────────────┘
             ┌────────────────────────────────┐
             │  Qdrant chatbot_<uuid>         │
             │  + shared_<template>_... opt.  │
             └────────────────────────────────┘

Invarianten: - Jeder Tenant hat genau ein aktives tenant_package, das bestimmt, welche Module und Limits gelten - Jeder Chatbot gehört genau einem Tenant - Jeder Chatbot hat N Wissensquellen (Upload ist eine Quellen-Art unter vielen) - Alle Daten scopen auf chatbot_id, mit tenant_id als denormalisiertem Redundanz-Feld


4. Datenmodell

4.1 Neue und geänderte Tabellen

-- Tenants (Kurverwaltungen)
CREATE TABLE tenants (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    slug            VARCHAR(64) UNIQUE NOT NULL,
    display_name    VARCHAR(255) NOT NULL,
    status          VARCHAR(32) NOT NULL DEFAULT 'active',
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    deleted_at      TIMESTAMPTZ,
    contact_email   TEXT,
    notes           TEXT
);

-- [NEU v4] Tenant-Pakete (ersetzt Lizenz-Feature-Gating)
-- Jedes Paket definiert, welche Module aktiviert sind und welche Limits gelten
CREATE TABLE tenant_packages (
    tenant_id          UUID PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE,
    tier               VARCHAR(32) NOT NULL,         -- "basis" | "professional" | "enterprise" (AVS-definiert)
    enabled_modules    JSONB NOT NULL DEFAULT '[]'::jsonb,  -- ["analytics", "knowledge_hub", ...]
    max_chatbots       INT NOT NULL DEFAULT 3,
    max_docs_per_chatbot INT NOT NULL DEFAULT 50,
    max_storage_mb     INT NOT NULL DEFAULT 500,
    max_sources_per_chatbot INT NOT NULL DEFAULT 5,
    max_monthly_queries INT,                          -- NULL = unbegrenzt
    avs_shared_enabled BOOLEAN NOT NULL DEFAULT FALSE,  -- AVS-Shared-Handbücher dazubuchen
    configured_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    configured_by      VARCHAR(255) NOT NULL,
    notes              TEXT
);

-- Tenant-Default-Branding (von Chatbots geerbt, wenn dort NULL)
CREATE TABLE tenant_branding (
    tenant_id       UUID PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE,
    primary_color   VARCHAR(7),
    logo_url        TEXT,
    widget_title    VARCHAR(255),
    widget_subtitle VARCHAR(255),
    welcome_message TEXT,
    footer_text     VARCHAR(255),
    allowed_origins JSONB,
    custom_css      TEXT               -- nur operator-kontrolliert
);

-- AVS-gepflegte Chatbot-Templates
CREATE TABLE chatbot_templates (
    id                       VARCHAR(64) PRIMARY KEY,   -- Slug: "meldeschein", "kurverwaltung"
    display_name             VARCHAR(255) NOT NULL,
    description              TEXT,
    suggested_system_prompt  TEXT,
    suggested_suggestions    JSONB,
    language                 VARCHAR(5) DEFAULT 'de',
    shared_collection_name   VARCHAR(255),               -- Qdrant collection (optional)
    recommended_connectors   JSONB,                       -- z.B. ["confluence"]
    is_active                BOOLEAN NOT NULL DEFAULT TRUE,
    created_at               TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at               TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Chatbot-Instanzen (Tenant-erstellt)
CREATE TABLE chatbots (
    id                 UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id          UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    slug               VARCHAR(64) NOT NULL,
    display_name       VARCHAR(255) NOT NULL,
    description        TEXT,
    template_id        VARCHAR(64) REFERENCES chatbot_templates(id),
    system_prompt      TEXT NOT NULL,                     -- Snapshot aus Template oder custom
    suggestions        JSONB,                             -- Snapshot
    language           VARCHAR(5) NOT NULL DEFAULT 'de',
    uses_shared_docs   BOOLEAN NOT NULL DEFAULT FALSE,
    qdrant_collection  VARCHAR(255) NOT NULL UNIQUE,
    template_version_at_creation VARCHAR(32),             -- für "Template-Update verfügbar"-Hinweis
    status             VARCHAR(32) NOT NULL DEFAULT 'active',
    created_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
    deleted_at         TIMESTAMPTZ,
    UNIQUE (tenant_id, slug)
);

-- Chatbot-Branding (alle Felder NULL-able, Fallback auf tenant_branding)
CREATE TABLE chatbot_branding (
    chatbot_id      UUID PRIMARY KEY REFERENCES chatbots(id) ON DELETE CASCADE,
    primary_color   VARCHAR(7),
    logo_url        TEXT,
    widget_title    VARCHAR(255),
    widget_subtitle VARCHAR(255),
    welcome_message TEXT,
    footer_text     VARCHAR(255),
    allowed_origins JSONB
);

-- Konnektor-Registry (global, Operator-Ebene)
CREATE TABLE connectors (
    id                  VARCHAR(64) PRIMARY KEY,
    display_name        VARCHAR(255) NOT NULL,
    description         TEXT,
    config_schema       JSONB NOT NULL,
    credential_schema   JSONB,
    supports_permissions BOOLEAN NOT NULL DEFAULT FALSE,
    supports_incremental_sync BOOLEAN NOT NULL DEFAULT FALSE,
    semver              VARCHAR(32) NOT NULL,       -- Konnektor-Version
    previous_semver     VARCHAR(32),                 -- für Auto-Migration
    is_active           BOOLEAN NOT NULL DEFAULT TRUE
);

-- Credentials (verschlüsselt) — vor chatbot_sources definiert in v4.1,
-- damit der credential_id-FK in chatbot_sources keine forward-reference ist
CREATE TABLE credentials (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    display_name    VARCHAR(255) NOT NULL,
    connector_id    VARCHAR(64) NOT NULL REFERENCES connectors(id),
    encrypted_data  BYTEA NOT NULL,
    key_version     INT NOT NULL DEFAULT 1,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    created_by      VARCHAR(255) NOT NULL,
    last_used_at    TIMESTAMPTZ
);

-- Wissensquellen pro Chatbot (nach credentials, siehe oben)
CREATE TABLE chatbot_sources (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    chatbot_id          UUID NOT NULL REFERENCES chatbots(id) ON DELETE CASCADE,
    tenant_id           UUID NOT NULL REFERENCES tenants(id),  -- NEU v4.1: denormalisiert für RLS-Performance
    connector_id        VARCHAR(64) NOT NULL REFERENCES connectors(id),
    connector_version_at_creation VARCHAR(32) NOT NULL,
    display_name        VARCHAR(255) NOT NULL,
    config              JSONB NOT NULL,
    credential_id       UUID REFERENCES credentials(id),
    sync_schedule       VARCHAR(64),                -- cron oder "manual"
    sync_status         VARCHAR(32) NOT NULL DEFAULT 'never',
    last_synced_at      TIMESTAMPTZ,
    last_error          TEXT,
    next_sync_at        TIMESTAMPTZ,
    is_active           BOOLEAN NOT NULL DEFAULT TRUE,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),
    created_by          VARCHAR(255) NOT NULL
);

-- Sync-Jobs-Historie
CREATE TABLE sync_jobs (
    id                BIGSERIAL PRIMARY KEY,
    source_id         UUID NOT NULL REFERENCES chatbot_sources(id) ON DELETE CASCADE,
    tenant_id         UUID NOT NULL REFERENCES tenants(id),  -- NEU v4.1: denormalisiert
    started_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    finished_at       TIMESTAMPTZ,
    status            VARCHAR(32) NOT NULL,
    documents_added   INT DEFAULT 0,
    documents_updated INT DEFAULT 0,
    documents_deleted INT DEFAULT 0,
    error_message     TEXT,
    metrics           JSONB
);

-- Vendor-Audit-Log (Vendor-Zugriffe durch luki-net)
CREATE TABLE vendor_access_log (
    id             BIGSERIAL PRIMARY KEY,
    occurred_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    vendor_user    VARCHAR(255) NOT NULL,
    role           VARCHAR(32) NOT NULL,
    action         VARCHAR(64) NOT NULL,
    target_tenant  UUID REFERENCES tenants(id),
    target_chatbot UUID REFERENCES chatbots(id),
    details        JSONB,
    ip_address     INET,
    session_id     UUID
);
-- Retention: 2 Jahre (pg_cron-basierte automatische Archivierung)

-- Platform-Audit-Log (Operator- und Tenant-Aktionen) — NEU in v4.1
-- Komplementär zu vendor_access_log, das nur Vendor-Zugriffe loggt
CREATE TABLE platform_audit_log (
    id              BIGSERIAL PRIMARY KEY,
    occurred_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
    actor_role      VARCHAR(32) NOT NULL,       -- "operator" | "tenant"
    actor_user      VARCHAR(255) NOT NULL,      -- Keycloak-Username
    actor_keycloak_id UUID,                     -- Keycloak-User-UUID (für stabilere Zuordnung)
    tenant_id       UUID REFERENCES tenants(id),
    chatbot_id      UUID REFERENCES chatbots(id),
    action          VARCHAR(64) NOT NULL,       -- "tenant.create", "chatbot.delete", "package.update", ...
    entity_type     VARCHAR(64),                -- "tenant" | "chatbot" | "chatbot_source" | ...
    entity_id       VARCHAR(255),               -- UUID oder Slug der betroffenen Entität
    details         JSONB,                      -- vorher/nachher-Snapshot bei UPDATE, etc.
    ip_address      INET,
    session_id      UUID
);
-- Retention: 2 Jahre (pg_cron-basierte automatische Archivierung, analog vendor_access_log)

-- Hinweis zur FK-Reihenfolge (v4.1):
-- credentials wird vor chatbot_sources erzeugt, damit der credential_id-FK direkt
-- inline definiert werden kann (keine forward-reference). Ein nachträgliches
-- ALTER TABLE ADD CONSTRAINT wäre nur bei einem echten Zyklus nötig — das haben
-- wir hier durch die Reihenfolge bewusst vermieden. Die Intention ist dokumentiert,
-- falls spätere Credential-bezogene Schema-Evolution den FK temporär auflösen muss.

4.2 Umbau bestehender Tabellen (wie in v3)

-- chat_sessions, chat_messages, feedback, document_versions, evaluation_runs
-- jeweils:
ALTER TABLE <table>
    DROP COLUMN product_id,
    ADD COLUMN chatbot_id UUID NOT NULL REFERENCES chatbots(id),
    ADD COLUMN tenant_id UUID NOT NULL REFERENCES tenants(id);

-- evaluation_questions bekommt chatbot_template_id (Evaluation pro Template)
ALTER TABLE evaluation_questions
    DROP COLUMN product_id,
    ADD COLUMN chatbot_template_id VARCHAR(64) REFERENCES chatbot_templates(id);

-- document_versions zusätzlich Source-Tracking
ALTER TABLE document_versions
    ADD COLUMN source_id UUID NOT NULL REFERENCES chatbot_sources(id) ON DELETE CASCADE,
    ADD COLUMN external_id VARCHAR(512),
    ADD COLUMN external_url TEXT,
    ADD COLUMN external_metadata JSONB,
    ADD COLUMN last_modified_externally TIMESTAMPTZ;

CREATE UNIQUE INDEX document_versions_source_external_idx
    ON document_versions(source_id, external_id)
    WHERE external_id IS NOT NULL;

-- Trigger: tenant_id-Konsistenz
-- Auf chatbot_sources: tenant_id muss mit chatbots.tenant_id übereinstimmen
-- Auf sync_jobs: tenant_id muss mit chatbot_sources.tenant_id übereinstimmen
-- Auf chat_sessions/chat_messages/feedback/document_versions/evaluation_runs:
--   tenant_id muss mit chatbots.tenant_id übereinstimmen
-- Verhindert Tenant-Cross-Contamination bei fehlerhafter Insert-Logik.
-- Konkrete Trigger-Definitionen in Block 1.4 (separate Migration).

4.3 Qdrant-Payload (pro Chunk)

{
  "content": "...",
  "chatbot_id": "<uuid>",
  "tenant_id": "<uuid>",
  "source_id": "<uuid>",
  "source_type": "confluence",
  "external_id": "12345",
  "external_url": "https://...",
  "author": "max.mustermann",
  "last_modified": "2026-04-15T10:00:00Z",
  "permissions": {
    "read_groups": ["kur-admins"],
    "read_users": []
  },
  "page": 3,
  "chapter": "Meldewesen"
}

4.4 Chatbot-Quota — einstufig (v4)

Gegenüber v3 (zweistufiger Lizenz-/Operator-Check) bleibt in v4 nur noch die Operator-Entscheidung:

-- Einfacher Check bei Chatbot-Erstellung
SELECT max_chatbots FROM tenant_packages WHERE tenant_id = :tenant_id;
-- Ohne Lizenz-Obergrenze, AVS hat Vollzugriff und setzt Limits nach eigenem Ermessen

4.5 Branding-Resolution

Unverändert zu v3: Chatbot-Wert → Tenant-Fallback → System-Default.

4.6 Row-Level Security

RLS-Policies auf allen Tabellen mit tenant_id-Spalte. Durch das denormalisierte tenant_id auf chatbot_sources und sync_jobs (§4.1) lassen sich alle Policies als einfacher Gleichheits-Check gegen den Session-tenant_id-Context formulieren — ohne JOINs, ohne EXISTS-Subqueries.

Session-Context: Die API-Middleware setzt bei jedem Request SET LOCAL app.current_tenant_id = '<uuid>' aus dem JWT. RLS-Policies prüfen tenant_id = current_setting('app.current_tenant_id')::uuid.

Vendor-Bypass: Vendor-Rollen (vendor-support, vendor-breakglass, vendor-tunnel) nutzen einen Postgres-Rollen-basierten Bypass (BYPASSRLS), weil sie per Design tenant-übergreifend arbeiten. Jeder Zugriff wird über vendor_access_log auditiert.

Konkrete Policy-Definitionen in Block 1.5 (separate Migration).


5. Chatbot-Templates

5.1 Rolle

Templates sind Vorschlagsgeber von AVS für gängige Chatbot-Anwendungsfälle. Drei optional übernehmbare Bestandteile:

  1. Vorgeschlagener System-Prompt — kuratiert für die Domäne
  2. Vorgeschlagene Suggestions — typische Einstiegsfragen
  3. Shared-Handbücher — AVS-gepflegte Referenz-Dokumente, opt-in

5.2 Snapshot-Versionierung (festgelegt in v4)

Wenn AVS ein Template ändert, bleiben bestehende Chatbots, die auf diesem Template basieren, unverändert — der System-Prompt und die Suggestions wurden beim Chatbot-Erstellen als Snapshot kopiert, nicht referenziert.

Das verhindert unerwartete Änderungen beim Tenant und gibt klare Versionshoheit: - chatbots.template_version_at_creation hält die Template-Version zum Erstellungszeitpunkt fest - Wenn eine neuere Version existiert: UI-Hinweis im Chatbot-Detail "Template-Update verfügbar" - Tenant-Admin entscheidet explizit, ob Änderungen übernommen werden (Merge-Konflikt-Lösung bei eigenen Anpassungen)

Alternative wäre ein Referenz-Modell mit Override-Feldern — ist architektonisch eleganter, aber das "überraschende Änderung"-Risiko macht es in Tenant-Kontexten ungeeignet.

5.3 Template-Auswahl beim Chatbot-Erstellen

Wizard-Flow (unverändert gegenüber v3): 1. Grunddaten (Name, Slug, Sprache, Beschreibung) 2. Template wählen + feldweise Opt-Ins (System-Prompt ja/nein, Suggestions ja/nein, Shared-Handbücher ja/nein) 3. Dokumente/Wissensquellen hinzufügen 4. Optional: System-Prompt automatisch generieren lassen (§10)

5.4 Evaluation-Questions pro Template (festgelegt in v4)

evaluation_questions sind dem Template zugeordnet, nicht dem Chatbot. Das spiegelt das Muster wider: AVS pflegt als Operator die Qualitätsmessung pro Template, Tenants nutzen den Mechanismus ohne selbst Testfragen definieren zu müssen. Wenn Tenants später eigene Evaluations-Suites wünschen, kann das in v1.x als Erweiterung nachgerüstet werden.

5.5 Empfohlene Konnektoren pro Template

Ein Template kann recommended_connectors deklarieren. Beispiel: Template "Meldeschein" empfiehlt ["upload"], Template "Interne Kurverwaltungs-Info" empfiehlt ["upload", "confluence", "mediawiki"]. Das steuert die UI im Template-basierten Chatbot-Wizard, ist aber nicht zwingend.


6. Qdrant Collection-Strategie

6.1 Naming-Konvention

  • chatbot_<uuid> pro Chatbot-Instanz (schreibbar durch Tenant-Admin)
  • shared_<template_slug>_handbuecher pro AVS-Template (nur Operator schreibt)

6.2 Fan-Out-Retrieval

Query läuft parallel gegen Chatbot-Collection und (falls aktiviert) Shared-Collection. Union + Re-Ranker → Top-N. Shared-Collection wird nur abgefragt, wenn: - chatbots.uses_shared_docs = TRUE UND - chatbot_template.shared_collection_name IS NOT NULL

6.3 Indexing bei Sync

Sync-Job fügt Chunks inkrementell hinzu. Bei Löschung im Quellsystem werden Chunks via source_id + external_id entfernt.

6.4 Skalierung

Bis ~500 Collections pro Qdrant-Node ohne Performance-Probleme. Geplante Größenordnung v1.0.0: ~200 Collections (50-100 Tenants × 2 Chatbots im Mittel).

6.5 Backup-Strategie (festgelegt in v4)

Hybrid-Strategie: - Täglich: Qdrant-Native Bulk-Snapshot aller Collections, automatisiert. Snapshot-Rotation: 7 Tage behalten. - Wöchentlich: Zusätzlich Per-Collection-Snapshots als Archive (für granulares Einzel-Restore). Rotation: 4 Wochen. - Ad-hoc-Script: restore-tenant.sh <tenant_uuid> für manuelle Einzel-Restores aus archivierten Snapshots. Nicht automatisiert, bewusst so.


6a. Ingestion- & Retrieval-Pipeline (ergänzt in v5.2)

§6a beschreibt die Pipeline-Schicht zwischen Konnektoren (Ingestion, §13) und Qdrant-Collections (Persistenz, §6): Document-Normalisierung, Embedding-Modell-Wahl und Chunking-Strategie. Die drei Unter-Abschnitte sind Qualitäts-Entscheidungen, die als Blöcke 18/19/20 in Phase C vor Block 13 (Konnektor-Framework-Stub) umgesetzt werden, weil alle nachgelagerten Konnektor-Arbeiten auf einer konsistenten Normalisierungs- und Embedding-Infrastruktur aufsetzen müssen.

6a.1 Document-Normalisierung via Docling

Problem. Der Haystack-Default-Converter linearisiert strukturierte Inhalte: Tabellen-Spalten verschmelzen, Zeilen-Zusammenhänge gehen verloren, Layout-Hinweise (Überschriftsebenen, Figure-Captions) werden zerschlagen. Für AVS-Dokumente hat das konkrete Auswirkungen: Meldeschein-Feldkataloge, Paragraphen-Listen in Bundesländer-Verordnungen, Gebühren-Tabellen (Kurtaxe × Altersgruppe × Saison) und Fehler-Code-Referenzen werden im Retrieval als Fließtext-Fragmente zurückgegeben, nicht als Tabellen-Zellen mit erhaltenem Spalten-Kontext.

Lösung. IBM Docling (MIT-Lizenz, Linux Foundation AI & Data, v2.72.0 Februar 2026) als vorgelagerter Document-Converter. Docling kombiniert zwei Modelle: DocLayNet für die Layout-Analyse (trainiert auf 81.000 gelabelten Seiten) und TableFormer für die strukturierte Tabellen-Rekonstruktion. Die dokumentierte Tabellen-Extraktions-Genauigkeit liegt bei 97,9 %. Das optionale Granite-Docling-258M-VLM für OCR-lastige Inhalte ist in v1.0.0 nicht Default, sondern Backlog-Kandidat mit VLM Multimodal (Knowledge-Hub-Konzept).

Integration. Haystack-native via haystack-integrations/docling. Pipeline-Position: vor dem Semantic Chunker. Der Chunker bekommt eine zusätzliche Regel preserve_tables=True — Tabellen-Chunks sind atomar und werden nie geteilt, auch nicht wenn sie max_chunk_chars überschreiten. Das ist bewusst eine harte Regel, weil der Retrieval-Wert eines halbierten Tabellen-Chunks gegen null geht.

Metadaten-Anreicherung (Qdrant-Payload, vgl. §4.3). Jeder Chunk bekommt zusätzlich:

  • chunk_type: "table" | "text" | "figure_caption" | "heading"
  • docling_version: String (für spätere Re-Index-Trigger bei Docling-Upgrades)
  • has_tables: Boolean auf Dokument-Ebene

Diese Felder ermöglichen zukünftige Query-Patterns (strukturierte Fragen → Filter auf chunk_type=table; konzeptionelle Fragen → Filter auf chunk_type=text), ohne dafür in v1.0.0 schon eine UI zu brauchen.

Verhältnis zum Konnektor-Subsystem (§13). Docling ist die Normalisierungs-Schicht zwischen Konnektor-Output (raw_bytes + mime_type) und Indexierung (§6 Qdrant). Das vereinfacht §13 deutlich: Konnektoren bringen keine eigenen Parser mehr mit, sondern liefern rohe Bytes plus MIME-Type an Docling, das einheitliches Markdown produziert. Der Upload-Konnektor und der Confluence-Konnektor profitieren beide ohne Zusatzaufwand.

Aufwand. ~18h — umgesetzt als Block 18 (§17.2, §18 Phase C).

6a.2 Embedding-Modell & Hybrid-Retrieval via BGE-M3

Problem. Das bisherige Embedding-Modell multilingual-e5-large (Mitte 2023) ist technologisch überholt und hat zwei konkrete Retrieval-Lücken: (1) rein semantische Suche ist schwach bei exakten Token-Matches ohne semantisches Signal (Paragraphen-Nummern wie "§ 23 Abs. 4", Error-Codes wie "E-1042", Eigennamen/Formular-IDs wie "ELMA-Kennzeichen"); (2) die 512-Token-Kontext-Länge zwingt zu kleinteiligen Chunks, was mit den atomaren Tabellen-Chunks aus §6a.1 kollidiert.

Lösung. BGE-M3 (BAAI, MIT-Lizenz) liefert in einem Modell simultan drei Embedding-Typen:

  • Dense (dichte 1024-dimensionale Vektoren) — semantische Ähnlichkeit wie bisher
  • Sparse (Lexical-Weights, SPLADE-ähnlich) — exakte Token-Matches
  • Multi-Vector (ColBERT-Style-Late-Interaction) — optionales Re-Ranking-Signal

Das ersetzt den ursprünglich separat diskutierten SPLADE-Einschub und den Grep-Search-Fallback durch eine Modell-Komponente. Vorteile gegenüber e5-large: 8.192 Tokens Kontext-Länge (vs. 512), konsistenter Token-Space für Dense + Sparse (keine Modell-Divergenz), bessere DACH-Performance auf MIRACL/MKQA-Benchmarks.

Hybrid-Retrieval. Zwei Retriever auf derselben Qdrant-Collection (Dense + Sparse), Fusion via Reciprocal Rank Fusion mit Startgewichten Dense:Sparse = 0.7:0.3. Der bestehende Cross-Encoder-Ranker (mmarco-mMiniLMv2-L12-H384-v1) bleibt als finale Stage oder wird optional auf bge-reranker-v2-m3 upgegradet (konsistenter Stack, +2h Aufwand). Die Gewichts-Parameter werden über die Eval-Suite getunet, keine hartcodierten Werte.

Qdrant-Collection-Schema. Dense- und Sparse-Vectors liegen in derselben Collection (VectorParams + SparseVectorParams nebeneinander, §6.1-Naming unverändert). Re-Indexing ist erforderlich und nutzt das in §13.4 definierte Versionierungs-Pattern — document_versions markieren alte Embeddings als stale, ein Background-Job re-embedded. Keine User-Action nötig.

Feature-Flag. USE_HYBRID_RETRIEVAL (global in v1.0.0, pro Tenant erst ab v1.1.0 über tenant_packages konfigurierbar). Das erlaubt Rollback auf reines Dense-Retrieval, falls der produktive Eval-Run Regressionen zeigt.

Komplementarität zu §6a.1 (Docling). Die 8.192-Token-Kontext-Länge ist nur sinnvoll nutzbar, wenn Chunks nicht sinnlos klein bleiben. Docling's atomare Tabellen-Chunks und §6a.3's AST-Block-Chunks profitieren direkt, weil sie jetzt in einem Embedding ohne Mittelungs-Verlust abgebildet werden können.

Aufwand. ~25h — umgesetzt als Block 19 (§17.2, §18 Phase C).

6a.3 Chunking-Strategien: Semantic + AST-Aware

Bestand. Der Semantic Chunker aus der Demo (Kapitel-Grenzen-Erkennung anhand von Heading-Hierarchie, Recall@5 = 100 %, MRR = 1.000 auf aktuellem Eval-Set) bleibt Default-Strategie für Prosa-Dokumentation — Handbücher, Bundesländer-Verordnungen, Anleitungen.

Erweiterung. Für strukturierte Inhalte (Code-Snippets in technischen Handbüchern, JSON/YAML-Schemas, API-Referenzen) wird ein AST-Aware Chunker als optionale Strategy ergänzt. Implementierung: Tree-sitter-basiert. Auswahl pro Source oder pro Chunk-Kandidat via Parameter chunk_strategy: "text" | "ast". Auto-Detection auf Chunk-Kandidat-Ebene: Code-Fences in Markdown werden extrahiert und sprach-spezifisch zerlegt, auch wenn die Source-Datei nicht primär als Code markiert ist.

Unterstützte Sprachen (gestaffelt).

  • v1.0.0: Python, JSON, YAML (JSON/YAML für AVS-Schema-Doku und Template-Configs, Python für Integrations-Beispiele)
  • v1.1.0+: TypeScript, SQL (je nach Kundenbedarf)
  • Nie: C#, Java, Rust, Go (AVS' Tech-Stack umfasst diese Sprachen nicht, Priorisierung nach Kunden-Realität)

Chunk-Grenzen.

  • Python: def/class/Top-Level-Blöcke. Docstrings werden mit der Funktion gebundelt, nicht als separater Chunk.
  • JSON: Top-Level-Objekte. Arrays nur wenn sie selbst Top-Level sind.
  • YAML: Document-Marker (---) als harte Grenze; sonst Top-Level-Keys.

Metadaten-Anreicherung (Qdrant-Payload, vgl. §4.3 und §6a.1).

  • chunk_strategy: "text" | "ast"
  • ast_node_type: "function" | "class" | "object" | "document" | ...
  • ast_node_name: Identifier (z.B. Funktionsname, JSON-Key-Pfad)
  • language: ISO-Sprachcode oder MIME-Type

Ermöglicht zukünftige Metadata-Filter ("zeig mir alle Funktionen, die tenant_id im Signatur-Namen tragen"), ohne dafür in v1.0.0 schon UI-Arbeit zu leisten.

Verhältnis zu §6a.1. Docling handhabt Markdown-Tabellen atomar. AST-Chunking ergänzt um Code-Block-Verständnis — Docling liefert die Code-Fences aus dem Markdown-Output, AST-Chunking zerlegt sie semantisch. Beide Strategien koexistieren in der Pipeline; welche greift, entscheidet der Chunk-Kandidat.

Nutzbarkeit in v1.0.0. Aktuell nicht per Eval-Set messbar, weil AVS-Handbücher primär Prosa sind. Der Block ist Enabler-Feature für Zukunfts-Use-Cases:

  • Technische Integrations-Dokumentation bei Multi-Tenant-Kunden mit eigener IT-Doku
  • Ticket-Eskalation mit Code-Antworten (Knowledge-Hub-Modul, v2.0.0)
  • AVS-eigene Konnektor-Entwickler-Dokumentation (§13.10)

Aufwand. ~13h — umgesetzt als Block 20 (§17.2, §18 Phase C).

§6a.Y — Reranker-Rolle (Erkenntnis aus Block 19)

Der Cross-Encoder-Reranker (mmarco-mMiniLMv2-L12-H384-v1) ist nicht eine optionale "letzte Stufe", sondern die dominante Komponente für die finale Top-K-Rangordnung. Block-19-Phase-3.5-Diagnose hat empirisch belegt:

  • RRF-Weight-Sweep no-op: Konfigurationen 0.7:0.3, 0.8:0.2, 0.9:0.1, und sogar 1.0:0.0 (pure Dense, RRF deaktiviert) ergaben identische Final-Top-5-Listen. Der Reranker filtert die Pre-Reranker-Liste so hart, dass Pre-Reranker-Order keine Auswirkung hat.
  • Implikation: Optimierungs-Investitionen am Retrieval-Ranking (Embedder, Sparse-Vectors, RRF-Weights) haben nur dann Wirkung, wenn sie die Reranker-Inputs verändern (z.B. andere Doc-Set in Top-20 bringen). Re-Order innerhalb Top-20 ist verloren.
  • Bekannter Failure-Mode: Bei duplizierten Inhalten in mehreren Quellen (z.B. q006-Meldeschein-Thema in zwei Handbüchern) bevorzugt der Reranker basierend auf semantischer Density. Pre-Reranker-Top-2 kann zu Post-Reranker-Top-4 degradieren, ohne dass Hit@5 verloren geht.

Architektonische Konsequenz: Reranker-Wechsel (z.B. Block 19.5 bge-reranker-v2-m3) ist kein inkrementeller Tuning-Schritt, sondern eine zentrale Pipeline-Entscheidung mit größtem Hebel auf Final-Quality. Reranker-Upgrades brauchen eigene Eval-Cycles und A/B- Vergleiche analog zu Block 19.

Roadmap-Anker: Block 19.5 (Reranker-Upgrade), siehe roadmap.md.

§6a.X — Corrective-RAG / Grader-Stage (offen, Roadmap v1.0.0)

Aktuelle Pipeline: Embedder → Retriever → Ranker → LLM. Wenn der Ranker schwache Confidence liefert (Top-1-Score < no_match_threshold, aktuell 0.10), gibt das System eine graceful no-match-Antwort. Das ist reaktiv — der Pfad wird nur dann genommen wenn der Threshold sauber greift.

Corrective-RAG-Vorschlag. Optionale fünfte Pipeline-Stage zwischen Ranker und LLM, die Confidence proaktiv prüft via Grader-Prompt:

Ranker → Grader (LLM-light) → {
  confidence_high  → LLM-Generation (Standard-Pfad)
  confidence_low   → Eskalations-Pfad (Ticket / Support-Hand-off)
}

Der Grader ist ein leichter LLM-Call (≤200 Tokens, kein RAG-Context- Inject), der die retrievten Chunks gegen die Frage prüft und eine Confidence-Aussage zurückgibt: "ja, beantwortbar" / "teilweise" / "nein, eskalieren".

Strukturelle Fragen:

  • Position relativ zum existierenden no_match_threshold: Ergänzt der Grader den Threshold (zwei-Schichten-Filter) oder ersetzt er ihn?
  • Latenz-Budget: Grader fügt einen LLM-Call hinzu — bei 2-6s aktueller End-to-End-Latenz akzeptabel, aber muss in Phase-3-Style-Eval validiert werden.
  • Multi-Tenancy: Pro Tenant aktivierbar? Grader-Prompt-Customization pro Domäne (AVS vs. zukünftige Kunden)?

Architektonische Konsequenz: Grader-Stage ist Erweiterung der Pipeline, nicht Replacement. Cross-Encoder-Reranker bleibt dominant (siehe §6a.Y), Grader operiert nach Reranker und ist optional aktivierbar.

Roadmap-Anker: Roadmap-Karte L2 (Grader-Stage als Ergänzung zu Karte 14 Ticket-Eskalation), v1.0.0.


7. Lizenz-Schema als Installations-Marker (v4)

7.1 Rolle

Das v0.6.0-Lizenz-System bleibt im Code, aber in stark reduzierter Rolle:

  • Die Lizenz ist weiterhin RSA-4096-signiert und enthält die Kopfdaten der Installation (Kunde, Tier, Ausstellungsdatum, Kontaktdaten)
  • Sie dient als Vertragsartefakt — Referenz auf die Vereinbarung zwischen luki und AVS
  • Sie dient als Installations-Identifikator — beim Support-Case weiß luki sofort, um welche Installation es geht
  • Sie bildet keine Feature-Gates mehr ab

7.2 Was weiter passiert mit dem Schema

class LicensePayload(BaseModel):
    # Kopfdaten bleiben:
    customer_id: str
    customer_name: str
    installation_id: str           # eindeutige UUID pro Installation
    contract_reference: str        # Vertragsnummer, Freitext
    issued_at: str
    contact_email: str

    # Tier bleibt als grober Referenzwert (informativ):
    tier: str = "enterprise"       # aus Vertrag, ohne technische Konsequenz

    # entfallen in v4:
    # features: list[str]           -> entfällt, nicht mehr genutzt
    # products: list[LicenseProduct] -> entfällt
    # max_*: int                    -> entfällt

7.3 Display- und Support-Nutzung

  • Admin-UI /admin/license zeigt Kopfdaten und Vertragsreferenz
  • Support-Header in Error-Reports: X-Installation-ID: <uuid>
  • Kein technisches Enforcement mehr über Lizenz-Felder

7.4 Re-Interpretation des v0.6.0-Codes

Bestehender Code wie license_service.is_licensed(feature) wird refactored: - Im Core-Pfad: Alle Aufrufer migrieren zu tenant_package_service.has_module(tenant_id, module_id) - Die Funktion is_licensed() bleibt als No-op oder wird entfernt

Aufwand für diesen Refactor: siehe §17.


8. Plattform-Module (einstufige Freischaltung in v4)

8.1 Einstufiges Modell

Gegenüber v3 (zweistufig: AVS-global + pro-Tenant) in v4 nur noch eine Stufe:

Modul wird in platform_modules registriert (beim Deployment)
          │ Kein weiterer globaler Enable-Schritt mehr.
          │ Alles, was registriert ist, kann in Tenant-Paketen verwendet werden.
Pro Tenant in tenant_packages.enabled_modules eintragen
Modul ist für diesen Tenant aktiv und sichtbar

8.2 Modul-Registry

CREATE TABLE platform_modules (
    id                     VARCHAR(64) PRIMARY KEY,
    name                   VARCHAR(255) NOT NULL,
    description            TEXT,
    scope                  VARCHAR(32) NOT NULL,        -- 'core' | 'external_eligible' | 'internal_only'
    is_always_on           BOOLEAN NOT NULL DEFAULT FALSE,
    version                VARCHAR(32),
    config_schema          JSONB
);
-- is_external_enabled entfällt gegenüber v3

Scope-Werte sind jetzt nur noch Kategorisierung, keine Zugangs-Kontrolle: - core — immer aktiv (chatbot_engine) - external_eligible — normal zubuchbar (knowledge_hub, ticket_escalation, ms_integration, ...) - internal_only — konzeptionell Kategorie für zukünftige AVS-interne Tools (heute nicht genutzt)

8.3 Paket-Konfiguration im Operator-UI

Operator-Admin bei AVS: 1. Klickt Tenant-Detail → Reiter "Paket" 2. Sieht Liste aller registrierten Module mit Checkboxen 3. Aktiviert per Häkchen, setzt Limits, speichert 4. Änderung wird sofort wirksam (Tenant-User sieht bei nächstem Reload das aktivierte Modul)


9. Admin-UI-Architektur

9.1 Stack-Entscheidung (festgelegt in v4)

Frontend-Stack: Vue 3 + Composition API, ohne Pinia.

  • State-Weitergabe über props nach unten, emit nach oben
  • Für modul-übergreifenden Zustand: URL/Query-Parameter oder provide/inject bei klar begrenzter Komponenten-Hierarchie
  • API-Calls: native fetch oder dünner axios-Wrapper
  • Styling: Scoped CSS mit bestehendem CSS-Variable-System
  • Build: Vite (Standard im Vue-3-Ökosystem)

Integration in bestehenden Jinja2-Stack: - Neue Bereiche (Chatbot-Wizard, Sources-Management) als Vue-Komponenten eingebunden in Jinja2-Seiten via <div id="vue-app-<name>">-Mount-Points - Kein Full-SPA-Rewrite — bestehende Admin-Seiten bleiben Jinja2 und werden schrittweise migriert, wenn Mehrwert - Vue-Apps werden gebundelt und als statische Assets ausgeliefert

9.2 UI-Struktur

/admin
├── /vendor/           → vendor-* Rollen
│   ├── audit-log
│   ├── tenant-overview
│   ├── system-health
│   ├── tunnel-control
│   └── sync-health
├── /operator/         → operator-* Rollen (AVS)
│   ├── tenants/         (CRUD)
│   ├── tenants/{id}/    (Detail + Paket-Konfiguration)
│   ├── templates/       (Template-CRUD, Shared-Collections)
│   ├── connectors/      (Registry-Übersicht, Health)
│   ├── modules/         (Modul-Registry)
│   ├── settings/
│   └── vendor-access/
└── /tenant/           → tenant-* Rollen (Kurverwaltung)
    ├── dashboard
    ├── chatbots/        (Liste)
    ├── chatbots/new     (Vue-basierter Wizard)
    ├── chatbots/{id}/
    │   ├── overview
    │   ├── sources/     (Vue-basiertes Quellen-Management)
    │   ├── documents
    │   ├── prompt       (System-Prompt-Editor + Auto-Generate)
    │   ├── suggestions
    │   ├── branding
    │   ├── feedback
    │   ├── sessions
    │   └── embed
    ├── branding         (Tenant-weit)
    └── analytics        (falls Modul aktiv)

9.3 Container-Strategie — Monolith mit Embedded Vue-Komponenten (klargestellt in v5.1)

Die Plattform wird in v1.0.0 als ein Deployment-Artefakt ausgeliefert: ein Container-Image liefert FastAPI-Endpoints, Jinja2-gerenderten HTML-Rahmen und Vue-3-Komponenten als statische Assets (aus Multi-Stage-Build).

Rationale:

  • Dimensionierung passt zum Geschäftsmodell (wenige Operator-User + ~50–100 Tenants × 2–5 Admin-User); Container-Split hätte auf absehbare Zeit mehr Operations-Kosten als Nutzen.
  • OIDC-/Session-Handling in einer Codebasis — kein Cross-Origin-Kopfzerbrechen zwischen Frontend-Container und API-Container.
  • Konsistenz zur v0.9.x-Demo-Architektur (bekannt und stabil): der Demo-api-Container liefert Jinja2 + Widget in derselben Form aus.
  • Migration zu Multi-Container bleibt offen für v2.x+ bei nachgewiesenem Bedarf (externe API-Clients, abweichende Skalierungs-Profile).

Konkret:

  • src/kora_platform/ — Python-Code (FastAPI-App + Pydantic-Models + Services)
  • src/kora_platform/admin-ui/ oder admin-ui/ — Vue-3-Sources (Entscheidung zum Ablage-Ort fällt in Block 5 beim UI-Framework-Scaffolding; vorgezogen aus Block 7 wegen Template-Update-Dialog-Dependency, siehe §17.2 und Änderungshistorie v5.2 → v5.3)
  • infra/docker/Dockerfile.platform — Multi-Stage (Node-Build → Python-Runtime mit statischen Assets; aktuell nur Python-Stage, Node-Stage kommt mit Block 5 als UI-Framework-Scaffolding, nicht erst mit Block 7)
  • Serving:
    • /api/v1/* — REST-Endpoints
    • /admin/* — Jinja2-Rahmen mit Vue-Mount-Points
    • /admin/static/* — gebundelte Vue-Bundles
    • /widget/* — öffentliches Chat-Widget (aus derselben Instanz)

Post-v1.0.0: Container-Trennung (Frontend-nginx + API + ggf. BFF) bleibt Option, wird aber erst bei konkretem Skalierungs- oder externen-API-Client-Bedarf eingeführt.


10. System-Prompt-Automatismus

Unverändert zu v3: Multi-Step-Pipeline mit Qwen3-14B (Default), explizit per Button-Click getriggert (kein Auto-Trigger beim Upload). Siehe v3 §10 für Details zu Pipeline und Qualitätsmessung.

Neu in v4: Die LLM-Calls werden auch für chatbot_source-Typen parametrisiert — Confluence-Seiten werden anders zusammengefasst als Word-Dokumente (Metadaten-bewusst).


11. API-Middleware & Scope-Resolution

Die API-Middleware macht bei jedem authentifizierten Request:

  1. JWT-Validierung: Token gegen beide Realm-Issuer prüfen (kora-platform ODER kora-tenants). Signature via JWKS-Endpoint, aud: kora-api, Ablauf.
  2. Scope-Resolution:
  3. Issuer kora-platform → Request läuft im Operator- oder Vendor-Scope. Rollen aus realm_access.roles.
  4. Issuer kora-tenants → Request läuft im Tenant-Scope. groups-Claim auf /tenants/<slug> parsen, tenant_id via PostgreSQL tenants WHERE slug=... auflösen (Redis-Cache, TTL 60s).
  5. ContextVar: tenant_id, scope_level (vendor|operator|tenant), user_id, roles im FastAPI-Request-Context.
  6. Fail-Safe: Ungültiger Token → HTTP 401. Gültiger Token ohne passenden Scope für die Resource → HTTP 404 (nie 403, um Tenant-Existenz nicht preiszugeben).
  7. Chatbot-Ownership-Check: Für Chatbot-scoped Endpoints zusätzlich prüfen, ob chatbot_id aus URL zu tenant_id aus Context passt (SELECT 1 FROM chatbots WHERE id=? AND tenant_id=?).

Unit- und Integration-Tests: Cross-Tenant-Access-Attempts müssen HTTP 404 ergeben, nicht Daten eines fremden Tenants.


12. Widget & Branding

12.1 Einbettung pro Chatbot

<script
    src="https://plattform.kora.example.com/widget/kora-chat-widget.min.js"
    data-tenant="bad-toelz"
    data-chatbot="meldewesen">
</script>
<kora-chat-widget tenant="bad-toelz" chatbot="meldewesen"></kora-chat-widget>

12.2 Versionierung (festgelegt in v4)

  • Widget-Auslieferung über /widget/kora-chat-widget.min.js mit automatischen Non-Breaking-Updates
  • Breaking Changes: 6 Monate Deprecation-Policy, Versioniert als /widget/v2/kora-chat-widget.min.js
  • Console-Warnung bei Nutzung veralteter Attribute

12.3 Branding-Resolution

Chatbot-Wert → Tenant-Fallback → System-Default. custom_css nur operator-kontrolliert (XSS-Risiko).

12.4 Widget-Authentifizierung (festgelegt in v5)

Widget-Traffic umgeht Keycloak komplett. End-Nutzer des Chatbots sind anonym; Token im Widget-HTML wären Security-Theater.

Stattdessen: - chatbot_branding.allowed_origins (JSONB-Array) speichert die erlaubten Origins pro Chatbot (z.B. ["https://bad-toelz.de", "https://www.bad-toelz.de"]) - API-Endpoint POST /api/v1/query validiert den Origin-Header gegen diese Liste — nicht passend → HTTP 403 - Rate-Limit per (chatbot_id, sha256(client_ip)) bleibt bestehen - Widget sendet lediglich tenant- und chatbot-Attribute im Request-Body

Kein zusätzlicher Keycloak-Client für das Widget nötig. allowed_origins ist Datenmodell-Feld in chatbot_branding (siehe Block 1 oder Nachtrag in Block 4).


13. Konnektor-Subsystem

13.1 Architektur

(Unverändert zu v3: Registry + Runtime + Indexing-Pipeline, siehe Drei-Schichten-Diagramm in §1)

13.2 Base-Interface

(Unverändert, BaseConnector aus v3 §13.2)

13.3 Credential-basierter Rate-Limit-Scheduler (v4)

Der Scheduler gruppiert Sync-Jobs nach credential_id. Pro Credential wird ein Rate-Limit-Bucket geführt, dessen Parameter aus der Konnektor-Registry kommen (connectors.config_schema.rate_limits):

class SyncScheduler:
    async def execute_sync(self, source_id: UUID):
        source = await get_source(source_id)
        credential = source.credential_id

        async with self.rate_limiter.acquire(credential):
            async for doc in connector.fetch_documents(...):
                await self.index(doc)

Beispiel: Atlassian-API hat ~10 Req/s pro Token. Bei 50 parallelen Tenants mit derselben Credential (was AVS-intern vorkommen könnte bei einem gemeinsamen Support-Token) werden die Syncs seriell ausgeführt, nicht parallel — verhindert API-429-Responses.

13.3a Inkrement-Sync via Merkle-Tree-Snapshots (ergänzt in v5.2)

Problem. Die Konnektor-Roadmap v2 sieht periodische Sync-Zyklen für Confluence (§2.2), SharePoint, Teams und Jira vor. Der naive Ansatz — bei jedem Sync alle Dokumente der Quelle neu fetchen, chunken und embedden — verschwendet drei Ressourcen gleichzeitig: Upstream-API-Quota, GPU-Zeit für Embeddings, Qdrant-Write-Last. Beispielrechnung: ein AVS-Tenant mit 5.000 Confluence-Seiten bei stündlichem Sync ergibt 120.000 Embedding-Calls pro Tag, von denen realistisch ~10 Seiten/Tag (also ~99,8 %) redundant sind.

Lösung. SHA-256-Content-Hash pro Dokument plus eine flache document_hashes-Map pro chatbot_source. Ein echter Merkle-Tree wird nicht persistiert — die Map erlaubt eine Rekonstruktion on-demand, was den Implementierungs- und Storage-Aufwand klein hält. Beim Sync wird erst der Root-Hash der neuen Map gegen den Root-Hash des letzten Snapshots verglichen (Early-Exit bei Gleichheit). Bei Unterschied wird auf Dokument-Ebene ein Diff gebildet und nur die geänderten Dokumente werden erneut fetcht, gechunked und embedded.

Schema-Erweiterung. Neue Tabelle connector_sync_snapshots:

Spalte Typ Anmerkung
id UUID PK
chatbot_source_id UUID FK ON DELETE CASCADE Referenz auf §4.1
root_hash VARCHAR(64) SHA-256 Hex über die sortierten Dokument-Hashes
created_at TIMESTAMPTZ
document_hashes JSONB {external_id: sha256_hex}
total_documents INTEGER Redundant zu document_hashes, für schnelle Dashboards

Index auf (chatbot_source_id, created_at DESC) für den "letzter Snapshot"-Lookup. Unique-Constraint auf (chatbot_source_id, created_at) verhindert Doppel-Snapshots zur selben Sekunde. Retention keep_last=3 über das in §6.5 etablierte _cleanup_expired-Pattern — drei Snapshots sind genug für einen Debug-Rückgriff, ohne JSONB-Last aufzubauen.

Hash-Strategie pro Konnektor-Typ. Jeder Konnektor-Typ bestimmt selbst, welches Feld-Set einen Dokument-Hash bildet:

  • Upload-API — SHA-256 des Byte-Inhalts. Deterministisch, trivial.
  • Confluence — SHA-256 von storage.value + version.number. Der API-eigene Version-Counter macht Content-Diffs schneller als reiner Body-Hash.
  • SharePointeTag direkt aus der MS Graph API. Bereits ein eindeutiger Änderungs-Token.
  • TeamslastModifiedDateTime + message-count pro Channel. Hash über das Tupel.
  • Jiraupdated + Hash über gefilterte fields (excl. transient wie worklog und watches).

Die Hash-Funktion wird Teil des BaseConnector-Interface (§13.2):

class BaseConnector:
    async def fetch_manifest(self, source: ChatbotSource) -> dict[str, str]:
        """Return {external_id: content_hash} ohne Body-Fetch."""
    def content_hash(self, document: Document) -> str:
        """Konnektor-eigene Hash-Strategie für Einzel-Dokumente."""

Konnektoren ohne brauchbaren Manifest-Endpoint (seltene Randfälle, z.B. Legacy-REST-APIs ohne Bulk-Metadata) fallen transparent auf Full-Sync zurück — fetch_manifest kann NotImplementedError werfen, der Scheduler fängt das ab.

Sync-Ablauf (7-stufig).

  1. fetch_manifest(source) — Upstream-Manifest (nur external_id + Hash, keine Bodies) holen
  2. Letzten Snapshot aus connector_sync_snapshots laden (via (source_id, created_at DESC))
  3. Root-Hash-Vergleich — Early-Exit bei Gleichheit, kein weiterer Zugriff
  4. Diff auf Dokument-Ebene: added, modified, removed anhand der beiden document_hashes-Maps
  5. Nur added ∪ modified fetcht der Konnektor als Full-Document, chunked und embedded (§6a.1, §6a.2)
  6. removed wird aus Qdrant entfernt (§6.3 source_id + external_id-Filter)
  7. Neuer Snapshot mit aktuellem document_hashes + root_hash persistiert; Retention-Job läuft

Verhältnis zu anderen §§. Das Interface-Update gehört zu §13.2 (Base-Interface); der Scheduler aus §13.3 konsumiert die Manifest-Funktion; die Versionierung aus §13.4 bleibt orthogonal (Konnektor-Version-Upgrade erzwingt einmaligen Full-Sync, da sich die Hash-Strategie ändern kann). Das Persistenz-Ziel in Qdrant (§6) bleibt unverändert; der Merkle-Sync reduziert nur die Write-Menge, nicht die Collection-Struktur.

Erwarteter Impact. Beim Referenz-Szenario (5.000 Confluence-Seiten, stündlicher Sync, ~10 Änderungen/Tag):

  • Embedding-Last: ~99,8 % Reduktion (240 Embeddings/Tag statt 120.000)
  • Upstream-API-Calls: ~96 % Reduktion (Full-Fetch nur für die geänderten Seiten plus ein schlankes Manifest-Endpoint-Call pro Zyklus)
  • Qdrant-Write-Last: proportional zur Embedding-Last

Inspirations-Quelle. LEANN's leann watch (Merkle-Tree für File-System-Change-Detection). Implementation ist Kora-eigen; kein Code-Import, nur das Pattern.

Aufwand. ~9h — integriert in Block 13 (Konnektor-Framework-Stub, §13). Der Block wächst damit von 48h auf 57h in §17.2.

13.4 Konnektor-Versionierung (festgelegt in v4)

Semver-basiert: Jeder Konnektor hat eine Version (MAJOR.MINOR.PATCH). Beim Deployment-Update wird bei connectors-Tabelle die neue Version registriert, wenn Breaking Changes mit Tool-Support migrierbar sind.

Auto-Migration: - PATCH/MINOR-Updates: transparent, keine UI-Action nötig - MAJOR-Updates: Bestehende chatbot_sources bleiben auf alter Version verknüpft. UI-Hinweis "Konnektor-Update verfügbar, migrieren?" - Bei kritischen Security-Issues: zwangs-Update mit 30-Tage-Ankündigung

chatbot_sources.connector_version_at_creation hält die Version zum Anlagezeitpunkt fest.

13.5 Permissions — Infrastruktur in v1.0.0, aktive Nutzung später

Konnektoren mit supports_permissions = true füllen permissions.read_groups in Qdrant-Payload. Retrieval-Filter nutzt das noch nicht in v1.0.0, wird ab v1.1.0 oder v1.2.0 aktiviert (siehe Konnektor-Roadmap §6).

13.6 Konnektoren für v1.0.0

Upload-Konnektor (Refactoring aus Bestand, ~8h) — siehe Konnektor-Roadmap §2.1

Confluence-Konnektor (~40h) — siehe Konnektor-Roadmap §2.2. Entscheidung im Dialog: Bleibt in Welle 1, trotz Unsicherheit über AVS-Realität (pragmatische Annahme-mit-Korrektur-Strategie).

Für v1.1.0 geplant: MediaWiki-Konnektor (~24h) — Details siehe Konnektor-Roadmap §2.3. In v1.0.0 bewusst ausgeklammert (Entscheidung 2026-04-24), weil AVS-Realität bezüglich MediaWiki unbestätigt ist und Confluence als erster externer Konnektor Priorität hat. Scope-Details (API-Client, Wikitext→Markdown, Inkrement via Revision-Timestamps) bleiben wie in der Konnektor-Roadmap hinterlegt; nur das Release-Zeitfenster ist jetzt firm.

§13.6a — MCP als Source-Type (offen, Roadmap v1.1.0)

Hinweis zur Verortung: ursprünglich als "§6.X" angedacht; im Konzept leben Source-Types in §13.6 (Konnektoren-Katalog), daher hier verankert.

Bisheriger Source-Type-Katalog (§13.6, §1 Drei-Schichten-Modell): upload, confluence, mediawiki (v1.1.0), sharepoint (v1.1.0+), teams (v1.1.0+), jira (v1.1.0+) — jeweils mit eigenem Konnektor- Code, Rate-Limit-Scheduler (§13.3), Inkrement-Sync (§13.3a). Das Modell ist N×Konnektor-Code für N Quellen.

MCP-Vorschlag. Generischer mcp_connector-Source-Type parallel zu den existierenden Types. Statt jeden Konnektor neu zu bauen, können MCP-Server (Confluence, Jira, Notion, Slack, beliebig) angebunden werden — Industrie-Standard-Protokoll, dokumentierter Vertrag mit Drittanbietern, AVS als designierter SDK-Stufe-2-Entwickler (§13.10) hätte einheitliche Integration-Surface.

Strukturelle Fragen, die vor Implementierung geklärt werden müssen:

  • Vierter Type oder Meta-Type? Lebt MCP gleichberechtigt neben confluence, oder ist confluence ein Spezialfall von mcp_connector mit Confluence-MCP-Server-URL?
  • Tenant-Isolation: Wie greift Multi-Tenancy bei MCP — pro Tenant eigene MCP-Server-Instance oder shared MCP-Server mit Tenant-Header?
  • Bulk-Ingestion vs. Tool-Use: MCP ist primär für Agent-Tool-Nutzung designt. Bulk-Dokumenten-Sync über MCP hat Protokoll-Overhead. Eigener Rate-Limit-Scheduler für Ingestion bleibt sinnvoll, MCP nur für inkrementelle Updates oder Live-Queries.

Architektonische Konsequenz: MCP als Source-Type ist keine reine Konnektor-Hinzufügung, sondern beeinflusst die Source-Type-Architektur strukturell. Implementierung erst nach Beantwortung der drei Fragen oben — voraussichtlich in v1.1.0.

Roadmap-Anker: Roadmap-Karte L1 (MCP-Konnektor-Source-Type), voraussichtlich v1.1.0.

13.7 Credentials-Verwaltung

AES-GCM-Verschlüsselung mit Master-Key aus Environment. Nur tenant-admin kann CRUDen. Details unverändert zu v3 §13.6.

13.8 Attachment-Größen (festgelegt in v4)

Default 50 MB pro Attachment, pro Source konfigurierbar. Größere Dateien werden mit Warnung übersprungen und in Sync-Jobs-Metrics dokumentiert.

13.9 Sync-Monitoring

Status-Badges im Tenant-UI, E-Mail-Alert bei 3 aufeinanderfolgenden Fehlern, Prometheus-Metriken. Details unverändert zu v3 §13.7.

13.10 Konnektor-API als interner Contract (SDK-Stufe 2)

Das BaseConnector-Interface (§13.2) ist in v4 nicht nur ein internes Implementierungs-Detail, sondern ein stabiler Contract, an dem luki und AVS als Zweit-Entwickler gemeinsam arbeiten können.

Stufe 2 des SDK-Modells bedeutet konkret:

  • Stabile API-Signaturen: Breaking Changes am BaseConnector-Interface werden wie regulärer Konnektor-Semver gehandhabt (MAJOR-Release mit Auto-Migration wo möglich, 6 Monate Deprecation für manuelle Migration)
  • Kein öffentliches SDK: Keine Publikation auf PyPI, keine öffentliche Dokumentations-Website, keine Drittzertifizierung
  • AVS als designierter Zweit-Entwickler: AVS erhält Zugang zum Konnektor-Entwickler-Guide (siehe unten) und kann eigene Konnektoren für AVS-interne Legacy-Systeme bauen
  • Security-Review: Neu entwickelte Konnektoren (von AVS) durchlaufen Code-Review durch luki, bevor sie in ein AVS-Deployment integriert werden

AVS-Konnektor-Entwickler-Guide (als Anhang im Deployment-Paket):

  • Interface-Dokumentation von BaseConnector und ExternalDocument
  • Beispiel-Konnektor-Implementierung (Upload als Referenz)
  • Testing-Framework: Mock-Fixtures für HTTP-Calls, Integration-Test-Template
  • Checkliste für Permission-Extraction
  • Rate-Limit-Best-Practices
  • Submission-Prozess: wie AVS einen Konnektor zur Integration vorschlägt

Aufwand für Guide: ~8h einmalig, fällt in den Docs-Block (§16, Block "Testing & Docs").

Was das nicht ist: Ein Plugin-System mit Zur-Laufzeit-Loading von Drittcode. Konnektoren werden weiterhin im Haupt-Codebase gepflegt und mit jedem Release ausgeliefert. AVS-entwickelte Konnektoren werden als Pull-Requests an luki beigesteuert und beim nächsten Release integriert.

13.11 Deprecation-Policy für Konnektoren

Wenn ein Konnektor abgekündigt werden muss (Quellsystem-EOL, nicht mehr gepflegt, Security-Issue), gilt folgende Policy:

Zeiträume: - Normal-Deprecation: 6 Monate Ankündigung - Security-Notfall-Deprecation: 30 Tage (mit sofortiger Risiko-Info an alle betroffenen Tenants)

Dreistufige Hilfestellung während der Deprecation-Phase:

Phase Zeitpunkt Aktion
1 Ab Ankündigung Persistentes UI-Banner im Tenant-Admin mit Hinweis und Link zum Migrations-Leitfaden
2 50% der Frist verstrichen (bei 6 Monaten: nach 3 Monaten) E-Mail an Tenant-Contact + Operator, Migrations-Leitfaden wird konkret (Alternativ-Konnektoren, Export-Optionen)
3 Letzter Monat vor Abschaltung Tägliche UI-Warnung beim Admin-Login, Bereitstellung des Export-Tools kora-platform connector-export <source_id>

Verhalten am Abschaltungstag: - Sync wird gestoppt, chatbot_sources.status = 'deprecated_disabled' - Bereits indexierte Dokumente bleiben 90 Tage im Qdrant-Index (Chatbot kann weiter antworten) - Einmalige E-Mail an Tenant mit Export-Link - Nach weiteren 90 Tagen: Hard-Delete der Chunks aus Qdrant, Source hard-deleted, document_versions-Einträge gelöscht

Security-Notfall-Abweichung: Bei Security-Incident kann Sync sofort gestoppt werden, aber Daten bleiben zunächst verfügbar (keine sofortige Löschung). Die 90-Tage-Retention startet nach Abschaltungstag.


14. Provisioning-Workflows

14.1 Neuer Tenant (Operator-Aktion)

Operator klickt "Tenant anlegen"
Formular mit Grunddaten + Paket-Auswahl (tier, enabled_modules, Limits)
Atomic Transaktion:
   a) INSERT tenants, tenant_branding, tenant_packages
   b) Keycloak Group in kora-tenants Realm anlegen
   c) E-Mail an Contact mit Initial-Admin-Link (48h gültig)

Kein Chatbot wird automatisch erstellt — Tenant-Admin macht das selbst.

14.2 Neuer Chatbot (Tenant-Aktion)

Unverändert zu v3 §14.2: Wizard → Template → Dokumente → optional Auto-Prompt-Generation.

14.3 Neue Wissensquelle (Tenant-Aktion)

Wizard (unverändert zu v3 §14.3): Konnektor → Verbindung konfigurieren → Scope → Sync-Zeitplan → initialer Sync.

14.4 Löschung

Soft-Delete mit 30-Tage-Grace für Tenants und Chatbots. 7-Tage-Grace für einzelne Quellen. Danach Hard-Delete.


15. Deployment-Paket

15.1 Lieferform

  • Docker-Compose + Images auf privater Registry
  • Versionsspezifisches Install/Update-Skript
  • Keycloak-Init-Scripts (festgelegt in v4): JSON-Realm-Exports für kora-platform und kora-tenants, automatisch importiert beim ersten Start
  • Alembic-Migrationen
  • Initial-Config-Template (.env.example)
  • Runbook: Install, Update, Backup, Restore, Troubleshooting

15.2 Update-Mechanismus

  • Minor-Updates: kora-platform update CLI → Pull neuer Images → Compose-Restart mit Migration
  • Major-Updates: Manueller Runbook-Schritt, ggf. luki-Begleitung via vendor-tunnel
  • Rollback: Jede Version hat Snapshot-YAML

15.3 Monitoring

Prometheus-Endpoint + Structured Logging in JSON + Health-Endpoints + mitgelieferte Grafana-Dashboards.

15.4 Keycloak-Init-Scripts und Bootstrap (festgelegt in v5)

Realm-Initialisierung: - Zwei JSON-Realm-Exports im Deployment-Paket unter infra/keycloak/realms/: - kora-platform-realm.json - kora-tenants-realm.json - Beim ersten Start importiert Keycloak sie automatisch (--import-realm Flag) - Produktiv-URIs hart im JSON; für Dev-Setup separates patch-dev-redirects.sh, das http://localhost:8280/* als Redirect ergänzt

Kritische Konfigurations-Defaults (müssen im JSON fest verdrahtet sein): - kora-tenants-Realm: First-Broker-Login-Flow "Create User If Unique" — verhindert, dass ein User, der aus zwei verschiedenen Tenant-IdPs mit derselben E-Mail kommt, auf einen gemeinsamen Account verlinkt wird. Das ist die Sicherheitsgrenze zwischen Tenants bei späterer Multi-IdP-Nutzung und nicht verhandelbar. - kora-tenants-Realm: Default-Rolle für neue User: tenant-viewer (statt Standard-Keycloak-Rolle). - kora-tenants-Realm: Identity-Provider-Support aktiviert belassen (Default; nur erwähnt, damit es nicht ausversehen abgestellt wird). - kora-platform-Realm: Separate Clients kora-api-vendor-breakglass und kora-api-vendor-tunnel, jeweils mit Client-Session-Max 2h und ausschließlich dem entsprechenden Vendor-User zugewiesen.

Operator-Admin-Bootstrap: - CLI-Kommando kora-platform bootstrap-operator-admin --email <mail> erzeugt einen Keycloak-User im kora-platform-Realm mit Required-Action UPDATE_PASSWORD - Keycloak sendet per execute-actions-email einen 48h-gültigen Action-Link - Erfordert SMTP-Konfiguration (siehe unten)

Tenant-Admin-Initial-Link: Provisioning nutzt Keycloak's PUT /users/{id}/execute-actions-email — Standard-Keycloak-Mechanismus, keine Custom-Token-Logik im Backend.

SMTP-Konfiguration: - Pro Realm per ENV-Variablen (KC_SMTP_HOST, KC_SMTP_PORT, KC_SMTP_FROM, ...) - Im Dev-Setup MailHog als Dummy-SMTP im docker-compose.platform.yml - In Prod: SMTP-Relay durch AVS bereitgestellt

Vendor-Audit-Polling: - Backend-Background-Task (IdpAuditPoller), läuft alle 60s - Abfrage an /admin/realms/kora-platform/events?type=LOGIN&user=vendor-* - Neue Events → E-Mail an AVS-Contact (konfigurierbar über tenants.primary_contact_email des AVS-Default-Tenants oder separate ENV AUDIT_RECIPIENT_EMAIL) - Letzter Event-Timestamp in Redis (avs:audit:last_event_ts), damit Neustart keine Doppel-Mails produziert

Vendor-Accounts beim Init: - Alle drei (vendor-support, vendor-breakglass, vendor-tunnel) werden durch das Init-Script angelegt, aber mit enabled=false - Operator aktiviert sie ad-hoc per Operator-UI (setzt Keycloak-User enabled=true via Admin-API) - Passwort-Setup durch luki-Personal beim ersten echten Zugriff via execute-actions-email


15a. Parallel-Entwicklung und Go-Live-Strategie

15a.1 Repo-Strategie: Long-Lived-Branch im bestehenden Repo

Die v1.0.0-Entwicklung findet im bestehenden avs-chatbot-Repo statt, in einem Long-Lived-Branch platform/v1.0.0. Der main-Branch bleibt während der 6-8 Entwicklungswochen der stabile Demo-Stand; dort laufen nur noch Bugfixes, keine Feature-Entwicklung.

Argumentation gegen Repo-Split: - Identitätskontinuität: Die Plattform ist die nächste Generation des Chatbots, kein separates Produkt - Git-Historie bleibt als wertvolle Referenz zugänglich - Bugfixes können per Cherry-Pick zwischen beiden Strängen ausgetauscht werden - Beim Merge wird v1.0.0 zum Release-Meilenstein mit narrativer Klarheit

15a.2 Container-Trennung und Port-Konvention auf luki-ai

Beide Stränge laufen parallel auf luki-ai. Die Trennung erfolgt auf Container-Ebene:

Komponente Demo (main) kora-platform (platform/v1.0.0)
Platform-URL demo.avs.luki-net.org platform.kora.luki-net.org
Auth-URL auth.avs.luki-net.org auth.kora.luki-net.org
API-Container avs-api kora-platform-api
Postgres avs-postgres (Port 5432) kora-platform-postgres (Port 8232)
Qdrant avs-qdrant (Ports 6333, 6334) kora-platform-qdrant (Ports 8233, 8234)
Redis avs-redis (Port 6379) kora-platform-redis (Port 8235)
Keycloak avs-keycloak (Port 8180) kora-platform-keycloak (Port 8236), Realms kora-platform + kora-tenants
API Public Port 8080 8280
vLLM-Inferenz eigener Service auf Port 8000 gemeinsam genutzt (kein eigener vLLM-Container)
Docker-Netzwerk avs-net kora-platform-net (isoliert)
docker-compose docker-compose.yml docker-compose.platform.yml
Volume-Präfix avs-*-data kora-platform-*-data

Port-Konvention: Alle Platform-Ports beginnen mit 82 (Block-Schema). Die Demo bleibt auf ihren bisherigen Standard-Ports unverändert. Das stellt sicher, dass beim Debugging sofort klar ist, welcher Stack angesprochen wird: 82XX = immer kora-platform. Die letzten zwei Ziffern spiegeln oft den Demo-Port wider (8232 → 5432, 8280 → 8080), was das Memorieren erleichtert.

Der zunächst erwogene 81XX-Block wurde verworfen, weil der Demo-Keycloak bereits auf Host-Port 8180 läuft. Der 83XX+-Bereich bleibt für spätere Umgebungen (Staging, Test) reserviert.

vLLM-Sharing: Im Dev-Setting braucht die Plattform-Instanz kein eigenes GPU-Deployment. Beide API-Container nutzen denselben vLLM-Upstream. Spart GPU-Speicher und vereinfacht das Dev-Setup. Erst beim Go-Live bekommt die Plattform ihre eigene endgültige vLLM-Konfiguration.

Subdomain-Logik: Die Plattform lebt unter kora.luki-net.org, entkoppelt vom AVS-Tree. platform.kora.luki-net.org ist die API, auth.kora.luki-net.org die Keycloak-Instanz (analog zum auth.avs.luki-net.org-Muster der Demo). Die Struktur ist erweiterbar (docs.kora.luki-net.org, status.kora.luki-net.org können später dazukommen). AVS bleibt als Kunde, Operator und Vertragsgegenstand bestehen — aber nicht mehr als Subdomain-Präfix.

15a.3 Arbeitsmodell während der 6-8 Wochen

  • Alle Platform-Feature-Arbeit geht in platform/v1.0.0
  • Demo-main bekommt nur Bugfixes, kein neues Feature-Development
  • Wechselseitige Fixes: Bugfixes, die beide Stränge betreffen, werden zuerst auf main committed, dann per Cherry-Pick in den Platform-Branch übertragen
  • Zwei separate CI/CD-Pipelines, eine pro Branch, mit jeweils eigenem Deployment-Ziel
  • Separate Grafana-Dashboards oder zumindest differenzierende Labels (deployment="demo" vs. deployment="platform")

15a.4 Go-Live-Strategie: "Frische Leinwand"

Die Plattform-Instanz startet mit leeren Daten. Bestehende Demo-Daten (Sessions, Feedback, indexierte Dokumente) werden nicht migriert. Das reduziert Migrations-Komplexität und entfällt Edge-Case-Behandlung für inkonsistente Altdaten.

Konsequenz: Die Demo wird beim Go-Live sichtbar "zurückgesetzt" — neue Sessions, keine historischen Feedback-Daten, keine Sessions-Historie. Das muss im Vorfeld kommuniziert werden (intern bei luki und bei AVS).

Mitigation: Operator-Admin legt vor dem Go-Live auf der Plattform-Instanz die gewünschte Grundkonfiguration an: - Default-Tenant "AVS-Intern" - Ein oder mehrere Chatbots mit Templates (z.B. Meldeschein) - Shared-Collection mit AVS-Handbüchern neu befüllt (via Konnektor oder Upload) - Smoke-Test über 10-20 typische Queries

15a.5 Go-Live-Ablauf (DNS-Switch)

Vorbereitung (Tag -1):
  ✓ Plattform-Instanz hat produktive Konfiguration (Tenant, Chatbot, Docs)
  ✓ Smoke-Tests 10-20 Queries bestanden
  ✓ Backup der Demo-Daten (Archiv, für Notfall-Rollback)

Go-Live (Tag 0):
  1. DNS: demo.avs.luki-net.org CNAME → platform-backend
     (alte Demo-Container laufen noch, aber ohne Traffic)
  2. Verifikation: 3-5 Smoke-Queries über die alte URL
  3. nginx/NPMplus-Regel aktualisieren: demo.avs.luki-net.org → neues Backend
  4. Alte Demo-Container stoppen (docker-compose down)
  5. Platform-Instanz übernimmt produktiv

Rollback-Option (innerhalb 24h):
  ✗ DNS zurück auf alte Demo-Container (die noch gestoppt, aber nicht gelöscht)
  ✗ Platform-Instanz bleibt auf platform.kora.luki-net.org erreichbar

Kein sichtbarer Downtime für demo.avs.luki-net.org — die URL bleibt erreichbar, nur das Backend wechselt.

15a.6 Nach dem Go-Live

  • Alte Demo-Container nach 7 Tagen Observation-Phase gelöscht (Docker-Volumes für Notfall-Rollback behalten)
  • platform.kora.luki-net.org kann als internes Staging weitergeführt werden oder abgeschaltet
  • Repo: platform/v1.0.0 wird zu main gemerged, Tag v1.0.0 gesetzt
  • main ist jetzt der neue Plattform-Stand; Demo-Code-Pfade werden in einem Cleanup-Release (v1.0.1) entfernt

16. Migration vom heutigen Stand

16.1 Ausgangslage

Der Platform-Branch startet mit leerer Datenbank und leerem Qdrant. Die Alembic-Migration 008 erstellt das v1.0.0-Schema von Grund auf, ohne Altdaten-Backfill. Die alte Demo-Instanz auf main läuft parallel weiter (siehe §15a).

16.2 Initialisierungspfad (Alembic 008 + Seed-Skripte)

Schritt 1: Schema-Migration (leeres Ziel-System)
   → Alle Tabellen aus §4.1 werden erstellt (tenants, chatbots, chatbot_templates,
     chatbot_branding, tenant_branding, tenant_packages, platform_modules,
     connectors, chatbot_sources, credentials, sync_jobs, vendor_access_log)
   → Chat-Session-/Feedback-/Document-Tabellen aus v0.8.0-Schema,
     aber mit tenant_id/chatbot_id NOT NULL von Anfang an
   → RLS-Policies aktiv

Schritt 2: Konnektor-Registry seeden
   → INSERT INTO connectors VALUES
     ('upload', ...),
     ('confluence', ...),
     ('mediawiki', ...);
   → Seed-Skript ist Teil des Deployment-Pakets

Schritt 3: Template-Seeds (von AVS vor Go-Live vorbereitet)
   → Template "meldeschein" mit System-Prompt, Suggestions, empfohlenen Konnektoren
   → Weitere Templates nach AVS-Bedarf (kurverwaltung, tourismus, ...)

Schritt 4: Default-Tenant "AVS-Intern" provisionieren (Operator-Aktion)
   → Tenant mit slug 'avs-internal', display_name "AVS Intern"
   → Tenant-Paket: alle Module aktiv, Limits großzügig
   → Tenant-Branding aus AVS-CI

Schritt 5: Ersten Chatbot erstellen (Operator- oder Tenant-Admin-Aktion)
   → Chatbot "meldewesen" mit Template meldeschein
   → uses_shared_docs: true
   → Neue AVS-Handbücher werden frisch hochgeladen (nicht aus alter Demo migriert)

Schritt 6: Shared-Collection befüllen
   → Operator lädt AVS-Handbücher frisch in shared_meldeschein_handbuecher
   → Indexing läuft, Qualität kann via evaluation_questions verifiziert werden

Schritt 7: Keycloak-Setup (aus Deployment-Paket, §15)
   → Realms kora-platform und kora-tenants werden via Init-Scripts angelegt
   → Operator-Accounts (AVS-Mitarbeiter) eingetragen
   → Tenant-Admin für "AVS-Intern" eingetragen

Schritt 8: Lizenz-Datei einspielen (v4-Schema: Installations-Marker)
   → RSA-signierte Lizenz mit Installation-ID in .env eintragen

Gesamt-Aufwand für das initiale Provisioning: ~4-6h (manuell durchführbar). Automatisierungs-Skripte (für wiederholbare Setups) können parallel entstehen, sind aber nicht Go-Live-kritisch.

16.3 Was aus der alten Demo übernommen wird

Nichts automatisch. Was weitergenutzt werden soll, wird bewusst neu erstellt:

  • AVS-Handbücher: Werden frisch hochgeladen. Wenn die aktuellen Demo-Dokumente die gewünschte Ausgangslage sind, exportiert der Admin sie aus der alten Instanz und lädt sie neu hoch.
  • Evaluation-Questions: Die 20 Testfragen aus der alten evaluation_questions-Tabelle werden per SQL-Export und SQL-Import ins neue Template "meldeschein" übernommen.
  • System-Prompt: Aus dem aktuellen system_de.j2 / system_en.j2 wird der neue Template-Prompt destilliert.
  • Widget-Branding: Aus der aktuellen .env-Konfiguration werden AVS-Farben und Logo ins neue tenant_branding übernommen.

Alles andere (Sessions, Feedback, individuelle Nutzer-Queries) ist weg. Das ist die Konsequenz der "frische Leinwand"-Entscheidung.

16.4 Rückwärts-Kompatibilität

  • Widget-Einbettungen auf der AVS-Website: Müssen um data-tenant="avs-internal" und data-chatbot="meldewesen" ergänzt werden. Bis zum Update funktioniert das Widget nicht — das ist im Go-Live-Plan zu berücksichtigen.
  • API-Konsumenten (falls vorhanden): Bekommen beim ersten Request nach Go-Live HTTP-400 wegen fehlender Tenant-Context. Muss vorher kommuniziert werden.
  • Bookmarks/Verlinkungen: Die URL demo.avs.luki-net.org bleibt, die Verknüpfung zu bestimmten Session-IDs oder Dokument-IDs geht aber verloren.

17. Aufwandsschätzung (v5.3.4)

17.1 Gegenüber v3 eingesparte Aufwände

  • Zweistufige Modul-Freischaltung: -6h
  • Lizenz-Feature-Checks im Code: -8h
  • Lizenz-Obergrenzen-Constraints: -4h
  • Vendor-UI Modul-Registry Stufe 1: -4h

Gesamt-Einsparung: ~22h

17.2 v4 Variante A (pragmatisch, 1 Konnektor)

Block v3 v4 Anmerkung
1. Tenant-Datenmodell + Migrations 28h 28h tenant_packages statt komplexer License-Logic, ähnlicher Aufwand
2. Keycloak Dual-Realm 16h 16h
3. API Scope-Middleware 16h 12h -4h v5.1-Reduktion (Dual-Middleware-Reuse aus Block 2 FastAPI-Skelett)
4. Qdrant Collections + erweiterte Metadaten 32h 32h
5. Chatbot-Templates & CRUD (inkl. 7a UI-Framework-Scaffolding, v5.3) 16h 16h + ~12h +12h in v5.3: 7a UI-Framework-Scaffolding (Vue-3-Build-System, Auth-Middleware-Integration, Base-Layout) als Block-5-Dependency vorgezogen aus Block 7; war bisher Scope-Teil von Block 7 §9.3
6. Plattform-Module (einstufig) 16h 10h -6h durch Vereinfachung
7. Operator-UI (AVS-Ebene): Content (= 7b) 32h ~20h -12h in v5.3: UI-Framework-Scaffolding (7a) nach Block 5 vorgezogen; verbleibt: Tenants-CRUD, Template-CRUD-Integration (nutzt Block 5), Modul-Katalog (nutzt Block 6), Vendor-Audit-Log-View, Bulk-Operationen
8. Tenant-UI mit Chatbot-/Sources-Management 44h 44h
9. Vendor-UI 16h 12h -4h durch Wegfall Modul-Registry-Stufe-1
10. System-Prompt-Automatismus 24h 24h
11. Widget 12h 14h +2h Refined-Reconciliation v5.3.3 (2026-05-01): Mid-Point zwischen 12h-§17.2-Baseline und 16h-frühere-roadmap-Schätzung, motiviert durch Sub-Route-Pattern aus Block 8.6 plus Widget-Schreib-Pfad-Refactor aus Block-8.7-Discovery; siehe §17.2a Überlapp-Quelle Nr. 6
12. Provisioning 18h 14h -4h durch Entfall Lizenz-Constraint
13. Konnektor-Subsystem (Framework + Upload-Konnektor + Credentials + Merkle-Sync) 48h 57h +9h in v5.2 für §13.3a Merkle-Tree-Snapshot-Sync; in v5.3 klargestellt: Upload-Konnektor-Port aus Demo ist expliziter Teil-Scope (~8h intern) — kein separater 13a-Eintrag mehr (war doppelt gezählt, siehe §17.2a)
13b. Confluence-Konnektor 40h 40h
14. Deployment-Paket + Parallel-Dev-Setup (§15a) 16h 20h +4h für docker-compose.platform.yml, Subdomain-Setup, NPMplus-Routing
15. Go-Live (statt Migration, §16) 20h 12h -8h durch frische Leinwand, kein Daten-Backfill
16. Testing & Docs (inkl. AVS-Konnektor-Entwickler-Guide) 24h 30h -2h einfacheres Testing, +8h AVS-Guide
Block 18 Docling Ingestion-Normalisierung (§6a.1) 18h neu in v5.2
Block 19 BGE-M3 + Hybrid Retrieval (§6a.2) 25h neu in v5.2
Block 20 AST-Aware Chunking (§6a.3) 13h neu in v5.2
Total ~400h ~449h v5.1 → v5.2: +65h

Gerundet: ~449h für v1.0.0 (siehe §18 Phasenplan; Einzelposten-Summe abweichend ~467h nach v5.3 — Aufschlüsselung in §17.2a).

v5.2-Erweiterung fügt drei neue Quality-Blöcke (18/19/20) in Phase C vor Block 13 ein und erweitert Block 13 um den Merkle-Sync-Einschub (§13.3a). Gesamt-Aufwand-Steigerung: 65h für v1.0.0 (16 Blöcke jetzt → 19 Blöcke + Block 13 erweitert). Aufschlüsselung: +9h Block 13 Merkle-Sync, +18h Block 18 Docling, +25h Block 19 BGE-M3, +13h Block 20 AST-Chunking. Siehe §6a und §13.3a für Konzept-Details, §18 für Phasenplan-Update.

17.2a Reconciliation: Einzelposten vs. Phasen-Summen (v5.3)

Auditiert in v5.3. Die Aufwandsschätzung wird in diesem Konzept an zwei Stellen aggregiert:

  • Als Summe der Einzelposten-Zeilen in §17.2 oben (~467h nach v5.3-Änderungen, vorher ~475h in v5.2)
  • Als Summe der Phasen-Aufwände in §18 (~449h für v1.0.0 ohne Block 17)

Die verbleibende Differenz beträgt nach v5.3 etwa ~18h (vorher in v5.2 ~26h; die v5.3-13a-Entfernung hat 8h konkret aufgelöst). Diese Diskrepanz ist keine Fehlkalkulation, sondern reflektiert Zähl-Überlapp bei einzelnen Posten plus Scope-Interpretationsunterschiede zwischen Block-Sicht und Phasen-Sicht. In v5.3 identifizierte Überlapp-Quellen (auf Basis der v5.2/v5.3-Version):

  1. 13a-Doppelzählung (in v5.3 aufgelöst, -8h): Die Einzelposten-Zeile "13a Upload-Konnektor-Refactoring 8h" war in der Block-13-Zeile "Framework + Upload + Credentials" bereits enthalten. Upload war explizit Teil des Block-13-Scopes. Die 13a-Zeile ist in v5.3 aus §17.2 entfernt; der Upload-Scope bleibt implizit und intern mit ~8h Teil-Scope in Block 13.
  2. Block 14 Parallel-Dev-Setup-Anteil (~4h) in Phase A enthalten: Der Block-14-Einzelposten-Wert (20h) wird in §18 auf Phase A (~4h Parallel-Deployment-Einrichtung) und Phase D (~16h Deployment-Paket + Update-Mechanismus) gesplittet. Die Phasen-Summe zählt die 4h einmal unter A und 16h unter D; die Einzelposten-Zeile zählt 20h in einem Block, ohne diesen Split abzubilden. Das ist kein Fehler, sondern Phasen-Sicht-Granularität.
  3. Block 16 Testing & Docs (30h) hat konzeptionellen Überlapp mit Block-internen Tests: Ein Teil der 30h für "Testing & AVS-Konnektor-Entwickler-Guide" wird in den Einzel-Blöcken (z.B. Unit-Tests in Block 13) mitgemeint, aber separat gezählt. Der Effekt ist klein (~2–4h), nicht exakt quantifizierbar, und wird bewusst so belassen — Block 16 bündelt integrations- und Dokumentations-Aufwand, der ohne dedizierten Posten untergehen würde.
  4. MediaWiki-Konnektor v1.0.0-vs-v1.1.0-Ambiguität (in v5.3.1 aufgelöst): Konnektor-Roadmap §2.3 markierte MediaWiki ursprünglich als "v1.0.0 oder v1.1.0 je nach Zeitbudget". Die §17.2-Tabelle führt keinen expliziten MediaWiki-Einzelposten (er ist nicht in 13b enthalten, 13b ist Confluence). Phase C im §18-Plan umfasst MediaWiki als Flex-Block. In v5.3.1 (2026-04-24) wurde MediaWiki firm auf v1.1.0 festgelegt — die ~24h-Ambiguität ist damit aus v1.0.0-Scope entfernt, Phase-C-Aufwand in §18 reduziert sich auf ~153h (siehe §13.6 und Konnektor-Roadmap §2.3).
  5. Block-5-/Block-7-Split-Artefakt (v5.3): Die 7a-Scaffolding-Verschiebung von Block 7 nach Block 5 ist numerisch neutral: Block 5 steigt um +12h, Block 7 sinkt um -12h. Die 7a-Arbeit wird aber in §18 unter Phase A gezählt (als Teil von Block 5), während sie inhaltlich zur UI-Framework-Schicht (Phase B) gehört. Phasen-Sicht vs. Block-Sicht divergieren hier legitim.
  6. Block 11 Widget §17.2-vs-roadmap-Diskrepanz (in v5.3.3 aufgelöst, +2h): §17.2 führte Block 11 mit 12h, die Roadmap-Status-Box zeitweise mit 16h (Konsequenz aus früheren Pre-Refinement-Schätzungen). TODO-Konzept-01 (Drift-Status-Audit 2026-04-30) hat die Diskrepanz benannt. In v5.3.3 (2026-05-01) als verbindliche Refined-Schätzung 14h (Mid-Point) festgelegt; der Aufschlag berücksichtigt das Sub-Route-Pattern aus Block 8.6 und den Widget-Schreib-Pfad-Refactor aus dem Block-8.7-Discovery (Feedback-Endpoint zeigt nach AVS-Demo-Schema, soll auf Platform). Phasen-Total v1.0.0: ~425h → ~427h, in Konzept-Header und Roadmap-Fortschritts-Box konsistent geführt.
  7. Pattern-Reife-Quote-Trendlinie pro Block-Typ (in v5.3.4 ergänzt, neutral, +0h): Pre-v5.3.4 ging die §17.2-Aufwandsschätzung implizit von einem Pauschalwert von ~60 % Pattern-Reife-Quote (Real / Refined) aus — abgeleitet aus dem Block-8-Schnitt. Mit fünf Datapoints aus Block 8 (~60 %), Block 11 (~40 %), v1.3.0-D2 (23 %), v1.3.0-D1 (31 %) und v1.3.0-E (~50 %) ist eine differenzierte Trendlinie pro Block-Typ möglich:

    Block-Typ Pattern-Reife-Quote (Real/Refined) Datapoints
    Cleanup-Welle Backend ~25 % v1.3.0-D2 (23 %)
    Cleanup-Welle Frontend ~30 % v1.3.0-D1 (31 %)
    Foundation-Reuse hoch ~40 % Block 11 (40 %)
    Foundation-Reuse + neuer Code ~50 % v1.3.0-E (~50 %)
    Foundation-erweiternd (neue Achse) 50–80 % Block 8 (~60 % Schnitt: 8.0 50 %, 8.2 80 %, 8.3 57 %, 8.4 64 %, 8.6 63 %, 8.7 43 %)

    Begründung: Quote sinkt mit steigendem Foundation-Reuse-Grad. Cleanup-Wellen profitieren stark vom etablierten Pattern (neue Routen sind Spiegelungen), Foundation-erweiternde Blöcke bauen neue Achsen mit weniger Reuse.

    Anwendung für Schätzungen:

    • Block 13 Konnektor-Framework (Foundation-erweiternd, neue Achse): Refined 57h × 50–70 % = Real ~28–40h
    • Block 18+19+20 Quality-Foundation (Foundation-erweiternd, partial Reuse): Refined ~56h × 50–60 % = Real ~28–34h
    • Block 8.5 Stub (Cleanup-Niveau): Refined ~6h × ~30 % = Real ~2h
    • TODO-Auth-NEU (Wartung mit niedriger Reuse): Refined 1.5h × ~100 % (kein Reuse) = Real ~1.5h

    Konsequenz: Refined-Schätzungen in §17.2 sind ein Boden, nicht der Real-Wert. Bei Block-spezifischer Schätzung ist die Block-Typ-Quote anzuwenden. Der Konzept-Header-Total bleibt 427h Refined-Boden; die Real-Trendlinie wird hier dokumentiert, nicht als zweite Aufwand-Zeile geführt (vermeidet Doppel-Tracking).

  8. v1.3.0-Cleanup-Wellen außerhalb des §17.2-Original-Plans (in v5.3.4 als §17.5 separat ausgewiesen, +0h zur 427h-Hauptachse): Die v1.3.0-Welle (D2 Backend Polish ~5h Real / 22h Refined, D1 Frontend Polish ~4.5h / 14.5h, E Operator-Per-Chatbot-Page ~3.5h / 6–8h) ist ein Wartungsschuld-Reduktion + Customer-Wert-Schluss, kein Original-Plan-Block. Sie schließt 33 TODO-IDs aus den Block-7-Review-Familien plus einen Block-11-Hand-off-Datapoint. Die Welle wäre in §17.2 ein Fremdkörper (kein Konzept-Block, kein Phasen-Plan-Slot). Achse-Trennung: §17.2-Refined-Boden bleibt 427h für v1.0.0 (Original-Plan), v1.3.0-Welle wird in §17.5 separat als ~13h Real / ~42.5h Refined geführt. Roadmap-Fortschritts-Box rechnet weiterhin gegen die 427h-Hauptachse (~203h Real / 427h = ~48 %).

Entscheidung: In v5.3 werden die Einzelposten-Zahlen mit Ausnahmen (13a-Entfernung in v5.3, Block-11-Refinement in v5.3.3) nicht korrigiert. Rationale:

  1. Die Einzelposten-Zeile dokumentiert die Sub-Scope-Realität pro Block (gut für Block-interne Planung)
  2. Die Phase-Total-Zeile dokumentiert die aggregierte Implementierungs-Realität (gut für Projekt-Management)
  3. Beide Sichten sind legitim; ihre numerische Divergenz ist Artefakt der zweifachen Aggregation und kein Planungsfehler
  4. Eine "Korrektur" zur künstlichen Angleichung würde eine der Sichten verzerren

Folge: Die maßgebliche Zahl für Zeitbudget und Fortschrittsmessung ist die konsolidierte Refined-Boden-Summe ~427h für v1.0.0 (Konzept-Header, Roadmap-Fortschritts-Box). Der ursprüngliche Phase-Total-Wert ~449h aus v5.2 wurde in v5.3.1 (MediaWiki-Verschiebung, −24h auf ~425h) und in v5.3.3 (Block-11-Refinement, +2h auf ~427h) konsolidiert. v1.3.0-Cleanup-Wellen werden in §17.5 separat geführt (vgl. Reconciliation Nr. 8) — sie sind außerhalb der 427h-Hauptachse. Einzelposten-Zahlen in §17.2 dienen der internen Scope-Orientierung pro Block; die Pattern-Reife-Quote-Trendlinie aus Reconciliation Nr. 7 erlaubt die Übersetzung von Refined-Boden zu Real-Erwartung.

17.3 Reduktionsoptionen

Wenn Zeitbudget unterschritten werden muss: - System-Prompt-Automatismus → v1.1.0: -24h - Vendor-UI minimal → v1.1.0: -12h - AVS-Konnektor-Entwickler-Guide → v1.1.0: -8h (würde SDK-Stufe-2-Positionierung aufschieben, aber API bleibt stabil) - Konnektor-Subsystem ohne Confluence (nur Framework + Upload): -40h - Komplett ohne Konnektor-Framework (zurück zu v2-Niveau): -96h

Minimal-Scope ohne diese Reduktionen gemeinsam: ~200h (aber wenig produktreif).

17.4 Block 17 — Post-Launch, separates Budget (festgelegt in v5)

Block 17 (Tenant-SSO-Self-Service) fällt nicht in v1.0.0-Budget. Er wird als v1.0.1 oder v1.1.0 nach Launch eingeplant.

Komponente Aufwand
IdPProvisioningService + Keycloak-Admin-API-Wrapper 8h
5 Preset-Templates (Entra ID, ADFS, Google Workspace, generisches OIDC, generisches SAML) 10h
REST-API-Endpoints + Permission-Checks 4h
Tenant-Admin-UI (Vue 3: Wizard + Detail-Ansicht) 12h
Operator-UI (wiederverwendet, minimaler Zusatz mit "Im Auftrag"-Banner) 3h
Verbindungstest-Logik (Metadata-Fetch, SAML-Parse) 4h
Integration-Tests (Mock-Keycloak, Entra ID + generisches OIDC) 6h
Operator-Runbook "Tenant-SSO einrichten (Notfall/Luki-Pfad)" 2h
Gesamt Block 17 ~49h

Rationale für Post-Launch: - AVS hat beim Launch ohnehin Setup-Kontakt mit ersten Tenants (Template, Widget-Einbettung, Handbuch-Upload). 30-min SSO-Setup durch Operator fügt sich dort ein. - Bis v1.0.1/v1.1.0 gibt es Erfahrungswerte (welche IdPs verlangen Kurverwaltungen wirklich — vermutlich zu 90% Entra ID). - Keycloak-Architektur in v1.0.0 (§2.6, §15.4) ist bereits IdP-fähig — keine Nacharbeit am Fundament.

Pfade zur SSO-Konfiguration: 1. Tenant-Admin per Self-Service-UI (v1.0.1/v1.1.0) 2. Operator per "Im Auftrag"-UI (v1.0.1/v1.1.0) 3. Luki per Keycloak-Admin-Console als Breakglass-Fallback (ab v1.0.0 nutzbar)

Alle drei Pfade gehen durch denselben IdPProvisioningService — keine Logik-Duplikation.

17.5 Cleanup-Wellen — separates Budget außerhalb 427h-Hauptachse (festgelegt in v5.3.4)

Cleanup-Wellen entstehen nach v1.x.0-Tags als Wartungsschuld-Reduktion (TODO-Schließung aus Code-Review-Familien) plus optional Customer-Wert-Schluss (Hand-off-Datapoints aus vorherigen Blöcken). Sie sind systematisch außerhalb der §17.2-Original-Plan-Aufwände, weil:

  • Sie schließen TODO-IDs aus Block-Reviews, die im Original-Refined-Boden nicht abgebildet sind
  • Sie liefern keinen neuen Konzept-Block, sondern konsolidieren bestehende Blocks
  • Sie haben eigene Pattern-Reife-Quoten (deutlich besser als die ~60 %-Schnitt-Erwartung), siehe §17.2a Reconciliation Nr. 7

Achse-Trennung: §17.2-Refined-Boden bleibt 427h für v1.0.0; Cleanup-Wellen werden hier separat geführt. Roadmap-Fortschritts-Box rechnet weiterhin gegen 427h-Hauptachse.

17.5.1 v1.3.0-Welle (D2 + D1 + E) ✅ Erledigt 2026-05-02

Sub-Block Charakter Refined Real Quote Merge
D2 Backend Polish Cleanup-Welle Backend (17 TODOs in 4 Familien) ~22h ~5h ~23 % bb0abbd
D1 Frontend Polish Cleanup-Welle Frontend (16 TODOs + 2 D2-Last-Mile) ~14.5h ~4.5h ~31 % 18c4884
E Operator-Per-Chatbot-Page Foundation-Reuse + neuer Code (Hand-off Block 11) ~6–8h ~3.5h ~50 % f5baf0f
v1.3.0-Welle Σ ~42.5–44.5h ~13h ~30 % (3 Sub-Merges)

Foundation-Reuse-Bilanz:

  • D1: flattenError, useApi-Pattern aus Block 7
  • D2: BYPASSRLS-Pattern aus operator_branding_router (Block 8.6); zentraler scope_guards.py-Helper aus TODO-Block-7-NN-01
  • E: Service-Layer (BrandingService, FeedbackService) aus Block 8.6/8.7 reused; EmbedCodeSnippet aus Block 11 reused

Datapoint-Wert für künftige Schätzungen: v1.3.0-Welle bestätigt, dass Cleanup-Wellen mit hoher Foundation-Reuse-Quote (~30 % Schnitt) systematisch unter dem 60 %-Pauschalwert liegen. Diese Erkenntnis ist in §17.2a Reconciliation Nr. 7 als Trendlinie pro Block-Typ formalisiert.

17.5.2 Künftige Cleanup-Wellen

Pattern wiederholt sich potenziell zwischen v1.x.0-Tags:

  • Vor Block 13 (Konnektor-Framework): Block-7-Review-Familien-Rest (D3-Items) — TODO-Block-7-NN-05, -7-3-01, -7-4-04, -7-4-05, -4b
  • Vor Block 14 (CI-Pipeline): TODO-Platform-11/12 (Pytest-Env, Profil-Trennung)

Jede Welle erhält einen eigenen §17.5.x-Sub-Eintrag mit Refined/Real/Quote-Bilanz; die §17.2a-Trendlinie wird mit jedem neuen Datapoint kalibriert.


18. Phasenplan (aktualisiert)

§18 wurde in v5.3 auf die 5-Phasen-Struktur der Platform-Roadmap umgestellt (vorher 4 Phasen mit D = Post-Launch; jetzt D = Deployment/Migration/Go-Live und E = Post-Launch). Phase-Namen und Block-Listen sind Roadmap-kanonisch; das Konzept folgt hier der Roadmap, weil die Roadmap die laufend gepflegte Planungs-Sicht ist.

Gesamt-Budget-Leitwert v1.0.0: ~449h (Header-Wert aus v5.2, unverändert in v5.3). Die arithmetische Summe der Phasen-Block-Aufwände nach v5.3 liegt höher (~467–491h je nach MediaWiki-Scope in Phase C); die Differenz ist in §17.2a dokumentiert und resultiert aus Zähl-Überlapp und Scope-Ambiguitäten — kein Planungsfehler. Budget und Fortschritt werden gegen die 449h-Leitzahl gemessen.

Phase A — Fundament (~126h)

Blöcke: 1, 2, 3, 4, 5 (inkl. 7a UI-Framework-Scaffolding), 6. Parallel dazu Block P1 (Speculative Decoding POC; ~30h, Performance-Track, nicht blocking für Multi-Tenancy-Stack).

Phase A legt das technische Rückgrat: Datenmodell mit RLS-Dreischicht-Schutz, Keycloak Dual-Realm + FastAPI-Skelett, API-Scope-Middleware mit ContextVar + SET LOCAL, Qdrant Collection-per-Chatbot mit Shared-Fan-Out, Chatbot-Templates + CRUD mit UI-Framework-Scaffolding (7a-Dependency, in v5.3 vorgezogen), einstufige Plattform-Modul-Freischaltung. Ohne dass Phase A abgeschlossen ist, kann weder Phase B (UIs, die auf dem Scaffolding aufbauen) noch Phase C (Konnektor-Arbeit, die auf Qdrant-Collections schreibt) sinnvoll beginnen.

Parallel zum Multi-Tenancy-Stack in Woche 1:

  • Branch platform/v1.0.0 anlegen
  • docker-compose.platform.yml mit separaten Container-Namen, Ports, Volumes
  • Subdomain platform.kora.luki-net.org einrichten (DNS + NPMplus-TLS)
  • Gemeinsamer vLLM-Upstream konfiguriert
  • Separate Grafana-Labels oder Dashboard

Ziel: Multi-Tenancy technisch funktional, Plattform-Instanz läuft parallel zur Demo, Tenant-Pakete steuern Modul-Verfügbarkeit.

Phase B — UIs & Automation (~126h)

Blöcke: 7 (= 7b Operator-UI-Content, ohne 7a-Scaffolding — das ist in Phase A), 8, 9, 10, 11, 12.

Phase B baut auf dem Scaffolding aus Block 5 (7a-Scope) auf und liefert die drei UI-Ebenen plus System-Prompt-Automation, Multi-Tenant-Widget und Provisioning-Automatisierung. Ohne Phase B ist die Plattform technisch funktional, aber ohne bedienbare Oberfläche — Operator-, Tenant- und Vendor-Rollen hätten nur SQL-/API-Call-Zugang.

Ziel: Operator kann Tenants und Pakete verwalten, Tenant-Admin kann Chatbot- und Source-Management per Klick machen, Vendor hat geprüften Audit-Zugang, Widget ist pro Tenant + Chatbot parametrisierbar, Tenant-Provisioning läuft atomar.

Phase C — Konnektoren, Quality & Templates-Content (~153–177h je nach MediaWiki-Scope)

Blöcke in Reihenfolge:

  • Block 18 — Docling Ingestion-Normalisierung (§6a.1) — 18h
  • Block 19 — BGE-M3 + Hybrid Retrieval (§6a.2) — 25h
  • Block 20 — AST-Aware Chunking (§6a.3) — 13h
  • Block 13 — Konnektor-Subsystem (Framework + Upload-Konnektor + Credentials + Merkle-Sync, §13, §13.3a) — 57h
  • Block 13b — Confluence-Konnektor (§13.6, Konnektor-Roadmap §2.2) — 40h
  • MediaWiki-Konnektor (Konnektor-Roadmap §2.3) — ~24h, v1.0.0 oder v1.1.0 je nach Zeitbudget
  • Template-Inhalte von AVS — AVS-Lieferung, kein luki-Aufwand

Rationale der Block-Reihenfolge. Die Quality-Blöcke 18/19/20 stehen vor dem Konnektor-Subsystem, weil Docling die Document-Normalisierung für alle Konnektoren liefert, BGE-M3 das Embedding-Modell für alle konnektor-indexierten Dokumente vorgibt (re-index-sparend, nur einmal) und AST-Chunking die Chunker-Pipeline erweitert, bevor Konnektoren produktiv indexieren. Block 13 (Konnektor-Subsystem inkl. Upload und Merkle-Sync) baut auf allen drei auf; der konkrete Confluence-Konnektor (13b) und optional MediaWiki setzen auf Block 13 auf.

Ziel: Confluence-Konnektor läuft mit Docling-Normalisierung, Hybrid-Retrieval und Merkle-Sync-Inkrement; Template-Inhalte (Meldeschein, Kurverwaltung) sind befüllt.

Phase D — Deployment, Migration & Go-Live (~62h)

Blöcke: 14 (Deployment-Paket & Update-Mechanismus, ~20h), 15 (Migration vom heutigen Stand & Go-Live, ~12h), 16 (Testing & Dokumentation inkl. AVS-Konnektor-Entwickler-Guide, ~30h).

Phase D bereitet den produktiven Betrieb vor: Deployment-Artefakt inkl. Update-CLI und Rollback-Snapshot-YAML, Migration mit frischer Leinwand laut §15a.4 (keine Daten-Migration), DNS-Switch-Runbook mit Rollback-Kriterien, 7-Tage-Observation. Testing deckt Cross-Tenant-Isolation als Integration-Suite ab; der AVS-Konnektor-Entwickler-Guide ist Liefergegenstand der SDK-Stufe 2 (§13.10).

Go-Live am Ende von Phase D:

  • Operator-Aktion: Default-Tenant, Template, Chatbot, AVS-Handbücher neu befüllen (~4–6h innerhalb Block 15)
  • DNS-Switch demo.avs.luki-net.org → Plattform-Backend
  • Alte Demo-Container stoppen, 7 Tage Observation, dann löschen

Kalenderzeit v1.0.0 (A+B+C+D): 9–10 Wochen bei Vollzeit-Fokus, 6–8 Wochen mit Claude-Code-Parallelisierung.

Phase E — Post-Launch: Tenant-SSO-Self-Service (~49h)

Blöcke: 17 (Tenant-SSO-Self-Service, §17.4, §21). Startet nach Go-Live von v1.0.0, wird als v1.0.1 oder v1.1.0 released.

Voraussetzung: v1.0.0 läuft produktiv, erste 1–2 Tenants sind durch den Operator- oder Breakglass-SSO-Pfad gegangen (Erfahrungswerte für UI-Ausgestaltung). Die Keycloak-Architektur in v1.0.0 (§2.6, §15.4) ist bereits IdP-fähig — Block 17 legt nur UI/UX darauf, keine Änderungen am Fundament.

Ziel: Tenant-Admins können eigenständig SSO für ihr Realm konfigurieren; AVS und luki bleiben als Unterstützungs-Pfade erhalten.

Parallele Planung möglich: Da Block 17 architektonisch isoliert ist (eigenes Modul, keine Verzahnung mit v1.0.0-Kern), kann die Implementierung theoretisch parallel zu späten v1.0.0-Blöcken laufen. Für v1.0.0-Fokus wird das aber nicht empfohlen.


19. Alle Punkte geklärt — Entscheidungs-Log

Nach der Dialog-Runde gibt es keine offenen Konzept-Punkte mehr. Die drei verbliebenen Fragen sind wie folgt geklärt:

19.1 Third-Party-Konnektor-SDK

Entscheidung: SDK-Stufe 2 — AVS als designierter Zweit-Entwickler, stabile interne API, kein öffentliches SDK.

Rationale: Das BaseConnector-Interface wird als stabiler Contract zwischen luki und AVS behandelt, mit Semver-basierten Breaking-Change-Ankündigungen. AVS kann eigene Konnektoren für interne Legacy-Systeme entwickeln und per Pull-Request an luki beisteuern. Kein öffentliches SDK, kein Plugin-Store, keine Drittzertifizierung — das bleibt Option für v2.x+ falls Ökosystem-Bedarf entsteht.

Implementierung: Siehe §13.10. Aufwand +8h für AVS-Konnektor-Entwickler-Guide.

19.2 Konnektor-Deprecation-Policy

Entscheidung: 6 Monate Normal-Deprecation, 30 Tage Security-Notfall, dreistufige Hilfestellung, 90 Tage Daten-Retention nach Abschaltung.

Implementierung: Siehe §13.11.

19.3 AVS-Realitätsdaten zu Konnektoren

Entscheidung: AVS hat die Nutzung von Confluence bestätigt. Die frühere Arbeitshypothese ("wahrscheinlich Confluence") ist jetzt gesicherte Grundlage. Kein weiterer Erhebungsaufwand nötig für AVS-interne Perspektive.

Kurverwaltungs-Ebene bleibt erst durch Produktivbetrieb sichtbar — aktive Erhebung erst, wenn Welle-2-Priorisierung (SharePoint/Jira) akut wird.

Rationale für Planung: Confluence in Welle 1 ist jetzt goldrichtig positioniert, nicht mehr "optimistisch". Risiko eines Fehl-Baus verschwindet.

19.4 Verschobene Punkte (außerhalb v1.0.0-Scope)

  • Lizenz-Server Stufe 2 — Backlog, Trigger: Akquise eines Kunden außerhalb AVS
  • Permission-Framework-Ausbau Phasen 2-4 — siehe Konnektor-Roadmap §6
  • Knowledge-Hub-Modul — nach 2-3 Monaten AVS-Feedback, eigenes Konzept

20. Was als Nächstes

Nach Konsolidierung dieses v5-Konzepts, in empfohlener Reihenfolge:

  1. Parallel-Entwicklungs-Setup (Phase A, Woche 1) — Branch platform/v1.0.0 angelegt, docker-compose.platform.yml aktiv, Subdomain platform.kora.luki-net.org + TLS eingerichtet, vLLM-Sharing konfiguriert (siehe §15a)
  2. Kick-Off Phase A, Block 1 — Tenant-Datenmodell + Migrations + Trigger + RLS abgeschlossen (Blöcke 1.1–1.5)
  3. Phase A, Block 2 — Keycloak Dual-Realm gemäß §2.6, §11, §15.4
  4. AVV-Entwurf aus Bitkom-Muster-Vorlage ableiten, Rollen-Definition aus §2.2 als TOM-Anlage
  5. AVS-Gespräch — Dokumente (v5-Fundament, Konnektor-Roadmap v2, Knowledge-Hub v2) als Diskussionsbasis teilen; neben Rollen (§2) und Paket-Modell (§8) auch Kommunikation des Go-Live-Modus (frische Leinwand, Demo-Daten gehen verloren, siehe §16) abstimmen
  6. Nach Go-Live v1.0.0: Phase D (Block 17 — Tenant-SSO-Self-Service, siehe §21)

21. Block 17 — Tenant-SSO-Self-Service (Post-Launch-Detail)

21.1 Ziel

Jeder Tenant kann für sein kora-tenants-Subkontingent (seine Group + angebundene User) einen eigenen Identity-Provider registrieren, ohne dass der Operator oder luki eingreifen muss. Die Operator- und Luki-Pfade bleiben als Fallback erhalten.

21.2 Architektur

Alle drei Pfade (Tenant-Self-Service, Operator-"Im Auftrag", Luki-Breakglass) gehen durch dieselbe Backend-Komponente IdPProvisioningService, die Keycloak-Admin-API-Operationen kapselt. Die UI-Ebene unterscheidet sich nur darin, wer die Operation auslöst — nicht wie sie ausgeführt wird.

21.3 Preset-Templates

Fünf Presets, jeweils mit fest verdrahtetem First-Broker-Login-Mapper, der neue Shadow-User zwangsweise in die Tenant-Group steckt:

  1. Microsoft Entra ID / Azure AD (OIDC) — Kurverwaltungs-Standard-Fall
  2. Microsoft ADFS (SAML) — zweithäufigster Enterprise-Stack
  3. Google Workspace (OIDC) — kleinere Kurverwaltungen
  4. Generisches OIDC (Power-User-Fallback)
  5. Generisches SAML (Power-User-Fallback)

IdP-Alias in Keycloak: tenant-<slug>-<type> (z.B. tenant-bad-toelz-entra-id) — stellt Cross-Tenant-Namespaceing sicher.

21.4 Sicherheits-Zusicherungen

  • First-Broker-Login-Flow ist "Create User If Unique" (aus §15.4) — kein Cross-Tenant-Account-Linking möglich
  • IdP-to-Group-Mapper ist hart konfiguriert durch den IdPProvisioningService; Tenant-Admin kann den Mapper nicht editieren (nur den IdP selbst entfernen oder Credentials ändern)
  • Default-Rolle neuer Shadow-User: tenant-viewer. Höhere Rollen (editor/admin) vergibt Tenant-Admin explizit pro User
  • Operator-Aktionen im "Im Auftrag"-Pfad erzeugen zusätzlichen App-Audit-Log-Eintrag (neben dem Keycloak-Event)

21.5 API-Surface

  • POST /api/v1/tenants/{id}/idp — IdP anlegen/aktualisieren
  • GET /api/v1/tenants/{id}/idp — aktuelle Config
  • POST /api/v1/tenants/{id}/idp/test — Verbindungstest (Metadata-Fetch)
  • DELETE /api/v1/tenants/{id}/idp — IdP entfernen

Permission: tenant-admin auf eigenen Tenant, operator-admin auf alle Tenants.

21.6 Aufwand

Siehe §17.4 (~49h Gesamt).

21.7 Abgrenzung zu v1.0.0

v1.0.0 liefert die Fähigkeit (Realm-Struktur, IdP-taugliche Konfiguration), aber nicht die UI/UX. In v1.0.0 ist SSO-Setup eine manuelle Operator-Aktion via Keycloak-Admin-Console, begleitet durch ein kurzes Runbook (fällt in Block 16 Testing & Docs, ~2h).