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

308 lines
16 KiB
Markdown

# 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/<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:**
```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/<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`:
```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`:
```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.