Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/docs/INTEGRATION-CONTRACTS.md
T

16 KiB

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.

// 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/<modul>/... app.hivegrid.de/sales/D-2026-0042
Mandant-Settings app.hivegrid.de/settings/<modul>/... app.hivegrid.de/settings/integrationen/kwp
Plattform-Admin admin.hivegrid.de/<bereich>/... admin.hivegrid.de/mandanten/123
Customer-Portal (Standard) portal.hivegrid.de/<tenant>/... portal.hivegrid.de/geerkens/projekte/42
Customer-Portal (verifizierte Custom-Domain) <portal-host>/... portal.geerkens.de/projekte/42
Public-Forms app.hivegrid.de/f/<tenant>/<slug> app.hivegrid.de/f/geerkens/kontaktformular
Public-Booking app.hivegrid.de/b/<tenant>/<slug> app.hivegrid.de/b/geerkens/wartungstermin
Public-Help app.hivegrid.de/help/<lang>/<kategorie>/<slug> app.hivegrid.de/help/de/integrationen/kwp-setup
Public-API api.hivegrid.de/v1/<resource> api.hivegrid.de/v1/contacts
MCP-Server mcp.hivegrid.de mcp.hivegrid.de
Connector-OAuth-Callback app.hivegrid.de/integrations/<provider>/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/<tenant>/.... 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/<provider>/oauth/callback. Die verwaltenden Connector-Screens liegen davon getrennt unter /settings/integrations/<modul> 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/<slug>/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:

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:

import { auditEvent } from '@hivegrid/audit';
await auditEvent({ ... });

7. Notification-Wiring (M011)

Wenn ein Modul Mitarbeiter benachrichtigen soll: NIE direkt Email/Push senden. Immer:

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:

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/<modul>.json. ESLint-Rule blockt JSX-Text-Nodes ohne t(). Build-Lint blockt Hard-Strings im Build.

Naming: <modul>.<bereich>.<key>, 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:

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/<modul>.spec.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:<sha>-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/<resolved-tenant>/...). 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/<tenant>/... und staging-portal.hivegrid.de/<tenant>/... 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.