Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,450 @@
# 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.