32 KiB
HiveGrid 2.0 — Architektur-Entscheidungen
Entscheidungen aus dem Boilerplate-Abgleich (Stand 2026-06-04). Diese ADR-Liste ergänzt
AGENTS.mdund 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-Pfadtenants/{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.
AdminAuditLogbraucht kein neues Schema: vorhandenerAuditEvent(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 Docsui-hivegrid/@hivegrid/foundationsagen, istui-app/@toolctrl/foundationgemeint.
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;
Statusist 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:
Kontaktist Oberbegriff mitKontakttyp(Privatperson/Organisation),Kontaktstatus(Kontakt/Kunde/Bestandskunde) undKontaktart(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:
- Ein Modul = eine eigene
prisma/schema/<modul>.prisma. Neue Modelle nie incore.prisma/accounts.prisma. Diese nur für Relationen zuTenant/Useranfassen, Rück-Relationsfelder dort minimal halten. - Migrationen erst beim Merge. Auf dem Feature-Branch mit
prisma db pushiterieren (kein Migrations-File). Die eine Migration pro Phase erzeugt der Dev, der zuerst aufmainmerged — Forward-only mit Inverse-SQL als Kommentar (AGENTS.md DoD 28). Tägliche Integration hält Diffs klein. - CI-Gate vor Merge:
prisma validate+prisma migrate statusgrü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.ipspeichernHMAC-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/longitudebleiben 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). publicUrlin 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:
- Boilerplate als Code-Basis einziehen (Repo ist heute Docs-only).
- Naming-Drift beheben:
@toolctrl/*→@hivegrid/*,ui-app-Stub →ui-hivegrid, Sidebar-Konvention, falsche AGENTS.md-Pfade (indexed-entities.ts,sidebar.config.ts). - EU-Sterilisation: GitHub/Google-OAuth-Reste +
User.githubId/googleIdentfernen. - 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. - 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):
- Touch-Signatur vor Ort (Canvas → PDF) + Fern-Link-Signatur (Angebot remote annehmen)
- OTP-Identifizierung (E-Mail; SMS als stärkere Zwei-Kanal-Variante)
- PDF-Versiegelung (PAdES mit HiveGrid-Organisations-Zertifikat → jede Änderung bricht die Signatur)
- Zeitstempel (TSA, RFC 3161; nicht-qualifiziert genügt für SES/AES)
- Audit-Trail über M001 (Link/Öffnen/OTP/Signatur: wer, wann, IP, Gerät, Geo, PDF-Hash; append-only)
- Completion-Certificate als PDF-Anhang (sichtbare Beweis-Doku)
- 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.