308 lines
16 KiB
Markdown
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.
|