Files
HiveGrid-Wireframes/hivegrid-cloud/docs/ARCHITECTURE-DECISIONS.md
T

451 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<modul>.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.