# HiveGrid Integration-Contracts **Zweck:** Verbindliche Docking-Points, an denen jedes neue Modul anschließt. Wenn ein AI-Coding-Agent eine Funktion baut, ohne sie hier anzudocken, ist die Funktion **nicht erreichbar** und das Milestone gilt als nicht erledigt. Diese Datei ist Quelle der Wahrheit für: Sidebar-Gruppen, Route-Patterns, Breadcrumbs, Permissions, Search-Index, Activity-Stream, Audit, i18n. > **Naming-/Pfad-Realität (siehe `AGENTS.md` §Naming-Konvention; Entscheidungs-ID AD-10):** > Das HiveGrid-UI-Paket heißt real **`packages/ui-app`** (nicht `packages/ui-hivegrid`). Die > Sidebar-Konfiguration liegt in **`apps/web/app/application/sidebar/`** (nicht `sidebar.config.ts`). > Der Such-Index / `indexedEntities` gehört nach **`packages/search/`** (nicht > `packages/db/src/search/indexed-entities.ts`). Geteilte Pakete bleiben `@hivegrid/*`; neue > HiveGrid-Querschnittsdienste (z.B. Activity/`logActivity`) werden als neues `@hivegrid/*`-Paket > angelegt. Lies die `ui-hivegrid`/`@hivegrid/*`/`sidebar.config.ts`-Erwähnungen unten entsprechend. ## 1. App-Shell-Sidebar (Mandanten-App `app.hivegrid.de`) Sidebar ist in 6 feste Gruppen geteilt. Jede neue Funktion gehört in **genau eine** Gruppe: ``` 🏢 ARBEITEN ├─ Dashboard → M147-M150 ├─ Globale Suche (Cmd-K) → M058 ├─ Lead-Inbox → M047-M051 ├─ Verkaufsfälle (Sales) → M059-M064 ├─ Aufträge (Projekte) → M065-M070 ├─ Aufgaben → M071-M074 ├─ Termine → M120 └─ Buchungs-Seiten → M126-M129 📇 STAMMDATEN ├─ Kontakte → M042-M046 ├─ Firmen → M042-M046 ├─ Dateien → M052-M055 ├─ Tags / Schlagwörter → M044 └─ Custom-Felder → M044 📨 KOMMUNIKATION ├─ Email-Inbox → M096-M099 ├─ Marketing-Kampagnen → M100-M104 ├─ Telefonie → M105-M109 ├─ Team-Chat → M110-M114 └─ Notification-Center (Glocke) → M115-M117 ⚙️ AUTOMATION & KI ├─ Automation-Studio → M075-M082 ├─ KI-Sprach-Assistent → M086 └─ Vorlagen / Templates → M082 👥 PERSONAL (HR-Hub) ├─ Schwarzes Brett → M137 ├─ Mitarbeiter-Verzeichnis → M133 ├─ Urlaub / Krankheit → M135, M136 ├─ Lohnabrechnungen → M134 ├─ Anträge → M139 └─ Passwort-Safe → M134 🛠️ EINSTELLUNGEN (Mandanten-Admin) ├─ Mandant (Stammdaten) → M018 ├─ Mitarbeiter & Rollen → M023, M024 ├─ Pipelines → M059, M065 ├─ Forms → M121-M125 ├─ Integrationen (Apps) → M008-M010 ├─ KWP-Verbindung → M089-M095 ├─ Audit-Log → M151 ├─ Subscription / Abo → M153-M156 ├─ Branding / Design → M030 └─ DSGVO / Datenschutz → M164-M166 ``` **Plattform-Admin-App `admin.hivegrid.de`** hat eine separate Sidebar-Struktur (siehe M034-M041). **Customer-Portal** hat eigene minimale Navigation (siehe M141-M146). **Mobile-App-Tab-Bar** ist UI-Modus-abhängig — siehe M169. ## 2. Sidebar-Wiring-Pflicht Jeder neue Modul-Bereich muss in `packages/ui-hivegrid/src/app-shell/sidebar.config.ts` registriert werden. Das ist die **einzige** Quelle der Wahrheit für Sidebar-Inhalte. ```ts // packages/ui-hivegrid/src/app-shell/sidebar.config.ts export type SidebarEntry = { id: string; // eindeutig, kebab-case group: 'arbeiten' | 'stammdaten' | 'kommunikation' | 'automation-ki' | 'personal' | 'einstellungen'; label: string; // i18n-Key, kein Hardcoded-String icon: string; // lucide-react Icon-Name route: string; // RR7-Route-Pattern permission: string | string[]; // Permission-Key aus M004; Eintrag nur sichtbar wenn berechtigt badge?: 'count' | 'dot' | null; // Live-Counter (z.B. ungelesene Mails) order: number; // Sortierung innerhalb der Gruppe feature_flag?: string; // optional: Sidebar-Eintrag nur sichtbar wenn Flag aktiv }; ``` **Regel:** Eine neue Funktion ohne Sidebar-Eintrag (oder ohne Inline-Verlinkung aus einem bestehenden Modul) wird **nicht akzeptiert**. ## 3. Route-Patterns Verbindliche URL-Schemata. Abweichungen erfordern Change-Request. | Bereich | Pattern | Beispiel | |---|---|---| | Mandanten-App | `app.hivegrid.de//...` | `app.hivegrid.de/sales/D-2026-0042` | | Mandant-Settings | `app.hivegrid.de/settings//...` | `app.hivegrid.de/settings/integrationen/kwp` | | Plattform-Admin | `admin.hivegrid.de//...` | `admin.hivegrid.de/mandanten/123` | | Customer-Portal (Standard) | `portal.hivegrid.de//...` | `portal.hivegrid.de/geerkens/projekte/42` | | Customer-Portal (verifizierte Custom-Domain) | `/...` | `portal.geerkens.de/projekte/42` | | Public-Forms | `app.hivegrid.de/f//` | `app.hivegrid.de/f/geerkens/kontaktformular` | | Public-Booking | `app.hivegrid.de/b//` | `app.hivegrid.de/b/geerkens/wartungstermin` | | Public-Help | `app.hivegrid.de/help///` | `app.hivegrid.de/help/de/integrationen/kwp-setup` | | Public-API | `api.hivegrid.de/v1/` | `api.hivegrid.de/v1/contacts` | | MCP-Server | `mcp.hivegrid.de` | `mcp.hivegrid.de` | | **Connector-OAuth-Callback** | `app.hivegrid.de/integrations//oauth/callback` | `app.hivegrid.de/integrations/google/oauth/callback` | Die Tenant-Slug ist **nicht** im Pfad bei der Mandanten-App — Tenant kommt aus Subdomain oder Session. **Customer-Portal (AD-30):** Auf dem Standardhost bleibt der Tenant-Slug Pflicht. Auf einer verifizierten Custom-Domain steht er nicht im Pfad; der Tenant kommt dort ausschließlich aus der global eindeutigen, aktiven Host-Zuordnung. Unbekannte, noch nicht verifizierte oder deaktivierte Portal-Hosts liefern 404. Staging verwendet `staging-portal.hivegrid.de//...`. Eine interne Route darf weiter auf einen normalisierten Portal-Namespace abbilden, aber die öffentliche kanonische URL folgt immer dieser Tabelle. **Public API und MCP:** Public API läuft ausschließlich auf `api.hivegrid.de` (staging: `staging-api.hivegrid.de`) und nutzt das Versionspraefix `/v1/...`. Es gibt keine Public-API-Routen unter `app.hivegrid.de/api`. Der MCP-Server läuft ausschließlich auf `mcp.hivegrid.de` (staging: `staging-mcp.hivegrid.de`) als separater Service/Container (`hivegrid-mcp`), ohne zusätzlichen `/mcp`-Pfad und nicht im Web- oder Public-API-Service. **Externe OAuth-Connectors (Google-Kalender, KWP, später AirCall/Microsoft/…):** Der Redirect-/Callback-Endpoint liegt **immer** auf `app.` (nicht `api.`) unter `/integrations//oauth/callback`. Die verwaltenden Connector-Screens liegen davon getrennt unter `/settings/integrations/` bzw. der generische Katalog unter `/settings/integrations`. Begründung: Der OAuth-Rückweg ist browser-basiert und braucht die an `app.` gebundene Nutzer-Session, um den Token dem richtigen Nutzer/Tenant zuzuordnen; `api.hivegrid.de` (token-basiert, ohne Session) ist dafür nicht geeignet. Tenant wird im OAuth-`state` mitgeführt (eine feste Callback-URL je Umgebung, kein Tenant im Pfad). Pro Umgebung registriert: `app.hivegrid.de`, `staging-app.hivegrid.de`, `localhost:3000`. Das frühere `/api/apps//oauth/callback` ist abgelöst (2026-06-21). ## 4. Breadcrumbs-Konvention Jede Detail-Route hat einen Breadcrumb in `packages/ui-hivegrid/src/breadcrumbs/`. Format: ``` [Sidebar-Gruppe] > [Modul] > [Listen-Name] > [Detail-Titel] ``` Beispiel: `Arbeiten > Verkaufsfälle > Anlagen-Verkauf > D-2026-0042 Heizung Schmidt` ## 5. Activity-Stream-Wiring (M045) Jede schreibende Aktion in jedem Modul muss einen Activity-Stream-Eintrag erzeugen, falls die betroffene Entität einen Stream hat (Kontakt, Firma, Lead, Verkaufsfall, Auftrag). **Pflicht-Service-Aufruf:** ```ts import { logActivity } from '@hivegrid/activity'; await logActivity({ tenantId, actorId, actorType: 'user' | 'auto' | 'sync' | 'system', entityType, entityId, type: 'contact.created' | 'deal.stage_changed' | ..., payload: { ... }, // Human-readable Klartext + ggf. Vorher/Nachher }); ``` Der `logActivity`-Aufruf ist NICHT der Audit-Eintrag — der entsteht zusätzlich automatisch via M001 Prisma-Middleware. ## 6. Audit-Wiring (M001) Audit ist **automatisch** via Prisma-Middleware. Agent muss nichts manuell aufrufen. Wenn etwas im Audit fehlt: Bug in M001-Middleware, nicht im Modul-Code. **Ausnahme:** Aktionen außerhalb von Prisma (z.B. Datei-Upload zu S3, externe Webhook-Aufrufe) müssen manuell auditiert werden: ```ts import { auditEvent } from '@hivegrid/audit'; await auditEvent({ ... }); ``` ## 7. Notification-Wiring (M011) Wenn ein Modul Mitarbeiter benachrichtigen soll: NIE direkt Email/Push senden. Immer: ```ts import { notify } from '@hivegrid/notifications'; await notify({ tenantId, recipientId, category: 'lead.received' | 'deal.won' | ..., template: 'lead-received', // i18n-Key in M003 payload: { ... }, priority: 'normal' | 'high' | 'urgent', }); ``` Der Notification-Service in M011 entscheidet Kanal (In-App/Email/Push), Bündelung (M117), Stille-Stunden (M116). ## 8. Search-Index-Wiring (M056) Neue Entität, die in der globalen Suche auftauchen soll, muss in `packages/db/src/search/indexed-entities.ts` registriert sein: ```ts export const indexedEntities = { contact: { table: 'Kontakt', searchable: ['displayName', 'email', 'phone'], permission: 'crm.read' }, deal: { table: 'Verkaufsfall', searchable: ['title', 'description'], permission: 'sales.read' }, // ... }; ``` Permission-aware Search (M057) liest diese Map. ## 9. i18n-Wiring (M003) Alle UI-Texte als i18n-Keys. Locale-Files unter `packages/i18n/src/locales/de/.json`. ESLint-Rule blockt JSX-Text-Nodes ohne `t()`. Build-Lint blockt Hard-Strings im Build. Naming: `..`, z.B. `sales.kanban.empty_state` oder `auth.login.password_too_short`. ## 10. Permission-Keys (M004) Permission-Definition als TypeScript-Konst in `packages/entitlements/src/permissions.ts`: ```ts export const Permissions = { CRM_READ: 'crm.read', CRM_READ_OWN: 'crm.read_own', CRM_WRITE: 'crm.write', SALES_READ: 'sales.read', SALES_VOLUME_READ: 'sales.volume.read', // separater Key für Volumen-Sichtbarkeit (siehe 3.11.2) // ... } as const; ``` Sidebar-Einträge, Routes, View-Models, Actions filtern alle gegen diese Keys. ## 11. View-Model-Pflicht (M013) JEDE Route in `app/routes/**` baut im Loader ein View-Model. UI-Components in `packages/ui-hivegrid/**` empfangen NUR das VM und Action-Callbacks. Siehe AGENTS.md. ## 12. Smoke-Test pro Route (Pflicht) Jede neue Route bekommt einen Smoke-Test in `e2e/smoke/.spec.ts`: ```ts test('contact-detail loads with view-model', async ({ page }) => { await loginAs(page, 'geerkens-admin'); await page.goto('/kontakte/test-kontakt-id'); await expect(page.getByRole('heading', { name: /kontakt/i })).toBeVisible(); }); ``` Smoke-Tests laufen im Hot-Swap-Drill (M017) gegen `packages/ui-mock` — wenn sie da brechen, ist die Hot-Swap-Architektur verletzt. > **Interim-UI (AD-28, `.planning/INTERIM-UI-PLAYBOOK.md`):** Reachability (Sidebar/Route/Breadcrumb/i18n/Permission-Gate) + Smoke + Hot-Swap gelten als erfüllt, sobald ein **Interim-Screen aus `~/ui`** steht (VM als Prop) — auch bevor Davids finaler `D-*`-Screen existiert. Die Visual-Regression (M016) läuft dann gegen eine **Interim-Baseline** mit **Re-Baseline-Pflicht** beim David-Nachzug; die finale Subframe-Optik bleibt ein getrackter Nachzug (Label `design-nachzug`), nicht Done-blockierend. ## 13. Mobile-Tab-Bar-Wiring (M169) Neue Funktionen, die in der Mobile-App relevant sind, müssen in `packages/ui-hivegrid/src/mobile/tab-bar.config.ts` für mindestens einen UI-Modus (Baustelle / Büro) registriert sein. ## 14. Customer-Portal-Wiring (M142, M143) Funktionen, die Endkunden sehen sollen, müssen explizit in `packages/ui-app/src/customer-portal/portal-shell.config.ts` registriert sein. Default-Sichtbarkeit: **NICHT sichtbar**. Tenant-Admin gibt Projekte, Stages und Dokumente explizit frei (M142.5). **Runtime-/Trust-Boundary (AD-30):** Öffentliche Portalrequests laufen über einen eigenen Coolify-Service und Container `hivegrid-portal`. Im ersten Schnitt verwenden `hivegrid-web` und `hivegrid-portal` dasselbe immutable `hivegrid/web:`-Image, aber getrennte Runtime-Modi, Secrets, Sessions, Ressourcenlimits und DB-Logins. `WEB_RUNTIME_SURFACE=core|portal` ist in Produktion Pflicht und bei fehlender, ungültiger oder zur Resource unpassender Konfiguration ein Boot-Fehler. Die Trennung erfolgt vor Static-Asset-Serving und React Router und ist fail-closed: | Resource | Erlaubt | Muss 404 liefern | |---|---|---| | `hivegrid-web` | App-/Admin-/Root-Hosts sowie Workspace-Admin-Konfiguration unter `app.hivegrid.de/settings/customer-portal/...` | Standard- und Custom-Portalhosts | | `hivegrid-portal` | `portal.hivegrid.de`, Staging-Portalhost, aktive verifizierte Custom-Hosts, Portalroutes, Assets und Health | App-, Admin-, API-, Root- und interne Routen | `hivegrid-portal` verwendet den dedizierten DB-Login `hivegrid_portal` mit minimalen Grants und PortalUser-/Projektzugriffs-RLS. Ein zweiter Container mit dem breiten `hivegrid_app`-Login erfüllt diesen Trust-Boundary-Vertrag nicht. Ein eigenes `apps/portal`, ein eigenes Portal-Image, eine eigene VM oder ein Deployment pro Tenant sind im ersten Schnitt nicht vorgesehen. Vor dem React-Router-Dispatch normalisiert die Portal-Runtime beide öffentlichen URL-Formen auf einen reservierten, nur intern dispatchbaren Portal-Namespace (Arbeitsvertrag `/__portal//...`). Beim Standardhost stammt der Tenant aus dem validierten ersten Pfadsegment, beim Custom-Host ausschließlich aus der aktiven Host-Zuordnung. Direkte öffentliche Requests auf den internen Namespace sowie Portalpfade auf `hivegrid-web` liefern 404; dadurch kann keine bestehende TenantUser-/Admin-Route versehentlich einen Portalrequest übernehmen. ### 14.1 Host- und Tenant-Auflösung (AD-30) - `portal.hivegrid.de//...` und `staging-portal.hivegrid.de//...` lösen den Tenant aus dem ersten Pfadsegment auf; Reserved-Words und Tenant-Existenz werden vor Auth geprüft. - Ein Custom-Host löst den Tenant ausschließlich über eine aktive, verifizierte Domain-Zuordnung auf. Ein Tenant-Slug im Pfad ist dort weder nötig noch als Override zulässig. - Bei Konflikt zwischen Pfad, Session und Host wird geschlossen abgebrochen. Ein Request darf niemals über einen vom Client gelieferten Hostnamen den Tenant wechseln. - Jede Portal-Session nutzt ein hostgebundenes `__Host-*`-Cookie. Auf dem geteilten Standardhost bleibt die Session zusätzlich unveränderlich an den Tenant gebunden; ein anderes Pfadsegment darf sie nicht umhängen. App-, Admin- und Portal-Cookies werden nicht geteilt; Sitzungen auf Standard- und Custom-Host sind ebenfalls hostgetrennt. ### 14.2 Custom-Domain-Lifecycle (VA-P26c) 1. Nur ein Workspace-Admin mit Portal- und Domain-Entitlement kann einen exakten Subdomain-Host anlegen. 2. Der Host durchläuft `pending_dns → verified → provisioning → active`; Fehler und Deaktivierung sind explizite Zustände. Global eindeutige Hostnamen und idempotente Übergänge sind Pflicht. 3. Ownership-Proof erfolgt per zufälligem TXT-Token; der Traffic-CNAME zeigt auf `domains.portal.hivegrid.de`. Erst danach provisioniert der getrennte Controller Traefik + TLS und routet den exakten Host ausschließlich auf `hivegrid-portal`. 4. Absolute Links in Einladungen, Magic-Links und Status-Mails stammen aus der gespeicherten aktiven Portal-Origin. Der Standardhost bleibt als sichere Fallback-Origin verfügbar. 5. DNS/TLS-Monitoring, Revalidierung, Deprovisionierung, ein 30-tägiger Reclaim-Cooldown und Operator-Kill-Switch gehören zum Lifecycle. Apex-Domains, Wildcards und mehrere parallele Domains sind nicht Teil des ersten Vertrags.