# HiveGrid 2.0 — Architektur-Entscheidungen > Entscheidungen aus dem Boilerplate-Abgleich (Stand 2026-06-04). Diese ADR-Liste > ergänzt `AGENTS.md` und ist verbindlich für die Umsetzung. Grundlage: > `docs/boilerplate-mapping/_SUMMARY.md` + die Modul-Reports. > Legende: ✅ entschieden · 🅿️ Backlog (Entscheidung später). --- ## AD-1 — Mandanten-Trennung: echtes Postgres-RLS ✅ **Entscheidung:** Row-Level-Security in Postgres als bauliche Vorgabe (nicht nur App-Level-Scoping). - RLS-Policies (USING/WITH CHECK) als Forward-Migration mit Inverse-SQL-Kommentar - Request-Middleware setzt `SET LOCAL app.tenant_id` - App-DB-Rolle **ohne** `BYPASSRLS` - Worker-/Job-Tenant-Context-Wrapper - Cache-Präfix `tenant:{id}:`, S3-Pfad `tenants/{id}/` - Ausnahmen ohne RLS: `users`, `subscription_*`, `feature_flags`, Branchen-Templates **Warum:** Spec A.4 + M019-Erfolgskriterium (Pen-Test). Defense-in-depth — ein vergessener Query-Filter kann keine Fremddaten mehr leaken. Boilerplate hat **nur** App-Level-Scoping. **Betrifft:** M019 (+ jedes tenant-scoped Modul). ## AD-2 — Daten-Modell: typisierte Tabellen, Entity-Builder raus ✅ **Entscheidung:** HiveGrid-Fachentitäten (CRM, Deals, Aufträge, Tasks, …) als dedizierte, typisierte First-Class-Prisma-Tabellen. Das Entity-Builder-CRM der Boilerplate (`Entity`+`Row`+`RowValue`, `apps/web/app/modules/crm`) wird **entfernt**, nicht angepasst. Custom-Felder über purpose-built `CustomFieldDefinition` + `CustomFieldValue` (JSONB-Wert), nicht über die polymorphe Row-Engine. **Warum:** Spec M01 verlangt typisierte Tabellen mit Constraints, Indizes (`pg_trgm`, `tenant_id, archived_at, last_activity_at`), `ContactEmail`/`ContactPhone`, M:N-Relationen, zentrale `Activity`-Tabelle. Polymorphes `RowValue` ist damit unvereinbar (Performance bei 10k+, Indizes, Permissions, RLS). Zwei Architekturen parallel = Fremd-Architektur sickert ein. **Betrifft:** M01, M02, M03, M04, M26 (+ alle Fachmodule). > **🔄 REVIDIERT 2026-06-13 (Modell-Pivot, siehe AD-20):** Deal + Project verschmelzen zu EINER > typisierten `Projekt`-Tabelle (Superset der Felder) + `CustomFieldDefinition/Value` + einem > neuen **Pipeline-Feld-Config-Layer** (welche Felder pro Pipeline+Status sichtbar/pflichtig sind; > nicht sichtbare Felder bleiben im Hintergrund erhalten). Die Konfigurierbarkeit ist **Metadaten > über typisierten Spalten** — der generische Entity-Builder (Entity/Row/RowValue) bleibt entfernt. > AD-2-Prinzip („typisiert, kein Generikum") bleibt gewahrt. Anker: `.planning/ENTITY-MODEL.md`. ## AD-3 — Plattform-Admin-Identität: getrennt, Email+PW+TOTP, optional Authentik-SSO ✅ **Entscheidung:** Vollständig getrennte Admin-Welt — eigene `AdminUser`-Tabelle (eigenes `passwordHash`/`totpSecret`/`status`), eigene `AdminSession` (server-side, IP-gebunden), eigene `AdminRole`/`AdminPermission`-RBAC. Primärer Login: **Email + Passwort + TOTP** (Magic-Link verboten). **Zusätzlich** optionaler **Authentik-OIDC/SSO**-Login, gemappt auf dieselbe getrennte Admin-Identität. **Kein** Generalschlüssel zwischen Plattform und Mandanten. - `AdminAuditLog` braucht **kein** neues Schema: vorhandener `AuditEvent` (Hash-Kette, `tenantId=null` = plattformweit) deckt das ab. - Boilerplate-Admin-Auth (Login-Flow, MFA-Step-up-Gate, Rate-Limit, Session-Cookie-Policy) dient als **Vorlage**, nicht 1:1 (BP hat AdminUser nur als `User`-Extension). **Warum:** Spec 37.1 / M034 verbietet den geteilten Identity-/RBAC-Pfad explizit. **Betrifft:** M034–M041 (M37). ## AD-4 — Sessions: Postgres als Source-of-Truth + Redis-Cache ✅ **Entscheidung:** Server-side Session-Tabelle in Postgres (Geräte-Liste mit Browser/IP/ Standort/letzter Aktivität, Revoke, „von allen Geräten abmelden", 4h-Inaktivität, 30-Tage-Sliding-Window, Auto-Logout bei Passwort-/2FA-/Email-Änderung) als Quelle der Wahrheit; **Redis-Cache** für schnelle Pro-Request-Validierung. Cookie hält nur die Session-ID-Referenz. **Warum:** Spec B.5 braucht dauerhafte, abfragbare Geräte-Liste + verlässliches Revoke-all; reine Cookies (BP-Default) können nicht serverseitig widerrufen werden; reines Redis verliert Geräte-Historie bei Flush. **Betrifft:** M021 (Mandanten-User), M034 (`AdminSession`), M141 (Customer-Portal). ## AD-5 — Such-Engine: Meilisearch (self-hosted) + pgvector-Hybrid ✅ **Entscheidung:** Keyword-Suche über das **selbst gehostete Meilisearch** (BP-`@toolctrl/search` weitgehend 1:1), semantische Suche über **pgvector + EU-Embeddings** (Scaleway 1024-dim, BP 1:1), kombiniert via **RRF-Fusion**. Embedding-Pipeline + Outbox + Indexer-Worker aus der BP übernehmen. **Warum:** Meilisearch wird ohnehin selbst gehostet (EU-Datenhoheit + Ops bereits gelöst) → bessere Tippfehler-Toleranz/Relevanz + maximaler Reuse. **Weicht bewusst von M056 „Postgres-FTS, kein Extra-Service" ab → Change-Request gegen M056 nötig.** **Betrifft:** M24 / M056–M058. ## AD-6 — EU-Email-Provider ✅ **Entscheidung (2026-06-16, EXT-02):** **Mailjet** (EU/FR) als EU-Email-Provider für transaktionale **und** Marketing-Mails. MailerSend (BP-Default) wird **verworfen** — MailerSend, Inc. ist US-Entität (Transfers via EU-US Data Privacy Framework), unter strikter LP-13-Lesart („keine US-Gesellschaft im Boot", wie bei OpenAI/Anthropic) problematisch. **Warum:** EU-Datenhoheit ohne US-Gesellschaft im Boot; feature-reich (transaktional + Marketing + A/B in P14). Boilerplate ist bereits auf `MAILJET_*` verdrahtet. **Verifikationsstand (EXT-02):** Sende-Domain `hivegrid.de` bei Mailjet verifiziert — SPF (`include:spf.mailjet.com`) + DKIM (Selector `mailjet`) Auto-Probe **grün**, Sender `*@hivegrid.de` aktiv; DMARC `p=none` (Monitoring während Warm-up). **Cleanup-Follow-ups:** (a) `include:_spf.mailersend.net` aus dem SPF-Record entfernen, sobald die Migration vollständig ist; (b) DMARC nach Warm-up von `p=none` auf `p=quarantine` ziehen. **Betrifft:** M09, M10, M22, M102. ## AD-7 — Captcha / Bot-Schutz 🅿️ (Backlog) **Status:** Entscheidung später. **Tendenz: EU** — Friendly Captcha (DE, GDPR-first) oder self-hosted mCaptcha (passt zum Meilisearch-Self-Host-Ansatz). - Turnstile (BP) und hCaptcha (Spec) sind beide US-Firmen → bei strikter Linie Change-Request. **Betrifft:** Login (M020), Public-Forms (M05/M125), Booking (M06). ## AD-8 — Verschlüsselung: Per-Tenant-DEK + Envelope ✅ **Entscheidung:** Pro Mandant ein Data-Encryption-Key (DEK), verschlüsselt mit einem Master-Key (Scaleway Key Manager o.ä.) — Envelope-Encryption. Crypto-Shredding bei Mandanten-Löschung (M165: Tenant-Key wegwerfen → alle verschlüsselten Daten unlesbar). **Warum:** Spec verlangt Per-Tenant für Passwort-Safe (M134) + OAuth-Tokens; globaler Key (BP-Default) exponiert bei Kompromiss alle Mandanten und erlaubt kein Crypto-Shredding. **Betrifft:** M134 (HR-Passwort-Safe), OAuth-Tokens (M09/M25/M12), M165 (DSGVO-Löschung). ## AD-9 — Realtime: WebSocket für Chat + SSE für Read-Updates ✅ **Entscheidung:** Generischer WebSocket-Room-Layer für bidirektionale Echtzeit (Team-Chat M13: Rooms, Presence, Typing, Read-Receipts). SSE (BP-Default) bleibt für einseitige Live-Updates (Kanban M03, Notifications M22). `hocuspocus` bleibt CRDT-only (Kollab-Dokumente). **Warum:** SSE ist einseitig → für Chat ungeeignet; hocuspocus ist CRDT, kein Chat-Broadcast. **Betrifft:** M13 (M110–M114); SSE-Pfad: M03, M22. ## AD-10 — Naming-Konvention: `@toolctrl/*` behalten, Docs anpassen ✅ **Entscheidung:** Die geteilten Boilerplate-Pakete behalten ihre Namen (`@toolctrl/*`, `packages/ui-app`) — **kein** flächiger Rename zu `@hivegrid/*`/`ui-hivegrid`. - `@toolctrl/*` + `ui-app`/`ui-subframe`/`ui-mock` = **geteilte Boilerplate-Pakete** (kommen von upstream, bleiben mergebar). - `@hivegrid/*` = **nur für genuin neue HiveGrid-Pakete**, die upstream nie existieren (z.B. das neue Activity-Paket, HiveGrid-Domänen-Pakete) → keine Merge-Konflikte. - HiveGrid-Doku (`AGENTS.md`, `docs/INTEGRATION-CONTRACTS.md`, PLANs, Reports) wird auf diese Realität gezogen; wo Docs `ui-hivegrid`/`@hivegrid/foundation` sagen, ist `ui-app`/ `@toolctrl/foundation` gemeint. **Warum:** Es sind 901 Dateien / 1649 `@toolctrl`-Vorkommen. Ein Rename würde **jeden** künftigen `git merge upstream/main` (AD-Setup: Git-Downstream) massiv kollidieren lassen und die Update-Fähigkeit zerstören. ToolCTRL ist die Firma, HiveGrid das Produkt — geteilte Pakete unter `@toolctrl` sind korrekt. **Betrifft:** Querschnitt (alle Module), Phase 0. --- # Delivery- & Scope-Entscheidungen ## AD-11 — Bau-Strategie: Hybrid (Fundament-Kern, dann Geerkens-Kernpfad) ✅ **Entscheidung:** Nach Phase 0 zuerst der Fundament-Kern (M27 Auth + M33a Apps-Engine + M01 CRM-Datenmodell), dann sofort die Geerkens-Wertschöpfungskette **Lead → Projekt (konfigurierbare Pipelines) → KWP**, danach die Breite (HR, Portal, Marketing, Mobile, Billing-Feinheiten). Balance aus sauberem Fundament und früh sichtbarem Nutzen. > **🔄 REVIDIERT 2026-06-13 (Modell-Pivot, AD-20):** Kette ist nicht mehr „Lead → Sales(Deal) → Auftrag(Project)", > sondern **Lead → Projekt → KWP**. Sales und Auftrag sind konfigurierbare Pipeline-Views auf denselben `Projekt`-Kern. ## AD-12 — SHK-Domänen-Defaults: aus Spec als Seed, Noah validiert ✅ **Entscheidung:** Die SHK-Inhalte (10 Standard-Rollen + Permission-Bundles [27.7/N1], SHK-Sales-Pipeline-Stufen [M59], Standard-Custom-Felder + Tags [M44], Provisionierungs-Daten [M158]) werden aus Spec/Pflichtenheft als **deklaratives, leicht änderbares Seed** umgesetzt; Noah/Geerkens validieren iterativ im Klick-Pfad. Blockiert den Bau nicht. > **🔄 AMENDMENT 2026-06-19 (Owner-Entscheidung Raffael, Partner-validiert):** Die spekulativen „10 Standard-Rollen" > aus 27.7/N1 sind auf die **6 vom Partner gelieferten Rollen** festgelegt: **Geschäftsführer, Projektleiter, > Verwaltung, Vertriebler, Kundensupporter, Monteur** (Seed `apps/web/app/utils/services/shkRoles.server.ts`, > idempotent, `is_demo_data`). Das deklarative-Seed-Prinzip bleibt unverändert; nur die Anzahl/Liste ist fixiert. > R-P2b-DoD + Planungs-Docs (.planning/, Milestone-Layer) entsprechend von 10 auf 6 nachgezogen. ## AD-13 — Go-Live-Scope: breiter Launch ✅ **Entscheidung:** Der erste Geerkens-Go-Live umfasst nicht nur die Vertriebskette, sondern auch **HR-Hub, Customer-Portal, Marketing und Mobile**. Scope ≈ Großteil der 35 Module. (Sequenzierung trotzdem nach AD-11.) ## AD-14 — Migration: sauberer Start + KWP-Sync füllt nach ✅ **Entscheidung:** Geerkens startet weitgehend leer; KWP-Sync (M089ff) bringt Auftrags-/Stammdaten automatisch. HubSpot/Quiply-Altdaten bleiben **extern als Read-only-Archiv**; nur punktuelle CSV-Importe via `packages/imports`. Keine vollen Migrations-Pipelines pro Quelle zum Launch. ## AD-15 — Pricing/Billing: Engine bauen, Preise geparkt ✅ **Entscheidung:** Stripe-Integration + Seat-/Plan-Engine + Verbrauchs-Metering (M153–M156) werden **technisch gebaut**, die konkreten Preis-Stufen (Cloud/Cloud+/Enterprise, Seat-Preise, Trigger-Volumen, Setup-Fee) bleiben **geparkt**. Geerkens läuft auf manueller Sonderkondition/Trial. Self-Service-Checkout-Preise = eigene spätere Pricing-Runde. ## AD-16 — USP-Säulen (Automation + KI): volle Tiefe zum Launch ✅ (⚠ KI-Hälfte abgelöst von AD-26, 2026-06-22) **Entscheidung:** Kompletter visueller Automation-Editor (M075–M082, alle Trigger/Aktionen, 24h-Undo, Guards) **und** voller KI-Layer (M083–M088, EU-Provider, RAG, Sprach-zu-Automation, Pay-as-you-go) sind day-1 dabei — nicht nur Fundament. > **⚠ KI-Hälfte abgelöst durch [AD-26] (2026-06-22):** Kein selbst-gehostetes LLM/RAG/HiveMind mehr — KI kommt via **MCP-Server** (externe Clients), M083–M088 gestrichen. Die **Automation-Säule bleibt** unverändert day-1. ## AD-17 — Mobile: PWA-only (v1 + v2), kein nativer Client ✅ (revidiert 16.06.) **Entscheidung (revidiert 2026-06-16):** Mobile läuft **ausschließlich als installierbare PWA / responsive Web** — in v1 **und** v2. Monteur kann auf der Baustelle offline Aufträge bearbeiten, Abnahmen erfassen, Plantafel sehen; alles wird über Service-Worker + IndexedDB-Offline-Queue + Background-Sync gequeued und bei Sync mit „KWP gewinnt" aufgelöst (KWP-Sync = v2). **Kein Expo, kein Capacitor, keine native iOS/Android-App, keine App-Store-Einreichung.** Push = **Web-Push (VAPID)** — aktiviert in v2 den in P1 ruhenden `notify()`-Push-Kanal. Native-App + App-Store → frühestens **v3** (Backlog, nicht beauftragt). **Historie:** 10.05. voller Offline-Betrieb (Capacitor, M167–M171) · 11.06. Capacitor→Expo + native Hülle nach v2 · **16.06. komplett auf PWA-only reduziert** (kein nativer Client in v1/v2; M167–M171 entfallen, „Mobile-App"-Arbeit = PWA-Ausbau: Installierbarkeit, Service-Worker, Offline-Härtung, Web-Push). ## AD-18 — Customer-Portal: voll inkl. Skalierungs-Sicht ✅ **Entscheidung:** Komplettes Portal day-1: Projekt-Sicht + Upload + Reklamation + Subunternehmer-Zugang + Skalierungs-Sicht (Service-Inbox + Routing + Pro-Kunden-Lebensdauer), M141–M146 inkl. [N19]. > **Scope-Muster (historisch):** AD-13/16 wählen den maximalen Umfang → der erste Go-Live ist faktisch > das **vollständige Phase-1-Produkt** in großer Tiefe. Sequenzierung nach AD-11. **AD-17 (Mobile) und > AD-18 (Portal) wurden später revidiert** (AD-18 → v2; AD-17 → PWA-only v1/v2) und folgen diesem Maximal-Muster > nicht mehr. Dies bleibt eine bewusst große Scope-Wahl (siehe Scope-Check in der Session + SCOPE-V1-CUTS.md). ## AD-19 — Go-Live-Modus: Big-Bang ✅ **Entscheidung:** Geerkens geht erst live, wenn das vollständige Phase-1-Produkt steht — **ein großer Launch**, kein phasiertes Produktiv-Go-Live. Interne Bau-Reihenfolge bleibt AD-11 (Fundament → Kernpfad → Breite), aber es gibt kein inkrementelles Produktiv-Schalten einzelner Module für Geerkens. **Bewusst akzeptiert:** längste Zeit bis zum ersten echten Nutzer-Feedback, höchstes „am-Alltag-vorbei"-Risiko. **Gegenmaßnahme empfohlen:** Noah/Geerkens-Validierung pro Modul über den PROGRESS-TRACKER-Klick-Pfad (READY-TO-VERIFY → Sign-Off) als Feedback-Ersatz vor dem Big-Bang. ## AD-20 — Projekt-zentrisches CRM/Pipeline-Modell ✅ (NEU 2026-06-13) **Entscheidung:** Das CRM/Pipeline-Modell wird **Projekt-zentrisch** (auf Basis von Davids UX-Briefing, voll adoptiert). Statt getrennter Deal- und Project-Entitäten gibt es **EINEN `Projekt`-Kern**, auf den **frei konfigurierbare Pipelines als Views** schauen. „Deal" und „Auftrag" sind nur Pipeline-Namen/-Typen, keine eigenen Objektarten. - **Pipeline = View:** Admin legt Pipelines an/benennt sie, steuert Zugriff (wer sieht), Feld-Sichtbarkeit, Pflichtfelder pro Pipeline/Status und die Statuswerte. Ein Projekt ist genau **einer** Pipeline zugeordnet; `Status` ist pipeline-intern (kein globaler Projektstatus). Nicht sichtbare Felder bleiben im Hintergrund erhalten. - **Lead-Flow:** Lead/LeadInput ist Vorstufe mit Datenpunkt `Weiterverarbeitungsfreigabe` (Ja/Nein/Unklar); bei Freigabe → Vererbung ins Projekt. **Lead → Projekt** (das frühere Lead→Contact-Promotions-Modal entfällt; Kontakt wird separat verknüpft). - **Kontaktmodell:** `Kontakt` ist Oberbegriff mit `Kontakttyp` (Privatperson/Organisation), `Kontaktstatus` (Kontakt/Kunde/Bestandskunde) und `Kontaktart` (Haupt-/Sekundärkontakt als Rolle/Relation). - **Scope-Calls (13.06.):** Abnahme bleibt **v1** als Checklisten/Protokoll-Sub-Area am Projekt (Mängel = Checkliste); Material/Zeiterfassung/Leistungspositionen/Baustelle/Support → v2-Backlog. **Rechnung** = einfaches Sub-Entity am Projekt in v1. KWP-Felder bleiben v2-Sync, v1 nur Feld-Halter. **Warum:** Davids konsolidiertes UX-Modell wird zur Bau-Grundlage gemacht; Raffael richtet die Planung daran aus (statt David einzunormen). Höhere Konfigurierbarkeit (SHK-Betriebe brauchen frei benennbare Pipelines) bei erhaltenem typisiertem Kern. **Betrifft:** AD-2 (revidiert), AD-11 (Kette revidiert), M01–M04, ENTITY-MODEL, ROADMAP P6–P9, REQUIREMENTS, SCREEN-MAP. **Anker:** `.planning/ENTITY-MODEL.md`. ## AD-21 — Prisma: Schema-Folder + Migrations-Disziplin für parallele Branches ✅ (NEU 2026-06-16) **Kontext:** 2 Devs (Raffael + Akeshan) bauen Modul-Phasen parallel in eigenen Branches. Risiko: Kollisionen im Datenmodell und in der Migrations-Historie. **Befund (16.06.):** Prisma **7.8.0**; das Schema ist **bereits als Ordner** organisiert (`prisma/schema/` mit 29 Domänen-Dateien: `core`, `accounts`, `permissions`, `appsEngine`, `email`, `workflows`, `notifications`, `marketingLeads`, `ai` …). Die **Datei-Ebene ist damit gelöst** — Module in verschiedenen Domänen-Dateien kollidieren nicht. Es existieren bereits 153 Migrations (lineare Timestamp-Historie). Connection-URLs in `prisma.config.ts` (Prisma 7): `DIRECT_URL` für Migrate, `DATABASE_URL` via PgBouncer zur Laufzeit. **Verbleibende Risiken:** (1) `core.prisma`/`accounts.prisma` sind der geteilte Kern (Tenant/User/Audit/File/ApiKey/Credential) — gleichzeitige Relations-Edits kollidieren *in dieser Datei*. (2) Zwei Branches mit je `migrate dev` erzeugen divergierende Migrations-Ordner → „applied out of order" beim Merge. **Entscheidung — drei verbindliche Konventionen:** 1. **Ein Modul = eine eigene `prisma/schema/.prisma`.** Neue Modelle **nie** in `core.prisma`/`accounts.prisma`. Diese nur für Relationen zu `Tenant`/`User` anfassen, Rück-Relationsfelder dort minimal halten. 2. **Migrationen erst beim Merge.** Auf dem Feature-Branch mit `prisma db push` iterieren (kein Migrations-File). Die **eine** Migration pro Phase erzeugt der Dev, der zuerst auf `main` merged — Forward-only mit Inverse-SQL als Kommentar (AGENTS.md DoD 28). Tägliche Integration hält Diffs klein. 3. **CI-Gate vor Merge:** `prisma validate` + `prisma migrate status` grün (pre-push-Hook schärfen). **Warum:** Schema-Splittung erledigt die Datei-Konflikte bereits; was bleibt (core-Hotspot + lineare Migrations-Historie) löst Konvention/Disziplin, nicht neue Technik. **Betrifft:** alle parallel gebauten Modul-Phasen (P2–P21, v2), `AGENTS.md` §Prisma-Schema, lefthook pre-push. **Anker:** `prisma/schema/`, `AGENTS.md`. ## AD-22 — GeoIP lokal + IP-Anonymisierung (EXT-12) ✅ **Entscheidung:** Geo-Auflösung von Login-/Session-IPs über eine **lokale GeoIP-Datenbank** (DB-IP Lite Country, CC-BY-4.0, MMDB-Format) statt eines externen Dienstes. Die gespeicherte IP wird **pseudonymisiert** (HMAC-SHA256 mit geheimem Pepper); persistiert wird nur das **Land** (`countryCode`/`countryName`). **Kein** EU-SaaS-Geo-Dienst, **kein** ipstack (US/apilayer, LP-13). **Warum:** - Ein externer Geo-Dienst — auch ein EU-gehosteter — bekäme die Endnutzer-IP (personenbezogen) übermittelt → DPA + Subprozessor (M166) + Latenz + Abhängigkeit. Der **Transfer selbst** ist das Risiko. - Lokale MMDB: Lookup passiert auf unserem Server, **die IP verlässt unsere Infra nie**. Kein DPA, kein Subprozessor. Passt zur Self-Host-Linie (Meilisearch/mCaptcha). **Wofür Geo-IP (Zweck):** „Login aus Land X" + Anomalie „Login aus neuem Land" in der Session-/ Security-Sicht (P2); optionale Analytics-Anreicherung nach Land (Default aus); Feature-Flag-Targeting nach Land. Der HIBP-Breach-Check (EXT-12, `password-policy.server.ts`) ist davon unabhängig und bereits aktiv. **Speicher-/Minimierungs-Regeln:** - IP nie im Klartext: `IpAddress.ip`/`IpAddressLog.ip` speichern `HMAC-SHA256(IP, IP_HASH_PEPPER)`. Der Pepper liegt in den Coolify-Secrets, **nicht** in der DB → ein DB-Leak allein ist wertlos; der Gleichheits-Vergleich („gleicher Client?") bleibt möglich (Pseudonymisierung statt Anonymisierung). - Geo nur Land: `regionCode`/`city`/`zipCode`/`latitude`/`longitude` bleiben leer/null. - Der Blacklist-Abgleich nutzt die Roh-IP nur zur Laufzeit (Security-Liste), persistiert sie nicht im Log. **Provisionierung:** Die MMDB ist ein **Deploy-Artefakt** (nicht im Git), Pfad via `GEOIP_DB_PATH` (Default `./data/geoip/dbip-country-lite.mmdb`); `scripts/update-geoip-db.sh` lädt die Monatsversion. Fehlt die Datei, ist die Land-Auflösung ein No-op (Stub) ohne Fehler. Reader: npm `maxmind` (MIT). **Env:** `IP_HASH_PEPPER` (Secret), `GEOIP_DB_PATH` (optional). **Folge-Arbeit (offen):** monatlicher Worker-Cron (BullMQ) statt Shell-Skript; `IpAddress`/`IpAddressLog` perspektivisch auf Land + Hash-IP eindampfen (Spalten entfernen, sobald Session-Filter/Feature-Flag- Konsumenten umgestellt sind); UI, die bisher die Roh-IP zeigte, auf Land umstellen. **Betrifft:** EXT-12, P2 (Sessions/Security), P21 (DSGVO/Retention), `modules/ipAddress`, `services/auth/password-policy`. **Anker:** `apps/web/app/modules/ipAddress/`, `docs/ARCHITECTURE-DECISIONS.md`. ## AD-23 — Media-Auslieferung: privater Bucket + authentifizierter App-Proxy (kein public-read) ✅ **Entscheidung (2026-06-17):** Hochgeladene Medien werden **nicht** über public-read S3 ausgeliefert. Der Bucket bleibt **privat**; die App liefert Medien über eine **authentifizierte Proxy-Route** aus (Session + Tenant-Scope + Permission), die das Objekt aus dem privaten Bucket streamt bzw. auf eine kurzlebige signierte Download-URL weiterleitet. `STORAGE_PUBLIC_BASE_URL` bleibt leer; der `publicUrl`- Pfad (`RowHelper`/`MediaHelper`/`ApiHelper`, `publicUrl ?? file`) zeigt auf die Proxy-Route. **Warum:** CRM-Medien enthalten Kundendaten/Dokumente — world-readable Buckets sind DSGVO-untragbar (jeder mit der URL käme ohne Auth/Tenant-Check ran). Weg A (public-read) ist damit verworfen. **Für P4 (Files, `AK-P4a/b`) umzusetzen:** - Proxy-Route (z.B. `/media/…` bzw. `/storage/…`) mit Auth + `withTenantScope()` + `withPermission()`, streamt aus dem privaten Bucket oder redirected auf signierte Download-URL (kurze TTL). - `publicUrl` in den Media-Helpern auf die Proxy-Route verdrahten (statt null/rohem Key). - **Genuin öffentliche Assets** (E-Mail-Bilder, öffentliche Formular-/Booking-/Portal-Seiten, Branding-Logos) können keinen Auth-Proxy nutzen → signierte URLs mit TTL **oder** ein separater `public/`-Prefix mit eng begrenztem public-read. Pro Asset-Typ entscheiden. - DSGVO-Export-Artefakte nutzen bereits signierte URLs (unverändert). **Betrifft:** P4 (Files), EXT-06 (nur Bucket/Infra), `packages/storage`, `apps/web` Media-Helper. **Anker:** AD-8 (Verschlüsselung), `packages/storage`, `apps/web/app/utils/helpers/MediaHelper.ts`. --- ## Phase-0-Pflichten (kein „Entscheid", sondern Voraussetzung — siehe `_SUMMARY.md`) Diese Punkte sind vor der ersten Fachmodul-Umsetzung abzuarbeiten: 1. **Boilerplate als Code-Basis einziehen** (Repo ist heute Docs-only). 2. **Naming-Drift beheben:** `@toolctrl/*` → `@hivegrid/*`, `ui-app`-Stub → `ui-hivegrid`, Sidebar-Konvention, falsche AGENTS.md-Pfade (`indexed-entities.ts`, `sidebar.config.ts`). 3. **EU-Sterilisation:** GitHub/Google-OAuth-Reste + `User.githubId/googleId` entfernen. 4. **M00 aktivieren** + 4 echte Neubauten: `logActivity()`/Activity-Stream, Search-`indexed-entities`-Registry, **Subframe-Adapter-Code** (Quer-Voraussetzung für jede UI-Route), Mass-Action-Schutz. 5. **Fehlende Dependencies** ergänzen (PLAN.md teils falsch): `signature_pad`, `browser-image-compression`, `react-grid-layout`, `@dnd-kit` (statt react-beautiful-dnd), `libphonenumber`, `ics`/`ical-generator`, `btree_gist`/`EXCLUDE`-Constraint u.a. ## AD-24 — Prod unter eigenem Hetzner-Account (dediziert, self-contained) ✅ **Entscheidung (2026-06-19, Owner Raffael):** Produktion läuft in einem **eigenen, dedizierten Hetzner-Cloud-Account**, vollständig getrennt von Staging/dev. Topologie — **5 VMs** (fsn1, x86 cx-Linie): - **control** — eigenes **Coolify** + Orchestrierung/Bastion (cx33: 4 vCPU/8 GB) - **web**, **api** (je cx33) - **worker + services** — 5 Worker-Queue-Gruppen + **self-hosted Meilisearch + ClamAV + mCaptcha** (cx43: 8 vCPU/16 GB) - **db** — Postgres + PgBouncer + Redis + Daten-Volume (cx33 + Volume) mCaptcha nutzt die Postgres/Redis der db-VM (eigene DB), keine separate VM. Self-contained: eigene Zentral-Dienste (kein Zugriff aufs Staging-Privatnetz), eigener **DEK-Master-Key** (AD-8) + eigener **S3-Bucket** (`hivegrid-prod`, fsn1), eigenes Coolify. **Ingress (Variante „Cloudflare-Proxy"):** Cloudflare **proxied** vor `app./api./captcha.` (Edge-TLS, DDoS/WAF, Origin-IP versteckt) → **Coolify/Traefik pro Host** (Let's-Encrypt am Origin). `admin.` restriktiv (dns-only + Firewall bzw. Cloudflare Access). **Kein Hetzner-Load-Balancer** bis zur horizontalen Skalierung — die App ist über serverseitige Sessions in Postgres+Redis (AD-4) dafür bereit; LB + 2+ web/api-Instanzen kommen nach Anhebung des VPS-Limits. **Netzwerk:** privates Hetzner-Network + Firewall — `db`/`services` nur privat erreichbar, öffentlich nur 80/443 (web/api/control), SSH eingeschränkt. **Warum:** Blast-Radius/Isolation (getrennte Tokens/Billing, kein Cross-Deploy-Unfall Staging↔Prod), saubere DSGVO-/Audit-Grenze, Crypto-Shredding-Grenze pro Account. Revidiert/erweitert die Infra-ADR-009 (Hetzner+Coolify) um die Account-Trennung. **Grenzen:** Hetzner-Default-Limit 5 VPS/Projekt → echtes HA (web1/web2 + api1/api2 hinter LB) erst nach Limit-Anhebung (Support). Provisionierungs-Schritte + Go-Gates: `docs/ops/prod-rollout-hivegrid.md`. **Betrifft:** Prod-Deploy, Ops, alle Module. ## AD-25 — E-Signatur: gehärtete SES-Eigenbau, kein externer Provider (v1); QES-Add-on via DE-QTSP (v2) ✅ (NEU 2026-06-21) **Entscheidung (2026-06-21, Owner Raffael):** HiveGrid baut die elektronische Signatur (Angebote/Aufträge/Abnahmen) für v1 **selbst als gehärtete SES** und macht das zum **Default für alle Tenants** — **kein externer Provider als Zwang**. **PandaDoc bleibt als optionale BYOK-Anbindung** erhalten: Tenants mit eigenem PandaDoc-Account tragen ihren **eigenen API-Key** ein und signieren über *ihren* Account (Opt-in). Damit ist die Annahme „PandaDoc ist der *zentrale/erzwungene* Provider" abgelöst (Abweichung vom Pflichtenheft V.4.4), PandaDoc selbst aber **nicht** — es wird vom Default zum **Opt-in-Adapter**. Betrifft AK-P16d, EXT-09, EXT-17, VR-P16. **BYOK-Vorteil (CLOUD Act):** Bei BYOK nutzt der Tenant bewusst seinen *eigenen* PandaDoc — die CLOUD-Act-/Drittland- Verantwortung liegt damit beim Tenant (dessen Account, dessen Entscheidung), nicht bei HiveGrid. Das „100 % EU"- Default (SES-Eigenbau) bleibt unangetastet; PandaDoc ist eine transparente Opt-in-Wahl (Subprozessor-Liste/AVV). **Begründung (dreifach belegt):** - **Rechtlich:** SHK-Dokumente sind formfrei oder Textform (§§ 631, 650i Abs. 2, 640 BGB) → **SES genügt** (auch beim Verbraucherbauvertrag, der nur Textform verlangt — keine QES). Einziger zwingender QES-Fall im ganzen SHK: **Kündigung eines Bauvertrags (§ 650h BGB)** — kein Tagesgeschäft, selten. - **Praxis:** Marktstandard im DE-Handwerk ist die **SES-Touch-Unterschrift vor Ort** (+ Fern-Link). Alle etablierten Tools (HERO, plancraft, Craftnote, ToolTime …) bieten SES; AES/QES spielt im Tagesgeschäft keine Rolle, **kein Marktdruck**. Wettbewerbsfaktor ist der Prozess (papierlos → schneller zur Rechnung), nicht die Stufe. - **Souveränität:** PandaDoc/DocuSign = US-Firmen → **CLOUD Act §2713**, bricht das „100 % EU"-Versprechen (A.12.4). EU-Provider geprüft (Yousign FR am besten; Skribble CH hat US-Subprozessoren/kein echtes Embedding/per-Seat). OSS geprüft (Documenso = nur SES + AGPL). **Eigenbau ist souveränster + günstigster + rechtlich ausreichend.** **Umsetzung v1 (P16 / AK-P16d) — „gehärtete SES" (praktisch auf AES-Beweisniveau, ohne PKI-pro-Person/QTSP):** 1. **Touch-Signatur vor Ort** (Canvas → PDF) + **Fern-Link-Signatur** (Angebot remote annehmen) 2. **OTP-Identifizierung** (E-Mail; SMS als stärkere Zwei-Kanal-Variante) 3. **PDF-Versiegelung** (PAdES mit HiveGrid-Organisations-Zertifikat → jede Änderung bricht die Signatur) 4. **Zeitstempel** (TSA, RFC 3161; nicht-qualifiziert genügt für SES/AES) 5. **Audit-Trail** über M001 (Link/Öffnen/OTP/Signatur: wer, wann, IP, Gerät, Geo, PDF-Hash; append-only) 6. **Completion-Certificate** als PDF-Anhang (sichtbare Beweis-Doku) 7. **Einmal-Link** (personengebunden) + expliziter **„Intent to sign"** > Beweiskraft: gehärtete SES wie AES unter freier Beweiswürdigung (§ 286 ZPO) — der Audit-Trail trägt, nicht das Stufen-Etikett. Anscheinsbeweis (§ 371a) hat nur QES. **Architektur:** Signatur über `defineAdapter` (Apps-Engine, provider-agnostisch). Adapter: - **`ses-selfhosted`** — Default (v1), die gehärtete Eigenbau-SES. - **`pandadoc-byok`** — optional (v1), Tenant bringt eigenen PandaDoc-API-Key (DEK-verschlüsselt im Credential-Store); Mandanten-Trennung via eigener Account + Webhook-Routing (pro-Tenant-URL + `metadata.hivegrid_tenant_id`). Anbindungs-Spec: [[EXT-17]]. - Spätere Optionen ohne Fachcode-Umbau: **AES** (Yousign managed oder EU-DSS-Library self-hosted, Java-Sidecar), **QES** (DE-QTSP **D-Trust sign-me**, Hash-only — Dokument bleibt im Perimeter) für § 650h-Bauvertragskündigung. **Betrifft:** P16 (AK-P16d), EXT-09 (SES-Eigenbau-Default), EXT-17 (PandaDoc-BYOK-Adapter), VR-P16. PandaDoc bleibt als BYOK-Opt-in. Vor Produktivschaltung der Signatur: anwaltliche Bestätigung der Form-/Beweis-Einordnung (SES genügt, § 650h→QES). ## AD-26 — KI-Pivot: MCP-only, kein selbst-gehostetes LLM/RAG (löst die KI-Hälfte von AD-16 ab) ✅ (NEU 2026-06-22) **Entscheidung (2026-06-22, Owner Raffael):** HiveGrid hostet **kein** eigenes LLM/RAG/HiveMind mehr. Statt eine KI-App zu sein, wird HiveGrid **KI-anschließbar** über einen **MCP-Server** (Model Context Protocol): externe KI-Clients (Claude Desktop/Cursor/ChatGPT) docken an, **Inferenz liegt beim Client**. Damit entfällt der EU-LLM-Provider-/DSGVO- Zwang für HiveGrid selbst (kein Mistral/Scaleway/AlephAlpha-Hosting mehr). Löst die **KI-Hälfte von AD-16** ab — die **Automation-Säule bleibt**. **Begründung:** Kleines Team; der teure/harte Teil (LLM-Hosting, RAG-Infra, Promptfoo-Eval, AiCredits-Abrechnung, EU-Provider-Compliance) fällt weg. MCP nutzt **dieselbe mandanten-gescopte Daten-/Aktions-Fläche** wie die geplante Public-API ([[VR-P22a]]/[[VR-P22b]]) → keine Doppelarbeit. **v1 (KI komplett gestrichen):** R-P13 (KI-Layer/Mistral-Gateway) + D-P13 entfernt, EXT-11 + Mistral-Key gelöscht, Milestones **M083–M088 gelöscht**, R-P12b verliert den Sprach→Automation-Teil (bleibt reiner visueller Flow-Editor). Neues letztes v1-Glied = R-P12b (~1 Tag Puffer → Härtung). Freeze/Launch **09./10.07.** bleiben fix. **v2:** VR-P25/VD-P25 (HiveMind/RAG/Whisper) **gestrichen** (Plane: Cancelled) → neu **VR-MCP + VD-MCP** im Slot **18.–20.07.**, `blocked-by` VR-P22a/b (Public-API, fertig 17.07. → kein Konflikt). **MCP-Design (Best-Practice, MCP-Spec-Rev 2025-11-25):** Remote-Server über **Streamable HTTP** (stateless Start), SDK **`@modelcontextprotocol/sdk`** (Gerüst `apps/web/app/modules/mcp/` existiert bereits). **Auth Stufe 1 = API-Key-Bearer** (aus VR-P22), **Stufe 2 = OAuth 2.1** (PKCE/RFC 9728/8707) später. **Tenant aus dem Token** (nie aus Tool-Argumenten) → `withTenantScope()` + RLS. **Read-Tools + Write-Tools** (`withPermission()` + Client-Confirmation + `auditEvent`/`logActivity`). Annotations (`readOnlyHint`/`destructiveHint`) nur als Hint — echte Guards serverseitig. Rate-Limit + Output-Limits + Pagination; Tenant-Daten sind **Daten, nie Instruktionen** (Prompt-Injection). **Code-Rip-out (Variante a):** raus = `packages/ai`, `packages/vector-store`, Module ai/ai-gen/ai-cost/chatbot/ ai-elements, AI-Routen, Worker-AI-Jobs, `ai.prisma` — UND die Legacy-PromptFlow-Engine (promptBuilder/admin/prompts/ `promptFlows.prisma`). **Bleiben:** `@modelcontextprotocol/sdk`, `modules/mcp/`, der EU-Sterilisations-Guard. **Pflichtenheft:** A.12.3 (EU-LLM-Provider) und V.2.2 („KI als Bedienoberfläche der Automation") widersprechen jetzt → **Change-Request (M176) nötig**; bis dahin gilt diese AD vor dem Pflichtenheft (Pivot-Vorrang). Anti-Liste V.3.2.7 (HiveMind autonom) bleibt erfüllt (wird gar nicht gebaut). **Betrifft:** R-P13/D-P13/EXT-11/M083–M088 (entfernt), R-P12b (Trim), VR-P25/VD-P25 (gestrichen), VR-MCP/VD-MCP (neu), R-P11a/R-P11b/D-P3/R-P5/D-P5/D-P20 (KI-Andock umdeuten/trimmen), VA-P24b/VA-P29/VR-P31/VR-P33 (KI-Teile entfallen). ## KWP (separater Strang — siehe `docs/boilerplate-mapping/M19-kwp-integration.md`) KWP bekommt eine offizielle **API-Anbindung** → M089 (Windows-Sync-Programm) entfällt, M090/M091/M092/M094/M095 vereinfachen sich, M093 bleibt. KWP als `DataConnection type='rest-api'` ins Apps-Engine-Muster (M33a). M089–M095 vor Umsetzung neu schneiden.