Hetzner-MVP für Industriekunden — Implementierungsplan

Overview

Operationale Umsetzung des bestehenden ADRs llm-hosting-eu-optionen (April 2026), das Hetzner Frankfurt als Default für App+DB und Mistral La Plateforme als Default-LLM für DSGVO-strenge Industriekunden festgelegt hat. Der bisherige Stack lebt auf AWS (Becker, VF, Voice-POC, agents-platform). Mit dem neuen Industriekunden-Lead (Datensicherheit oberste Priorität, „kein US-Anbieter”) wird die Umsetzung akut.

Dieser Plan baut einen Lean-Stack — bewusst schmaler als ein AWS-Twin: kein K3s, keine MCP-Bundle-Schicht, kein eigener Logging-Stack. Pattern: 2 Hetzner Cloud Server + Docker Compose + Postgres+pgvector + Mistral via LiteLLM (SDK-Mode) + Cloudflare-Tunnel + Better Stack als externer Log-Drain.

Wall-Clock-Ziel: ≤1 Woche aktive Bauzeit bis der Lead onboarded werden kann.

Problem Frame

Aus _index §Problem Frame: Neuer Industriekunden-Lead mit „kein US-Anbieter”-Anforderung. AWS Frankfurt fällt unter US CLOUD Act (siehe anthropic-datenschutz und llm-hosting-eu-optionen) — ein Knock-Out für deutsche Industrieunternehmen die Cloud-Souveränität verlangen. Hetzner ist deutsche AG, ISO 27001:2022 + BSI C5:2020 Type 2 zertifiziert, RZ Frankfurt/Nürnberg/Falkenstein/Helsinki — kein US-Mutterkonzern, kein CLOUD Act.

Requirements Trace

Carry-Over aus _index:

Infrastruktur: R1 (Hetzner-Account + Project) · R2 (2 Cloud Server) · R3 (Object Storage 7y-Retention) · R4 (Postgres 16 + pgvector + 50 GB Volume) · R5 (Backup-Strategie)

LLM-Schicht: R6 (Mistral La Plateforme + DPA) · R7 (LiteLLM-Adapter Pflicht) · R8 (Audit-Hook → Object Storage)

Edge/Auth: R9 (Cloudflare-Tunnel) · R10 (Cloudflare Access SSO/Whitelist)

Operations: R11 (Better Stack Log-Drain) · R12 (IaC-Approach) · R13 (Cost-Limit + Notification)

Daten-Souveränität: R14 (Client-Side-Encryption via age) · R15 (Hetzner AVV + Mistral DPA) · R16 (VVT-Eintrag)

Capability-Dokumentation: R17 (intern/capabilities/hetzner/_index.md) · R18 (lazyants Hetzner-MCP registriert)

Scope Boundaries

Aus _index §Scope Boundaries, hier verschärft mit Plan-Befunden:

Drin: 2-VM-Stack, Docker Compose, Becker-Stack-Mirror (Next.js + Vercel AI SDK + Drizzle + pg-boss + Better Auth + Monorepo via pnpm/Turborepo, D-PLAN-15), Mistral Medium 3 als Default (D-PLAN-3), pgvector-HNSW mit halfvec(1024) wenn RAG-Use-Case, Multi-Tenant tenant_id + RLS from Day One (D-PLAN-16), Cloudflare-Tunnel + Access, Better Stack, lazyants Hetzner-MCP, WAL-G-Backups, ClamAV optional bei File-Uploads, Open WebUI optional je Use-Case (D-PLAN-17).

Draußen — explizit deferred:

  • Terraform raus aus erster Iteration. Empfehlung Best-Practices-Agent + Repo-Pattern-Agent: hcloud CLI + lazyants MCP + Docker Compose reichen. Terraform-Pattern wird beim 2. Industriekunden extrahiert.
  • Cloudfleet K3s, MCP-Bundle, Vault, Loki/Grafana, Wazuh, Multi-Tenant-Schicht — wie in _index dokumentiert.
  • Markdown-Vault-Schicht (Pattern aus markdown-und-db-trennung) bewusst noch nicht aufgesetzt — Lead-Briefing entscheidet ob Knowledge-Layer dabei sein muss oder reines Custom-Backend (analog BAS-Twin) reicht.
  • Migration bestehender Kunden — running system.

Bewusst nicht entschieden bis Lead-Briefing:

  • Konkreter Use-Case (Custom-Backend vs RAG-System vs Internal-Chat)
  • Domain-Name der App
  • Welche Felder Client-Side-Encryption brauchen

Context & Research

Relevante Code- und Vault-Patterns

  • mcp-hosting-fargate-tunnel — Cloudflare-Tunnel-Sidecar-Pattern 1:1 übertragbar (Tunnel-API via cloudflare/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel ist Hetzner-konstant, config_src: cloudflare für zentrale Ingress-Rules, Lessons aus 3-Anlauf-Pivot vom Mai 2026)
  • open-webui-fargate-bedrock — Open-WebUI-Pattern, übertragbar auf Hetzner+Mistral (Provider-Prefix mistral/... statt bedrock/..., SQLite-NFS-Anti-Pattern: Open-WebUI mit Postgres-Backend stattdessen). Direkt anwendbar wenn D-PLAN-17 aktiviert
  • _index + ~/source/bas-twin/Stack-Mirror-Vorlage (D-PLAN-15). Konkrete Files zum Forken/Adaptieren: ARCHITECTURE.md (Sektion 3+4 Stack + Diagramm), package.json + pnpm-workspace.yaml + turbo.json (Monorepo-Root), packages/db/SCHEMA.md (Multi-Tenant-Schema-Convention), packages/db/migrations/0001_p1_safe_schema.sql etc. (Drizzle-Migration-Pattern), apps/worker/src/stages/ (Stage-Pattern als Vorlage), CLAUDE.md (AI-Routing-Pattern), conventions.md, 27× CONTEXT.md-Files in Code-Ordnern
  • stage-idempotenz-pgboss-upsert — pg-boss-Pattern für idempotente Pipeline-Stages, Becker-Lesson, direkt anwendbar
  • code-stil-ai-optimiert — Code-Stil aus Becker, übernehmen in neuen Stack
  • av-becker.md — Audit-Logging-Vorbild: separater Bucket, Versioning, Public-Access-Block, 7y-Lifecycle (2555d), Schreib-only-Principal. Übertragbar auf Hetzner Object Storage mit Object-Lock-Compliance-Mode + Schreib-only-Access-Key
  • _index.md · accounts.md · buckets.md · _context.md — Vorbild-Struktur für intern/capabilities/hetzner/-Bereich
  • _index und _index — Step-Tabellen-Pattern (1A.1, 1B.2-Nummerierung, Status-Markierungen ✅/☐/❌, „Aktive Bauzeit”-Spalte, Wall-Clock-Zeile pro Phase, Decision-Points-Tabelle, „Was Claude NICHT kann”-Sektion)
  • mayday — zweite Hetzner-Anwendung im Vault (VPS + Coolify + Postgres + R2). Coolify war zwar Anker, Plan hier bleibt aber bei nativem Docker Compose — Coolify wäre ein zusätzliches Operations-Tool für unsere erste Industrie-Iteration

Institutional Learnings

  • llm-hosting-eu-optionen (April 2026) — Mistral La Plateforme als Default für DSGVO-strenge Kunden, Hetzner Frankfurt als App+DB-Default mit Pro-Argumentation (Wartung, RZ-Klasse, Skalierbarkeit, DSGVO-gelöst, Remote-Help). Self-Hosted-LLM auf Hetzner-GPU nur bei >2000-5000 Anfragen/Monat oder „Daten verlassen DE-RZ niemals”-Härte
  • runner-architektur (April 2026) — LiteLLM als Provider-Adapter ist Architektur-Pflicht für alle LLM-Anbindungen. Wechsel A→B per Konfig, kein Refactor
  • cloudflare-dsgvo — CF-Tunnel ist für DSGVO-strenge Industriekunden konform. Pflicht: AVV-Check, Subprozessor-Eintrag in Kundenvertrag, Subprozessor-Liste pflegen. Bei US-Mutter-Härte: Data Localization Suite als Eskalation (~20 USD/Mo)
  • anthropic-datenschutz — 8-Punkt-Pflicht-Checkliste fürs DSGVO-Onboarding: DPA mit Kunde, Sub-Processor-Liste, DPA mit LLM-Anbieter, Datenstandort abgestimmt, VVT-Eintrag, TOMs, Datenminimierung, PII-Filter
  • eu-ai-act-pflichten — Risikoklassifizierung vor Sprint-Start; Industrie-B2B-Use-Cases sind strukturell nicht Hochrisiko (kein Profiling natürlicher Personen); KI-Kompetenz-Schulungs-Modul (5 Bausteine) als Sprint-Modul
  • markdown-und-db-trennung — Hybrid-Pattern S3-Markdown-Vault + DynamoDB. Auf Hetzner = Object Storage (Markdown) + Postgres (Operational). Anti-Pattern: Knowledge in Postgres legen. Falls Knowledge-Layer Teil des Use-Case wird, separates Bucket nötig
  • becker §Do’s — DSGVO-konformes Hosting (Hetzner Frankfurt + Mistral La Plateforme) stand als Architektur schon im April fest, wurde aber operativ auf AWS umgesetzt — dieser Plan ist die erste echte Umsetzung

External References (Mai 2026)

Terraform / hcloud:

LiteLLM:

Cloudflare:

Mistral La Plateforme:

pgvector:

  • pgvector GitHub — v0.8.2, halfvec(1024) für Mistral-Embed empfohlen (50% kleinerer Index), HNSW als Default für <1M Vektoren

Postgres-Backup-Stack:

Hetzner Object Storage:

Encryption:

  • age GitHub + SOPS Docs — age als Industry-Default für File-Encryption, sops+age für strukturierte Secrets

Key Technical Decisions

DecisionRationale
D-PLAN-1 Terraform raus aus 1. Iterationhcloud_object_storage_bucket existiert eh nicht im Provider (Doku-Lücke). Plus: Empfehlung beider Recherche-Agents — hcloud CLI + lazyants MCP + Docker Compose reichen für 2-VM-Stack. Terraform-Pattern extrahieren wir beim 2. Industriekunden, wenn sich Wiederholung lohnt. Konsistent mit Repo-Konvention „Patterns wenn 2-3 Kunden”
D-PLAN-2 LiteLLM SDK-Mode statt Proxy-ContainerBei 1 App / 1 Tenant ist Proxy-Container unnötiger Overhead. SDK-Mode (litellm.completion(...) direkt im App-Container) ist simpler, gleicher Callback-Mechanismus für Audit-Hooks. Proxy-Mode kommt erst wenn mehrere Apps gemeinsamen LLM-Gateway brauchen
D-PLAN-3 Mistral Medium 3 als Default-Modell90% Large-3-Qualität für 1/3 Preis (3 per 1M). 128k Context, multimodal, gut auf Deutsch. Large 3 nur wenn echt komplexes Multi-Step-Reasoning + >100k Context. Small 3.x für hochfrequente Klassifikation/Extraktion als Pre-Filter
D-PLAN-4 WAL-G + 3-Layer-BackuppgBackRest seit 27.04.2026 archiviert — No-Go für neue Setups. WAL-G ist Go-Binary, S3-nativ, parallele Base-Backups + WAL-Archiving alle 60s. Plus pg_dump täglich als Logical-Backup (anderer Failure-Mode), plus Hetzner Volume-Snapshots wöchentlich als 3. Layer
D-PLAN-5 pgvector HNSW + halfvec(1024)HNSW gibt 30× Throughput und 15× p99-Latency-Vorsprung gegen IVFFlat bei <1M Vektoren (Mittelstand-Volumen). halfvec(1024) halbiert den Index-Speicher gegen vector(1024) bei identischer Recall. m=16, ef_construction=64 als Default
D-PLAN-6 Hetzner Object Storage Object-Lock Compliance-Mode7y-HGB-Retention für LLM-Audit-Logs strukturell gegen Löschung gesichert (auch nicht durch uns selbst). Pflicht: Compliance-Mode bei Bucket-Erstellung aktivieren — nicht nachträglich möglich, unumkehrbar. Erst Test-Bucket mit 1-Tag-Retention probieren
D-PLAN-7 Audit-Log via LiteLLM s3_v2 + JSONL/Hour gzipRefactored s3_v2-Callback (async, batched, +130 RPS gegen alte s3-Callback). Pattern audit/<kunde>/<yyyy>/<mm>/<dd>/<hour>.jsonl.gz. Hourly statt Daily/Per-Call weil: (a) Lifecycle-Transitions granular möglich (z.B. 90d warm → cold), (b) Restore bei Fehl-Fenster <24h schneller handlbar, (c) parallele Async-Uploads via s3_v2 ohne Bottleneck, (d) Per-Call wäre Millionen-Objekt-Bereich bei höherem Volume — gegen Hetzner-50M-Objects-Limit
D-PLAN-8 age + Multi-Recipient EncryptionMaster-Key NICHT im Hetzner-Project (sonst Kompromittierung = Backup offen). Multi-Recipient gegen Bus-Faktor: Marvin-Laptop + Marvin-Recovery-Safe + Kunden-CTO-Recovery. Rotation via add-then-remove alle 12 Monate + sofort bei (a) Mitarbeiter-Austritt, (b) vermuteter Kompromittierung, (c) Verlust eines Recipient-Devices
D-PLAN-9 Cloudflare-Tunnel config_src: cloudflareZentrale Ingress-Rules in CF-Dashboard, keine config.yml im Container. Sauberer als File-Mount-Pattern. Token-Pattern (remotely-managed) statt cred-File
D-PLAN-10 Better Stack als externer Log-Drain0-25 €/Mo extern statt 2 Tage Setup + monatliche Pflege eines eigenen Loki+Grafana-Stacks. Neu als Sub-Prozessor: AVV abklären, EU-Hosting bestätigen, in Subprozessor-Liste an Kunde aufnehmen
D-PLAN-11 Hetzner-Account-Klärung als 1. Schrittintern/firma/stack.md:63 sagt Hetzner ist „aktiv
D-PLAN-12 PII-Filter DEFAULT-ON + Object-Lock-Gate (Security-Audit-P0 F-02)Object-Lock-Compliance-Mode löscht NICHT (irreversibel). Wenn unmaskierte PII einmal im Audit-Bucket: 7y nicht löschbar = DSGVO-Art-17-Konflikt strukturell unauflösbar. Daher: Microsoft Presidio (oder Regex-Pre-Filter) als PII-Filter-Middleware ist Pflicht in Unit 5 — auch wenn Lead-Briefing PII verneint. Plus: Object-Lock-Aktivierung auf Audit-Bucket erst NACHDEM Lead-Briefing den Use-Case + PII-Klassifikation bestätigt hat. Default-Position bewusst invertiert vs ursprünglicher Plan-Annahme
D-PLAN-13 SSH-Zugriff via Tailscale (kein IP-Whitelist) (Security-Audit-P1 F-07)Marvin arbeitet remote (Lissabon ab Mai, Hotel-WLAN, mobile Hotspots) — keine statische IP. IP-Whitelist führt entweder zu Update-Aufwand pro Standortwechsel oder breitem CIDR der die Whitelist trivial macht oder Lockout. Tailscale (oder Wireguard-self-hosted) ist Zero-Trust-Identity, MFA-fähig, kein offener SSH-Port. Cloud-Firewall blockt Port 22 vom Internet komplett
D-PLAN-14 Audit-Bucket Layered Encryption (Security-Audit-P0 F-03)Object-Lock schützt vor Löschung, NICHT vor Lesen. Bei S3-Access-Key-Leak hätte Angreifer 7y-Volltext-Vollzugriff. Daher: Audit-Files VOR Upload mit age verschlüsseln (Multi-Recipient analog WAL-G), App-Server bekommt nur PUT-Key, Read-Access nur über separaten Marvin-IAM-User mit MFA. Audit-Bucket-S3-Key-Rotation alle 90 Tage
D-PLAN-15 Becker-Stack-Mirror als DefaultNach Becker-Projekt-Review (Sprint 1 läuft produktiv mit funktionierendem Tech-Stack): wir nehmen den gleichen Stack für den neuen Industriekunden. Next.js 15 + React 19 + TypeScript für Web + API-Routes, Node-Worker für Background-Jobs, PostgreSQL 16 + Drizzle ORM statt SQLAlchemy, pg-boss als Queue auf gleicher Postgres, Vercel AI SDK als LLM-Adapter (TypeScript-nativ, gleicher Effekt wie LiteLLM für Python), Better Auth + RBAC, pnpm + Turborepo Monorepo. Ersetzt frühere FastAPI/Python-Annahme im Plan. Vorteil: 60-70% des Becker-Repo-Skeleton ist auf neuen Kunden übertragbar (Build-Config, DB-Setup, Auth, Worker-Skelett, Test-Pattern, ce-review-Baseline). Wenn Becker später auf Hetzner migriert: 1:1 portierbar
D-PLAN-16 Multi-Tenant from Day OneBecker-Lesson aus ADR 0005 (multi-tenant): tenant_id als erste Spalte jeder Tabelle + Postgres Row-Level-Security (RLS), Drizzle-Middleware setzt tenant_id automatisch via Auth-Context. Auch bei 1-Kunden-MVP — null Mehraufwand beim Day-One-Bau, aber massiver Refactor wenn nachgezogen. Plus: Industriekunden haben fast immer Geschwister-Gesellschaften (Becker hat BSS + BES als Upsell-Potenzial). Kein Tenant-Switching-UI in 1. Iteration nötig — nur das Schema
D-PLAN-17 Open WebUI als optionaler Stack-BestandteilWenn der Use-Case Multi-User-Chat-Frontend braucht (analog VF, perspektivisch auch Becker für Vertriebs-Team-Chat gegen BAS-Twin-Daten): Open WebUI als zusätzlicher Docker-Compose-Service. Pattern bekannt aus open-webui-fargate-bedrock, hier auf Hetzner mit Mistral statt Bedrock — Vercel AI SDK / LiteLLM-Sidecar davor für Provider-Abstraction. SQLite-auf-NFS-Anti-Pattern beachten (aus VF-Lessons), stattdessen Open-WebUI mit Postgres-Backend gegen unsere Postgres. Default in 1. Iteration: deferred bis Use-Case-Klärung im Lead-Briefing — Custom-Backend-App ist Standard, Open WebUI nur wenn Chat-UI ein gewünschtes Feature ist
D-PLAN-18 Becker als erster Hetzner-Stack-Validator (Pfad A) (2026-05-13)Statt Greenfield-Lead-Industriekunde wird Becker als Pilot-Mandant gewählt. Begründung: (a) realer Use-Case + aktive Pipeline (Aufwandsschluessel v15 von Alex, 5 Stages) statt Lead-Briefing-Hypothesen, (b) Stack-Mirror-Hypothese D-PLAN-15 wird gegen lebenden Code validiert, (c) Becker-V3-Vertragspaket sagt Hetzner zu, Lieferung jetzt = Glaubwuerdigkeit, (d) AWS-Bestand av-becker bleibt als Cost-0-Backup stehen (Buckets leer + kein Compute, kostet effektiv nichts), kein Rip-out, kein Migrations-Risk. Industriekunden-Lead wird Project #2 sobald Briefing durch. Slug: av-becker (Mirror zum AWS-Account-Namen, eindeutig pro Kunde)
D-PLAN-19 Phase-Trennung: Infra-leer (Phase 1) vs App-Live (Phase 2) (2026-05-13)Realer bas-twin-Stand: packages/llm/src/bedrock.ts ist direkt Bedrock-gewrappt, nicht via Vercel AI SDK abstrahiert. D-PLAN-15 Stack-Mirror ist Future-State, nicht gegenwaertig. Heisst: Layer-Refactor bedrock.ts → Vercel AI SDK + Mistral-Provider ist Sprint-2-Code-Arbeit, kollidiert mit Sprint-1-Wrapup (Stage 5 PDF Branch feat/stage-5-pdf-und-runbook PR #23 noch open). Saubere Sequenz: Phase 1 (jetzt) — Hetzner-Infra steht leer + bereit (Unit 2-4, 6-7 ohne App-Deploy). Phase 2 (nach Sprint-1-Wrapup ~31.05.) — Sprint 2 macht LLM-Refactor + App-Deploy + DB-Migrate + Daten-Sync (Unit 5 + 8). Vorteil: entkoppelte Risken, Mistral-DPA-Wartezeit (2-8 Wochen) faellt in Phase-1-Window, kein Sprint-Stress. Bridge-Path falls DPA nicht durch: LLM-Layer ist post-Refactor provider-agnostic via Vercel AI SDK, App auf Hetzner kann temporaer Bedrock-EU als Bridge nutzen (Anthropic-DPA ist da), Switch auf Mistral als Adapter-Tausch sobald DPA durch

Open Questions

Resolved During Planning

  • Backup-Strategie Postgres → WAL-G + pg_dump + Hetzner Volume-Snapshots (3-Layer, siehe D-PLAN-4)
  • Audit-Log-Format → JSONL/Hour mit gzip via LiteLLM s3_v2 (D-PLAN-7)
  • Capability-Doku-Struktur → 1:1-Spiegel von intern/capabilities/aws/-Bereich: _index.md, _context.md, projects.md, storage.md, av-industriekunde.md
  • IaC-Approach 1. Iteration → kein Terraform, hcloud CLI + lazyants MCP + Docker Compose (D-PLAN-1)
  • LiteLLM-Mode → SDK statt Proxy (D-PLAN-2)
  • Mistral-Modell-Wahl → Medium 3 Default (D-PLAN-3)
  • pgvector-Setup → HNSW + halfvec(1024) + m=16, ef_construction=64 (D-PLAN-5)
  • Hetzner-Skill-Frage → deferred bis 2. Industriekunde, dann Pattern extrahieren

Deferred to Implementation

  • Use-Case Industriekunde (Custom-Backend vs RAG vs Internal-Chat vs Pipeline-Verarbeitung wie Becker) — kommt mit Lead-Briefing, bestimmt: (a) ob apps/worker Pipeline-Stages braucht (wie Becker), (b) ob pgvector aktiviert wird (RAG), (c) ob Open WebUI als zusätzliche UI dazu kommt, (d) ob ClamAV-Sidecar gebraucht wird (File-Uploads).
  • Becker-Repo-Skeleton-Reuse-Grad — wieviel bas-twin/ direkt forken vs neu aufsetzen: Build-Config + Auth + DB-Skeleton + Worker-Boilerplate sind klar 1:1, Stages + Domain-Modell sind kunden-spezifisch. Entscheidet sich beim ersten Sprint-Schritt (~Tag 1-2).
  • Open WebUI für Becker-Bestand (D-PLAN-17) — separater Folge-Schritt, NICHT Teil dieses Plans. Wenn gewünscht: eigenes Mini-Projekt analog _index-Pattern, Hetzner-Stack-Mirror als Vorlage. Realistischer Anschluss-Punkt nach Becker M3 (15.07.).
  • Domain-Name der App (z.B. <kunde>.agenticventures.de oder Kunden-eigene Domain)
  • Welche Felder Client-Side-Encryption brauchen — hängt am Datenmodell, deferred bis App-Code-Phase
  • Identity-Provider für Cloudflare Access (Azure AD vs Google Workspace vs Keycloak vs OIDC) — kommt mit Kunden-IT-Briefing
  • Modell-Wahl für Embed-Use-Cases — falls RAG dabei: mistral-embed 1024-Dim ist gesetzt; falls Code-Search: Codestral Embed deferred
  • pgbouncer-vs-direkt-Pool im App-Code — Best-Practices-Agent empfiehlt pgbouncer als Session-Pooler bei CCX13. Default ja, prüfen wenn App-Layer steht

High-Level Technical Design

Illustriert die Ziel-Architektur als Direction für Review, nicht als Implementations-Spezifikation. Der bauende Agent behandelt es als Kontext, nicht als Vorlage.

flowchart TB
    subgraph Edge[Cloudflare Edge - extern]
        U[User-Browser oder Service]
        CFA[Cloudflare Access<br/>AUD-Tag, IdP, JWT]
        CFT[Cloudflare Tunnel<br/>TLS-Termination]
    end

    subgraph Hetzner[Hetzner Project Industriekunde - eu-central]
        subgraph Server1[Cloud Server CCX23 - Web+Worker]
            CFD[cloudflared Sidecar<br/>config_src cloudflare]
            WEB[apps/web<br/>Next.js 15 + Vercel AI SDK<br/>Better Auth + CF-Access-JWT-Middleware]
            WORKER[apps/worker<br/>Node + pg-boss-Subscriber<br/>Pipeline-Stages + LLM-Calls]
            CLAMAV[ClamAV-Sidecar<br/>optional bei File-Uploads]
            OWUI[Open WebUI<br/>optional je Use-Case<br/>D-PLAN-17]
        end

        subgraph Server2[Cloud Server CCX13 - DB]
            PGB[pgbouncer Session-Pooler]
            PG[Postgres 16<br/>pg-boss Jobs + audit_log<br/>pgvector v0.8.2 optional fuer RAG<br/>Drizzle Migrations + RLS pro tenant_id]
            VOL[(50 GB Volume<br/>Hetzner Snapshots)]
        end

        BUCKET[(Object Storage<br/>Object-Lock Compliance 7y<br/>App-Daten plus age-encrypted Audit-Logs<br/>plus WAL-G und pg_dump Backups)]

        FW[Cloud Firewall<br/>SSH 22 nur via Tailscale<br/>plus interner Compose-Traffic]
    end

    subgraph External[Externe Services]
        MISTRAL[Mistral La Plateforme<br/>FR Mistral Medium 3<br/>plus Mistral Embed optional]
        BS[Better Stack<br/>Log-Drain plus Alarme]
    end

    U -.OIDC SAML.-&gt; CFA
    CFA --&gt;|Cf-Access-Jwt-Assertion| CFT
    CFT -.QUIC outbound 443.-&gt; CFD
    CFD --&gt;|http web 3000| WEB
    CFD -.optional.-&gt; OWUI
    WEB --&gt;|Drizzle pgbouncer 6432| PGB
    WORKER --&gt;|Drizzle pgbouncer 6432| PGB
    WORKER -.LISTEN NOTIFY pg-boss.-&gt; PG
    PGB --&gt; PG
    PG --&gt; VOL
    WEB --&gt;|Vercel AI SDK| MISTRAL
    WORKER --&gt;|Vercel AI SDK| MISTRAL
    WEB --&gt;|aws-sdk S3-API| BUCKET
    WORKER --&gt;|Audit-Hook age-encrypted| BUCKET
    WORKER -.scan.-&gt; CLAMAV
    PG --&gt;|WAL-G stream age-encrypted| BUCKET
    PG --&gt;|pg_dump nightly age-encrypted| BUCKET
    WEB --&gt;|stdout JSON| BS
    WORKER --&gt;|stdout JSON| BS
    CFD --&gt;|stdout JSON| BS

    style BUCKET fill:#e1f5ff
    style MISTRAL fill:#fff3e1
    style BS fill:#fff3e1
    style OWUI stroke-dasharray: 5 5
    style CLAMAV stroke-dasharray: 5 5

Lesehilfe: Stack-Mirror zu Becker (D-PLAN-15) — gleiche Schichten wie bas-twin/-Repo, aber auf Hetzner statt AWS Fargate und mit Mistral statt Bedrock. Public-Eingang ist ausschließlich Cloudflare Edge — keine Public-IP an den Hetzner-Servern. cloudflared etabliert outbound-QUIC zur Cloudflare-Anycast-Infra. Web-App ist Next.js 15 mit Server-Components + API-Routes + Better-Auth-Session, plus eine Middleware die das Cloudflare-Access-JWT (Cf-Access-Jwt-Assertion) validiert (zweite Schicht über Better Auth — defense in depth). Worker ist separater Node-Prozess der pg-boss-Jobs aus Postgres holt und Pipeline-Stages ausführt (analog Becker apps/worker/src/stages/). Beide nutzen Drizzle als ORM, beide gehen über pgbouncer auf Postgres. Multi-Tenant via tenant_id-Spalte + RLS (D-PLAN-16) ist im Drizzle-Schema von Anfang an drin, auch bei nur einem Kunde. LLM-Calls gehen über Vercel AI SDK an Mistral La Plateforme — Provider-Wechsel zu Bedrock/Anthropic ist Adapter-Tausch in packages/llm/. WAL-G streamt WAL-Files continuous, pg_dump läuft täglich, beides nach Object Storage mit age-Verschlüsselung dazwischen. Log-Output via Compose-Logging-Driver an Better Stack. ClamAV-Sidecar nur aktiviert wenn der Use-Case File-Uploads hat (Becker hat das wegen Email-Anhänge + EDI; Plan-Default je Use-Case). Open WebUI als optionaler Compose-Service wenn der Use-Case Multi-User-Chat-Frontend braucht (gestrichelt im Diagramm — Lead-Briefing entscheidet, D-PLAN-17). Kein US-Anbieter im Daten-Pfad (Cloudflare ist DPF-abgefedert; bei Härte: Data Localization Suite).

Implementation Units

  • Unit 1: Account-Klärung + Capability-Doku-Skeleton (Capability-Doku-Skeleton ✅ 2026-05-13; Account-Pfad-Entscheidung Option A ✅ 2026-05-13; MCP-Install + Token-Setup + Smoke-Test ✅ 2026-05-13; Account-Migration UG-Profile/Billing/Payment läuft asynchron als Marvin-Console-Aktion)

Goal: Hetzner-Account-Situation klären, Industriekunden-Project anlegen, Vault-Capability-Bereich intern/capabilities/hetzner/ als 1:1-Spiegel von intern/capabilities/aws/ aufsetzen. lazyants Hetzner-MCP in MCP-Config registrieren damit Claude Code Infra-Steuerung kann.

Requirements: R1, R17, R18

Dependencies: keine (Start-Unit)

Files:

  • Create: intern/capabilities/hetzner/_index.md (analog intern/capabilities/aws/_index.md — Tabellen für Projects, Identity, Cost-Alarme, Service-Aktivierung, Naming-Konvention, CLI-Profile, Nächste Schritte)
  • Create: intern/capabilities/hetzner/_context.md (analog intern/capabilities/aws/_context.md — drei Pflicht-Sections aus _meta/conventions §11)
  • Create: intern/capabilities/hetzner/projects.md (analog accounts.md — Project-Inventar)
  • Create: intern/capabilities/hetzner/storage.md (analog buckets.md — Bucket-Map + Naming-Konvention)
  • Modify: CLAUDE.md (Routing-Tabelle ergänzen: „Hetzner-Hosting (Projects, Bestand, Pattern)” → [[intern/capabilities/hetzner/_index]])
  • Modify: intern/firma/stack.md:63 (Hetzner-Account-Eintrag aktualisieren mit Status der Klärung — privat@hello@-Migration vs Co-Existenz)

Approach:

  • Marvin entscheidet zuerst (siehe Decision-Points unten): Account-Pfad. Drei Optionen: (a) bestehenden privat@-Account auf hello@-UG migrieren (Rechnungs-Adress-Wechsel), (b) neuen hello@-UG-Account anlegen und privat@ daneben laufen lassen (sauber getrennt aber Doppel-Account), (c) neues Project im bestehenden Account anlegen (schnellster Pfad, aber Vermischung).
  • Pro Industriekunde eigenes Hetzner Project (Mandanten-Trennungs-Doktrin aus aws-multi-account-strategie gilt analog). Project-Name av-<kunde-slug>, Labels project=<kunde-slug>, environment=prod, managed-by=marvin auf jeder Resource (Best-Practice-Pattern).
  • API-Token mit Read+Write-Permission für das Project generieren, in 1Password ablegen, lokale env-var HCLOUD_TOKEN.
  • lazyants-MCP: npm install -g @lazyants/hetzner-mcp-server, dann in .mcp.json oder Claude-Code-Settings registrieren mit HETZNER_API_TOKEN.

Patterns to follow:

  • Frontmatter aus _meta/schemas.md §5.23 (type: doc) für _index.md und Doku-Files
  • Frontmatter §5.20 für _context.md (type: folder_context)
  • Sektion-Struktur aus intern/capabilities/aws/_index.md 1:1 übernehmen, „AWS” → „Hetzner”

Test scenarios:

  • Test expectation: none — pure Doku + Account-Setup, kein Code

Verification:

  • hcloud project list zeigt das neue Project unter dem zugewählten Account
  • lazyants-MCP via Claude Code aufrufbar: mcp__hetzner__list_servers liefert leere Liste ohne Fehler
  • intern/capabilities/hetzner/_index.md öffnet sich in Obsidian ohne tote Wikilinks
  • CLAUDE.md Routing-Tabelle hat neuen Hetzner-Eintrag

Aktive Bauzeit: 30 Min Marvin (Account-Entscheidung + Console-Setup) + 60-90 Min Claude (Doku-Skeletons + MCP-Reg)


  • Unit 2: Infrastructure-Provisioning (Server + Network + Firewall + SSH)

Goal: Beide Hetzner Cloud Server provisionieren, Networking, Firewall, SSH-Zugriff einrichten. Ohne Terraform — via hcloud CLI plus lazyants-MCP für Ad-hoc-Operationen.

Requirements: R2

Dependencies: Unit 1 (Project + API-Token müssen existieren)

Files:

  • Create: intern/capabilities/hetzner/av-<kunde-slug>.md (analog intern/capabilities/aws/av-becker.md — Header-Tabelle, Was-drin-ist Sub-H3s pro Service-Kategorie, Wer-hat-Zugriff, Audit-Trail, Offene Punkte)
  • Create: ~/source/<kunde-slug>-infra/hcloud-setup.md (lokales Operations-Doc mit verwendeten hcloud-Commands für Reproduzierbarkeit, gitignored bzw. in privates Repo)
  • Modify: intern/capabilities/hetzner/projects.md (Eintrag für neues Project mit ID, Bestand, Tokens-Status)

Approach:

  • App-Server: CCX23 (4 vCPU dedicated, 16 GB RAM, 80 GB NVMe), Location nbg1 oder fsn1 (Marvin entscheidet — beide DE, beide DSGVO-equivalent). Image: ubuntu-24.04. SSH-Key von Marvin-Laptop hinterlegen.
  • DB-Server: CCX13 (2 vCPU dedicated, 8 GB RAM, 80 GB NVMe), gleiche Location wie App-Server. Plus 50 GB Volume mit format=ext4, automount=true, attached an DB-Server.
  • Network:hcloud network av-<kunde>-net mit Range 10.0.0.0/16, Subnet 10.0.1.0/24 in der Location. Beide Server-Eth1 ans Network attachen. Compose-Service-Discovery läuft über lokales Bridge-Network im Compose, das Hetzner-Network dient nur Server-Server-Kommunikation (App → DB).
  • Firewall:hcloud firewall av-<kunde>-fw. Inbound: SSH 22 nur über Tailscale-Subnet (D-PLAN-13, Security-Audit-P1 F-07) — Marvin-IP-Whitelist explizit nicht wegen Remote-Workflow (Lissabon ab Mai 2026, Hotel-WLAN, mobile Hotspots, keine statische IP). Falls Tailscale nicht gewünscht: Wireguard-self-hosted als Alternative (gleiche Effekte, mehr Wartung). Cloud-Firewall blockt Port 22 vom Internet komplett. Inbound 6432 (pgbouncer) nur vom App-Server-Internal-IP. Sonst alles inbound deny. Outbound: alles allow (cloudflared braucht 443 outbound, App braucht Mistral + Object Storage).
  • Cloud-Init via user_data: Update OS, install docker, docker-compose-plugin, age, awscli (für S3-kompatibles Hetzner Object Storage), wal-g (Download Binary auf DB-Server), ufw (zusätzliche Defense-in-Depth-Schicht zur Cloud-Firewall).
  • Marvin-Operations: Hetzner Console für Erst-Anlage (lazyants MCP kann’s auch, Marvin entscheidet); für Folge-Operations ist lazyants-MCP der Default-Pfad.

Patterns to follow:

  • AWS-Pattern aus intern/capabilities/aws/av-becker.md für die Bestand-Doku-Struktur: 1 H1 + 1 Header-Tabelle (Project-ID, Region, Erstellt, Kunde, Projekt-Wikilinks) + H2 „Was drin ist (Stand )” mit Sub-H3s pro Service-Kategorie
  • Defense-in-Depth aus zugriffsmodell Phase-2-Beschreibung: OS-Layer (SSH/UFW) für Team-Zugriff + App-Layer für Kunden-Zugriff trennen

Test scenarios:

  • Happy path: ssh marvin@app-server und ssh marvin@db-server über Marvin-Key funktioniert; ping db-server-internal-IP vom App-Server erfolgreich; ping app-server von außen (nicht Marvin-IP) timeoutet
  • Edge case: Reboot des App-Servers — kommt sauber hoch, Docker startet auto, Firewall-Rules persistent
  • Error path: SSH-Login von nicht-Marvin-IP wird durch Cloud-Firewall geblockt vor SSH-Daemon

Verification:

  • hcloud server list zeigt beide Server running, beide in gleicher Location, beide am Network
  • hcloud firewall describe av-<kunde>-fw zeigt korrekte Inbound/Outbound-Rules
  • hcloud volume list zeigt 50 GB Volume attached an DB-Server, mounted
  • intern/capabilities/hetzner/av-<kunde-slug>.md hat den Bestand mit aktuellen IDs dokumentiert

Aktive Bauzeit: 60-90 Min Claude (hcloud-Calls + Doku) + 15-30 Min Marvin (SSH-Setup, Cloud-Firewall-IP-Verifizieren)


  • Unit 3: Hetzner Object Storage — Buckets mit Object-Lock + Lifecycle

Goal: Object-Storage-Buckets für (a) App-Daten und (b) Audit-Logs anlegen. Object-Lock Compliance-Mode bei Bucket-Erstellung aktivieren (irreversibel!). Lifecycle für 7-Jahre-Retention auf Audit-Bucket.

Requirements: R3

Dependencies: Unit 1 (Project + S3-Credentials), Lead-Briefing (PII-Klassifikation) für finale Object-Lock-Aktivierung

Files:

  • Modify: intern/capabilities/hetzner/storage.md (Bucket-Map mit Naming-Konvention + Retention-Rules + Region)
  • Create: ~/source/<kunde-slug>-infra/buckets-config.md (lokales Doc mit Bucket-Settings + Access-Keys-Speicherort)

Approach:

  • Schritt 0 — Test-Bucket: Erst ein Test-Bucket <kunde-slug>-test-objectlock mit Object-Lock + Retention 1 Day anlegen, ein File hochladen, prüfen dass Delete blockiert. Dann erst Prod-Bucket — Object-Lock ist nicht rückgängig zu machen.
  • Bucket 1 — <kunde-slug>-data (App-Daten + age-encrypted Backups): Versioning enabled, Public Access blockiert, Object Lock NICHT aktiviert (App muss Daten löschen können bei DSGVO-Art-17-Anfrage).
  • Bucket 2 — <kunde-slug>-audit (LLM-Audit + sonstige Audit-Logs): Versioning enabled, Public Access blockiert, Default-Retention 2555 Days (7 Jahre HGB). Object Lock Compliance-Mode: Bucket wird OHNE Object-Lock angelegt für die ersten ~Tage des Sprints; Object-Lock-Aktivierung erst NACH Lead-Briefing-Verifikation der PII-Klassifikation (D-PLAN-12). Wenn PII verifiziert ausgeschlossen ist: Compliance-Mode aktivieren. Wenn PII drin: erst Presidio-Filter in Unit 5 sicherstellen, dann aktivieren. (Hetzner-Quirk: Object-Lock-Aktivierung kann nicht nachträglich beim selben Bucket — wenn wir später aktivieren wollen, ist es ein neuer Bucket. Pragma: direkt mit Compliance-Mode anlegen, sobald PII-Klärung vorliegt, im Zweifel kurz warten.)
  • Werte-Double-Check vor Bucket-Erstellung (Security-Audit-P3 F-14): Retention-Days = 2555 (nicht 25550 = 70 Jahre!). Screenshot der Bucket-Konfig vor Commit, zweite Verifikation durch Claude oder einen menschlichen Reviewer.
  • Lifecycle auf <kunde-slug>-audit: Aktuelle Objekte → Expiration nach 2555d. Noncurrent-Versions: Expiration nach 365d. MultipartUpload-Abort nach 7d. Achtung: Lifecycle funktioniert nicht bei suspended Versioning — Versioning muss enabled bleiben.
  • Access-Keys mit Layered-Permission (Security-Audit-P0 F-03, D-PLAN-14): Pro Bucket drei separate S3-Keys: (a) <kunde-slug>-data-rw für App-Server (Read+Write auf data-Bucket), (b) <kunde-slug>-audit-write für App-Server (NUR s3:PutObject auf audit-Bucket, kein Read, kein Delete), (c) <kunde-slug>-audit-read-mfa für Marvin-IAM-User mit MFA-Schutz (Read-only auf audit-Bucket, nur für Audit-Recherche-Fälle). App bekommt nie Read-Key auf Audit-Bucket — selbst bei vollem App-Kompromiss kein Volltext-Vollzugriff. Audit-Bucket-S3-Key-Rotation alle 90 Tage als Cron.
  • Endpoint: https://<region>.your-objectstorage.com (z.B. https://nbg1.your-objectstorage.com). Region = gleiche wie Server.

Patterns to follow:

  • Audit-Log-Bucket-Pattern aus av-becker.md §Bedrock-Invocation-Logs: separater Bucket, Versioning enabled, Public Access blockiert, 2555d Retention, Schreib-only-Principal-Äquivalent
  • Naming-Konvention aus buckets.md: kebab-case mit Kunden-Slug-Prefix

Test scenarios:

  • Happy path: Test-File hochladen + lesen erfolgreich auf beiden Buckets
  • Edge case Object-Lock: PUT eines neuen Objects in <kunde-slug>-audit mit Retention-Header setzt Retention korrekt; nachträgliches DELETE schlägt fehl mit “InvalidRequest: Object is WORM protected”
  • Edge case Lifecycle: Versioning suspended testweise (im Test-Bucket!) → Lifecycle wirft Fehler oder wird ignoriert. Wiederherstellen.
  • Error path: PUT mit falschem Access-Key gibt 403 zurück

Verification:

  • aws s3 ls --endpoint-url https://<region>.your-objectstorage.com zeigt beide Buckets
  • Versioning + Object-Lock + Lifecycle via aws s3api get-bucket-*-Calls korrekt
  • intern/capabilities/hetzner/storage.md hat Bucket-Map mit IDs, Region, Settings dokumentiert

Aktive Bauzeit: 60-90 Min Claude (Bucket-Setup + Lifecycle-Konfig + Doku) + 15 Min Marvin (Access-Keys ins 1Password)


  • Unit 4: Postgres 16 + pgvector + pgbouncer + WAL-G-Backup-Stack

Goal: Postgres 16 mit pgvector 0.8.2 auf CCX13-Server installieren, mit Tuning-Werten für 2 vCPU / 8 GB / 50 GB Volume. pgbouncer als Session-Pooler davor. WAL-G für Continuous WAL-Archiving + pg_dump-Cron als 2. Layer + Hetzner Volume Snapshots als 3. Layer. Sonntäglicher Auto-DR-Test.

Requirements: R4, R5

Dependencies: Unit 2 (DB-Server existiert + Volume mounted), Unit 3 (Object Storage für WAL-G + pg_dump)

Files:

  • Create: ~/source/<kunde-slug>-infra/db-server/postgres.conf (Tuning-Werte)
  • Create: ~/source/<kunde-slug>-infra/db-server/pgbouncer.ini
  • Create: ~/source/<kunde-slug>-infra/db-server/walg-config.json (S3-Endpoint, Credentials, age-Pubkey für Stream-Encryption)
  • Create: ~/source/<kunde-slug>-infra/db-server/backup-cron.sh (pg_dump nightly + age-encrypt + rclone-upload)
  • Create: ~/source/<kunde-slug>-infra/db-server/dr-test.sh (Sonntäglicher Restore-Test auf temporären 2. Server)
  • Modify: intern/capabilities/hetzner/av-<kunde-slug>.md (§Compute / §DB mit installierten Versionen + Tuning-Werten)

Approach:

  • Postgres-Install: Ubuntu-24.04, apt install postgresql-16 postgresql-16-pgvector postgresql-contrib. Data-Dir auf Volume (/mnt/postgres/data). CREATE EXTENSION vector;.
  • Tuning für CCX13 (2 vCPU / 8 GB / 50 GB):
    • shared_buffers = 2GB (25% RAM)
    • effective_cache_size = 6GB (75% RAM, dedicated DB-Server)
    • work_mem = 32MB (Default für <50 Connections)
    • maintenance_work_mem = 512MB
    • max_connections = 50 (pgbouncer davor poolt)
    • random_page_cost = 1.1 (NVMe)
    • effective_io_concurrency = 200
    • wal_buffers = 16MB
    • max_wal_size = 4GB
    • checkpoint_completion_target = 0.9
    • archive_mode = on, archive_command = 'wal-g wal-push %p'
  • pgvector-Setup (optional je Use-Case): Extension für 1024-Dim Mistral-Embed bereit. App legt eigene Tabellen mit halfvec(1024) + HNSW-Index m=16, ef_construction=64 — nur aktivieren wenn Use-Case RAG braucht (Lead-Briefing-Klärung).
  • pg-boss-Setup (Stack-Mirror, D-PLAN-15): Schema-Migration via Drizzle in packages/db/migrations/. Eine Queue pro Pipeline-Stage analog Becker apps/worker/src/stages/. Singleton-Keys + UPSERT-Pattern für TOCTOU-Freiheit (siehe stage-idempotenz-pgboss-upsert). Läuft auf gleicher Postgres-Instanz, keine separate Queue-Infra nötig.
  • Multi-Tenant-Schema (D-PLAN-16): Drizzle-Migration 0001_multi_tenant_baseline legt tenants-Tabelle an + erweitert jede Domain-Tabelle um tenant_id (FK auf tenants.id, NOT NULL) + aktiviert RLS-Policy current_setting('app.tenant_id'). Pattern aus Becker packages/db/SCHEMA.md direkt übernehmen. Drizzle-Middleware in packages/db/middleware.ts setzt SET LOCAL app.tenant_id pro Request basierend auf Auth-Context. Initial-Tenants: <kunde-slug> (plus später Geschwister-Gesellschaften ohne Refactor).
  • audit_log-Tabelle (Stack-Mirror): Postgres-Tabelle für jede schreibende Operation, Spalten tenant_id, user_id, action, entity, entity_id, before (jsonb), after (jsonb), at (timestamptz). Drizzle-Trigger oder Application-Layer-Hook. Plattform-agnostisches Audit-Pattern aus Becker — funktioniert auch bei späteren Migrationen.
  • pgbouncer: Session-Pooling (Mode session für ORM-Kompatibilität, transaction wenn App-Code keine Cursor-Sessions hält), max_client_conn = 200, default_pool_size = 25. App verbindet auf Port 6432 statt direkt 5432.
  • WAL-G: Binary nach /usr/local/bin/wal-g. wal-g backup-push täglich 02:00 (Full Base Backup, gestreamt durch age -r <pubkeys> vor Upload). archive_command für Continuous WAL-Push. WALG_S3_PREFIX=s3://<kunde-slug>-data/walg/, AWS_ENDPOINT=https://<region>.your-objectstorage.com. age-Pubkey-Availability-Monitoring (Security-Audit-P2 F-10): Public-Keys nach /etc/postgresql/walg-recipients.txt mit chmod 644 owner=postgres. Sanity-Check-Cron alle 5 Min: echo test | age -r $(cat /etc/postgresql/walg-recipients.txt) > /dev/null + Better-Stack-Heartbeat. Notfall-Fallback: Pubkey-Copy auch auf App-Server.
  • pg_dump nightly: Cron 03:00, pg_dump | gzip | age -r <pubkeys> | aws s3 cp - s3://<kunde-slug>-data/pgdump/<date>.sql.gz.age --endpoint-url=....
  • Hetzner Volume Snapshot weekly: hcloud volume create-snapshot via lazyants-MCP, Cron auf Marvin-Laptop (oder kleine Lambda). Retention: 4 Snapshots kurz (letzte 4 Wochen) + 12 monatlich (letzte 12 Monate). Auto-Cleanup älterer Snapshots via hcloud volume snapshot delete im selben Cron-Schritt. Wichtig: Snapshots sind crash-konsistent, NICHT db-konsistent — taugen als 3. Notnagel-Layer, nicht als Primary-Backup.
  • Off-Site Copy: deferred zu 2. Industriekunde oder auf Kunden-CTO-Wunsch nachgezogen. Hetzner-Single-Region hat 99,9% SLA pro Region; WAL-G + pg_dump + Snapshot innerhalb einer Region decken bereits 3 unabhängige Failure-Modes ab. Cross-Region-rclone-Sync nbg1→hel1 (Strecke ca. 2€/Mo) wenn DR-Hardening explizit gefordert.
  • DR-Test (manuell + quartalsweise rotiert, Lean-MVP-konsistent): 1× manueller Full-Restore-Test vor Customer-Onboarding-Go-Live als Audit-Beweis. Danach quartalsweise Rotation mit allen 3 age-Recipients (Security-Audit-P2 F-11): Q1 Marvin-Daily-Key, Q2 Marvin-Recovery-Key (manuell aus Safe geholt), Q3 Kunden-CTO-Recovery-Key (mit Kunde dokumentiert — Audit-Beweis dass Multi-Recipient-Versprechen funktioniert), Q4 wieder Daily. Bei jedem Key-Add im Rotation-Zyklus: DR-Test-Runde mit neuem Key bevor alter entfernt wird. Skript für reproduzierbaren Restore: wal-g backup-fetch LATEST auf temporärer CCX13, pg_ctl start, Smoke-Queries (Tabellen-Count, neueste Row pro kritischer Tabelle), Server abschalten. Better-Stack-Heartbeat nach jedem Test. Sonntäglich-Auto-DR-Test deferred zu 2. Industriekunde oder >10 Live-Days (Production-Maturity-Pattern, nicht Lean-MVP).

Patterns to follow:

  • 3-Layer-Backup-Pattern (WAL-G + pg_dump + Snapshot) aus Best-Practices-Recherche
  • HGB-7-Jahre-Retention-Pattern aus av-becker.md §Bedrock-Invocation-Logs (auf Audit-Bucket aus Unit 3)
  • Idempotenz-Pattern aus stage-idempotenz-pgboss-upsert falls App-Schicht Pipeline-Jobs braucht (UQ-Index + atomare UPSERT)

Test scenarios:

  • Happy path: App-Server kann via pgbouncer auf DB verbinden; CREATE TABLE mit halfvec(1024) und HNSW-Index erfolgreich; INSERT + SELECT ... ORDER BY embedding <=> $1 LIMIT 10 liefert Resultate
  • Happy path: WAL-G backup-push schreibt nach Object Storage erfolgreich; wal-g backup-list zeigt Backup; Manual-Restore auf 2. Server wal-g backup-fetch LATEST stellt DB wieder her, Smoke-Query liefert Daten
  • Edge case: DB-Server Reboot — Postgres + pgbouncer kommen sauber hoch (systemd-units enabled); App reconnectet automatisch über pgbouncer
  • Edge case: Volume vollläuft (Test mit künstlicher Last) — Postgres logged Warning, pg_dump läuft trotzdem (Object Storage hat keinen Volume-Bezug)
  • Error path: WAL-G mit falschen S3-Credentials → exit code != 0, archive_command fails, Postgres archive backlog wächst; Better-Stack-Alarm
  • Error path: age-Pubkey-Mismatch im DR-Test → restore schlägt fehl, dokumentierte Recovery-Anleitung greift (Marvin-Recovery-Key)
  • Integration: App schreibt einen Test-Embedding nach Postgres, danach Server-Kill, danach DR-Restore — Embedding noch da

Verification:

  • psql -h db-server -p 6432 -U app_user Connect erfolgreich; SELECT extversion FROM pg_extension WHERE extname='vector';0.8.2
  • WAL-G-Backup älter als 30 Min sichtbar in Object Storage
  • pg_dump Cron-Log zeigt erfolgreichen Run der letzten Nacht
  • DR-Test-Heartbeat in Better Stack grün
  • intern/capabilities/hetzner/av-<kunde-slug>.md §DB hat alle Versionen + Tuning + Backup-Setup dokumentiert

Aktive Bauzeit: 4-6h Claude (Setup + Tuning + WAL-G-Config + DR-Test-Skript + Doku) + 30 Min Marvin (age-Keys generieren, in 1Password ablegen + Recovery-Key an Kunde-CTO übergeben)


  • Unit 5: App-Monorepo (Next.js + Worker + Drizzle) + Vercel AI SDK + Mistral + Audit-Hook

Goal: Becker-Stack-Mirror auf Hetzner (D-PLAN-15): pnpm + Turborepo Monorepo mit apps/web (Next.js 15), apps/worker (Node + pg-boss-Subscriber), packages/{db,llm,shared,ui}. Drizzle-Schema mit Multi-Tenant-RLS (D-PLAN-16). Vercel AI SDK + Mistral als Default-Provider, mit Audit-Hook nach Hetzner Object Storage. Mistral DPA-Workflow gestartet. PII-Filter als Pflicht-Middleware (D-PLAN-12).

Requirements: R6, R7, R8, R14

Dependencies: Unit 2 (App-Server), Unit 3 (Object Storage Audit-Bucket), Unit 4 (Postgres + Drizzle-Migrations laufbereit)

Files:

  • Create: ~/source/<kunde-slug>/package.json + pnpm-workspace.yaml + turbo.json (Monorepo-Root analog bas-twin/)
  • Create: ~/source/<kunde-slug>/apps/web/ — Next.js 15 App-Router, Better Auth + CF-Access-JWT-Middleware, Tailwind + shadcn/ui
  • Create: ~/source/<kunde-slug>/apps/worker/ — Node-Prozess mit pg-boss-Subscriber, Pipeline-Stages je nach Use-Case (Skelett analog bas-twin/apps/worker/src/stages/)
  • Create: ~/source/<kunde-slug>/packages/db/ — Drizzle-Schema mit tenant_id-Spalte jede Tabelle + RLS-Migrations + pg-boss-Schema + audit_log-Tabelle
  • Create: ~/source/<kunde-slug>/packages/llm/ — Vercel AI SDK Wrapper mit Mistral als Default, PII-Filter-Middleware (Microsoft Presidio via REST oder Regex-Library wie compromise/pii-detect), age-encrypted Audit-Logger zu Object Storage
  • Create: ~/source/<kunde-slug>/packages/shared/ — Zod-Schemas, Types
  • Create: ~/source/<kunde-slug>/packages/ui/ — shadcn-Komponenten + Kunden-Branding
  • Create: ~/source/<kunde-slug>/docker-compose.yml (web + worker + cloudflared-Sidecar + optional ClamAV + optional Open WebUI, internes Bridge-Network)
  • Create: ~/source/<kunde-slug>/Dockerfile.web + Dockerfile.worker (Node-20 Base, Multi-Stage-Build, Non-Root)
  • Create: ~/source/<kunde-slug>/.env.example (alle Env-Vars dokumentiert, gitignored real .env)
  • Create: ~/source/<kunde-slug>/secrets/.gitkeep (secrets/-Ordner für tunnel-token, mistral-api-key, s3-credentials, age-pubkeys)
  • Create: ~/source/<kunde-slug>/ARCHITECTURE.md + README.md + CONTRIBUTING.md + conventions.md (Skeleton analog bas-twin/)
  • Modify: intern/capabilities/hetzner/av-<kunde-slug>.md (§Compute mit Container-Versionen, §LLM mit Modell-Liste + Audit-Pfad, §Stack mit Verweis auf Becker-Mirror)
  • Create: inbox/2026-XX-YY-mistral-dpa-anfrage.md (Inbox-Eintrag für DPA-Anfrage an Mistral via Trust Center)

Approach:

  • Vercel AI SDK statt LiteLLM (D-PLAN-15 Stack-Mirror): TypeScript-natives Provider-Adapter-SDK. pnpm add ai @ai-sdk/mistral. Aufruf: import { mistral } from "@ai-sdk/mistral"; await generateText({ model: mistral("mistral-medium-latest"), messages: [...] }). Provider-Wechsel zu Bedrock/Anthropic/Self-Host = Adapter-Import-Tausch + eine Zeile. Keine Proxy-Container nötig, SDK läuft im Web- oder Worker-Prozess direkt.
  • Default-Modell mistral-medium-latest (D-PLAN-3) — keine Routing-Logik in 1. Iteration. Modell-Splitting Small/Medium/Large kommt mit Use-Case-spezifischem App-Code nach Lead-Briefing. Provider-Adapter ist da, also Wechsel zu Claude via Bedrock-EU bleibt offen falls Use-Case höhere Reasoning-Tiefe braucht (CLOUD-Act-Trade-Off mit Kunde besprechen).
  • Audit-Hook in packages/llm/ (Layered Encryption, Security-Audit-P0 F-03 + D-PLAN-14): Wrapper-Function um generateText/streamText/generateObject die VOR Return den vollen Request + Response durch age -r <pubkeys> streamt und als JSONL.gz.age in Hetzner Object Storage schreibt. So sind Audit-Files doppelt geschützt: (a) Hetzner Object-Storage-Encryption-at-Rest + Object-Lock + Bucket-Policy, (b) zusätzliche age-Encryption mit Multi-Recipient damit selbst Bucket-Read-Key-Leak keine Volltexte preisgibt. Format: audit/<kunde>/<yyyy>/<mm>/<dd>/<hh>.jsonl.gz.age. Standard-Felder (Token-Counts + Latenz + Model + User-ID aus Auth-Context) plus Volltext-Messages + Response — alles im verschlüsselten Payload. Hourly-Bucket-Logic via Background-Buffer (in-memory queue, flushed alle 60s oder bei Hour-Roll) damit nicht pro Call ein S3-PUT.
  • PII-Filter DEFAULT-ON in packages/llm/middleware/pii.ts (D-PLAN-12, Security-Audit-P0 F-02): Pflicht-Middleware vor jedem LLM-Call, auch wenn Lead-Briefing PII verneint. TypeScript-Optionen: (a) Microsoft Presidio via REST — Container neben App, NPM-Wrapper presidio-client. (b) Reine JS-Library wie compromise + Custom-Recognizer für IBAN/Email/Telefon. (a) ist stärker (NER-basiert), (b) ist simpler. Default-Empfehlung: Presidio-Container weil Becker-Stack-Mirror-Konsistenz und stärker. Grund für Pflicht: Object-Lock-Compliance-Mode löscht nicht — unmaskierte PII landet 7 Jahre im Audit-Bucket, DSGVO-Art-17-Konflikt strukturell unauflösbar. Konfiguration: deutsche + englische Recognizer (Email, IBAN, Telefon, Adressen, IP wenn nicht audit-pflichtig). Verifizierung nach Lead-Briefing ob spezifische Industrie-Patterns ergänzt werden müssen.
  • Multi-Tenant in Drizzle-Schema (D-PLAN-16): Jede Tabelle in packages/db/schema/ hat tenantId als erste Spalte mit FK auf tenants. RLS-Policy via Drizzle-Migration aktivieren. Auth-Context-Middleware setzt tenant_id automatisch in Query-Builder (analog Becker packages/db/middleware.ts). Tenants initial: nur <kunde-slug>. Drizzle-Idiom direkt aus Becker-Repo übernehmen — packages/db/SCHEMA.md aus bas-twin/ als Vorlage.
  • audit_log-Tabelle in Postgres analog Becker: jede schreibende Operation hinterlässt Audit-Trail mit tenantId, userId, action, entity, entityId, before, after, at. Plattform-agnostisches Pattern (funktioniert auch wenn Cloud-Anbieter wechselt).
  • pg-boss-Setup für Background-Jobs (Stack-Mirror): packages/db/pgboss.ts initialisiert pg-boss, apps/worker/src/stages/ enthält Stage-Handler. Pattern-File stage-idempotenz-pgboss-upsert gilt 1:1.
  • ClamAV-Sidecar bei File-Uploads: wenn der Use-Case Datei-Uploads hat (verifizieren mit Lead-Briefing), ClamAV als Compose-Service. Worker scant jeden Upload via TCP-Socket gegen ClamAV-Container bevor Postgres-Insert. Aus Becker-Pattern direkt übernehmen.
  • Open WebUI als optionaler Service (D-PLAN-17): wenn Use-Case Multi-User-Chat braucht, zusätzlicher Compose-Service ghcr.io/open-webui/open-webui:main mit Postgres-Backend (NICHT SQLite!) und Vercel-AI-SDK-Proxy davor. Pattern aus open-webui-fargate-bedrock, adaptiert auf Hetzner+Mistral. Eigene Sub-Domain z.B. <kunde-slug>-chat.agenticventures.de via zweiter Tunnel-Ingress-Rule.
  • Mistral-API-Key: Über La-Plateforme-Console generieren, in 1Password, ins Compose-secrets/-Volume mounten, App liest aus MISTRAL_API_KEY Env-Var.
  • Mistral DPA-Workflow: Per Trust Center (https://trust.mistral.ai/) DPA anfragen. Standardvorlage, unterschreiben, abheften in assets/firma/compliance/ und referenzieren im Kunden-AVV als Sub-Prozessor.
  • Compose-Network: Internes Bridge-Network private, App-Service exposed nur intern Port 8000, kein ports:-Mapping nach außen. cloudflared (Unit 6) erreicht App über http://app:8000.
  • Database-Connection: App connectet auf pgbouncer-Port 6432 am DB-Server. Connection-String über Env-Var DATABASE_URL. SQLAlchemy oder asyncpg.

Patterns to follow:

  • LiteLLM-Sidecar-Konfig aus open-webui-fargate-bedrock — Mistral-Provider-Prefix mistral/... statt bedrock/eu.anthropic...
  • Pydantic-Settings für Env-Var-Validation (aus mcp-vf-hosted gelernt: sprechende Error-Messages bei fehlenden Env-Vars)
  • Vendor-Neutralitäts-Checkliste aus runner-architektur: generische description-Felder in Tool-Definitions, keine Mistral-spezifischen Idiomatik in System-Prompts

Test scenarios:

  • Happy path: litellm.completion(model="mistral/mistral-medium-latest", messages=[{"role":"user","content":"Hallo"}]) liefert Response; Audit-Log-File entsteht im Object Storage unter dem Stunden-Pattern
  • Happy path: Tool-Use mit JSON-Schema funktioniert (tools=[...], Mistral antwortet mit tool_calls)
  • Happy path: App liest DB via pgbouncer; Insert + Select erfolgreich
  • Edge case: Audit-Upload schlägt fehl (Object Storage Down) — Default-LiteLLM-Verhalten: Hook ist async, blockt nicht; Better-Stack-Alarm wenn Fehlerquote >1%
  • Edge case: Mistral 429 Rate Limit → LiteLLM retry mit Backoff; bei dauerhaft 429: App liefert 503 + Alarm
  • Error path: Falscher Mistral-API-Key → 401, App liefert 500 + Better-Stack-Alarm
  • Error path: PII-Filter triggert auf Test-Input mit Email-Adresse → Email maskiert oder Request abgelehnt (je nach Use-Case-Wahl)
  • Integration: App-Container restart → DB-Connection rebuilt, Audit-Hook reattacht, Mistral-Call erfolgreich nach Boot
  • Integration: 100 parallele LLM-Calls — pgbouncer + Audit-Async-Upload halten Stand, keine Connection-Exhaustion

Verification:

  • App-Container hochgefahren via docker compose up app, Health-Check GET /health liefert 200
  • Audit-Files entstehen stündlich in s3://<kunde-slug>-audit/audit/<kunde>/<yyyy>/<mm>/<dd>/<hh>.jsonl.gz
  • Object-Lock auf neuen Files greift: aws s3api delete-object → “InvalidRequest: WORM protected”
  • Mistral-Tool-Use funktioniert: Test-Skript mit definiertem Tool liefert tool_calls im Response
  • Mistral DPA-Anfrage abgesendet (Inbox-Eintrag dokumentiert)

Aktive Bauzeit: 6-8h Claude (App-Skeleton + LiteLLM-Integration + Audit-Hook + Doku) + 30 Min Marvin (Mistral-API-Key, DPA-Anfrage)


  • Unit 6: Cloudflare-Tunnel + Access — Public-Eingang einrichten

Goal: Cloudflare-Tunnel via CF-API anlegen, cloudflared-Sidecar in Compose, Cloudflare Access Application mit AUD-Tag und Identity-Provider, JWT-Validation-Middleware in der App. Service-Token für M2M-Zugriff parallel.

Requirements: R9, R10

Dependencies: Unit 1 (CF-Account + Zone), Unit 5 (App ist auf app:8000 erreichbar)

Files:

  • Create: ~/source/<kunde-slug>-app/src/auth/cf_access_middleware.py (JWT-Validation gegen JWKS-Endpoint)
  • Modify: ~/source/<kunde-slug>-app/docker-compose.yml (cloudflared-Service hinzu)
  • Modify: ~/source/<kunde-slug>-app/.env.example (CF_ACCESS_AUD, CF_ACCESS_TEAM_DOMAIN)
  • Create: ~/source/<kunde-slug>-app/secrets/tunnel-token (gitignored)
  • Modify: intern/capabilities/hetzner/av-<kunde-slug>.md (§Edge mit Tunnel-ID + DNS-Record + AUD-Tag)
  • Modify: Kunden-AVV: Cloudflare als Sub-Prozessor eintragen (Pflicht aus cloudflare-dsgvo)

Approach:

  • Tunnel anlegen via CF-API — gleiches Pre-Run-Pattern wie aus mcp-hosting-fargate-tunnel §Pre-Run-Setup:

    1. POST /accounts/$CF_ACCOUNT_ID/cfd_tunnel mit {"name":"<kunde-slug>-app","config_src":"cloudflare"} → liefert TUNNEL_ID + TUNNEL_TOKEN
    2. PUT /accounts/$CF_ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID/configurations mit Ingress-Rules: <kunde-slug>.agenticventures.de → http://app:8000, service: http_status:404 als Fallback
    3. POST /zones/$CF_ZONE_ID/dns_records mit CNAME <kunde-slug><TUNNEL_ID>.cfargotunnel.com, proxied: true
    4. TUNNEL_TOKEN in ~/source/<kunde-slug>-app/secrets/tunnel-token-File ablegen (gitignored)
  • cloudflared-Sidecar in Compose: Image cloudflare/cloudflared:2026.4 (Version pinnen, nicht :latest!), command: tunnel --no-autoupdate run, Env TUNNEL_TOKEN_FILE=/run/secrets/tunnel-token plus TUNNEL_METRICS=0.0.0.0:2000. Healthcheck via cloudflared tunnel ready (Exit 0 wenn ≥1 aktive Connection).

  • Cloudflare Access Application für die App-Domain:

    • AUD-Tag generieren (Application Audience UUID).
    • Identity-Provider: Marvin entscheidet — Google Workspace, Azure AD, OIDC gegen Kunden-IdP, oder One-Time-PIN-Email als Fallback. Default-Empfehlung: gegen Kunden-IdP (Pflicht-Klärung im Lead-Briefing).
    • Policy: Allow group=<kunde-slug>-internal, deny rest.
    • Service-Token (optional, deferred): für etwaige Machine-zu-Machine-Calls aus weiteren Systemen (z.B. Marvin-Operations-Skripte). In 1. Iteration NICHT aktiviert — kommt bei konkretem M2M-Use-Case. Middleware-Code ist eh dual-fähig (User-JWT + Service-Token validieren gegen dieselbe Logik), nur Cloudflare-Konfig + Tests werden später ergänzt.
  • Two-Layer-Auth (Stack-Mirror, D-PLAN-15):

    • Cloudflare Access als äußere Schicht (Tunnel-Edge) — kein Request kommt zur App ohne gültigen CF-Access-Login oder Service-Token. Identity-Provider gegen Kunden-IdP.
    • Better Auth als innere Schicht (App-Level) — Application-Sessions, RBAC, User-Profile, Role-Permissions in Postgres. Becker-Pattern direkt übernehmen.
    • Defense-in-depth: CF-Access schützt vor Internet-Zugriff insgesamt, Better Auth steuert was authentifizierte User innerhalb der App dürfen. Die JWT-Validation-Middleware unten ist die Brücke — sie verifiziert CF-Access-JWT und legt User-Identity in Auth-Context, Better Auth setzt darauf auf.
  • JWT-Validation-Middleware in App (Security-Audit-P1 F-04 verschärft):

    • JWKS-URL https://<team>.cloudflareaccess.com/cdn-cgi/access/certs cachen (~1h TTL via PyJWKClient).
    • Pro Request: Cf-Access-Jwt-Assertion-Header lesen, mit kid JWKS-Key matchen, Signatur prüfen, aud-Claim gegen Application-AUD matchen (sonst kann jeder andere CF-Access-Tenant einreichen), iss gegen https://<team>.cloudflareaccess.com, exp nicht abgelaufen, nbf-Check mit max 30s Clock-Skew-Toleranz.
    • Replay-Schutz: jti-Claim-Tracking via Postgres-Tabelle jwt_replay_cache (Spalten jti, exp_at), Eintrag pro neuer JTI; bei wiederholter JTI → 403. Cleanup-Cron löscht Einträge > EXP+5min.
    • JWKS-Force-Refresh-Endpoint in App (admin-only via Tailscale): triggert sofortiges Re-Fetch der JWKS-URL ohne auf 1h-TTL zu warten — wichtig für Notfall-Key-Rotation durch Cloudflare.
    • Failure-Logging: alle JWT-Validation-Failures mit Source-IP + User-Agent + Failure-Reason in Better Stack. Rate-Limit auf wiederholte Failures pro IP (anti-Brute-Force).
    • Service-Token-Pfad: Cf-Access-Client-Id + Cf-Access-Client-Secret-Header werden von Cloudflare validiert und in dasselbe JWT umgesetzt — gleiche Middleware-Logik.
    • Bei ungültigem JWT: 403 zurück, nicht 401 (App ist hinter Tunnel — User kommt nur mit gültigem CF-Access-Login durch).
  • Cloudflare-Subprozessor-Eintrag im Kunden-AVV: Pflicht laut cloudflare-dsgvo §Pflichten. Liste: Cloudflare Inc. (US), CDN + Tunnel + Access, DPF-zertifiziert.

Patterns to follow:

  • Tunnel-API-Pattern aus mcp-hosting-fargate-tunnel §Pre-Run-Setup — gleicher Workflow, nur Origin ist http://app:8000 statt ECS-Task
  • config_src: cloudflare (D-PLAN-9) statt config.yml-Mount
  • pyjwt[crypto] + PyJWKClient für JWT-Validation
  • Subprozessor-Eintrag-Pattern aus cloudflare-dsgvo §Pflicht-To-Dos

Test scenarios:

  • Happy path: curl https://<kunde-slug>.agenticventures.de/health mit gültigem Login (Browser-Test, CF-Access-Cookie) liefert App-200
  • Happy path: Service-Token-Call mit CF-Access-Client-Id + CF-Access-Client-Secret-Header → App-200 ohne User-Login
  • Edge case: cloudflared-Container restart → Tunnel reconnected in <30s, App nicht betroffen
  • Edge case: Token rotated → alter Token bleibt aktiv bis Container-Restart, kein Service-Disruption (Pattern aus Fargate-Tunnel-Lessons)
  • Error path: Request ohne JWT → 403 von Cloudflare (kommt nicht bis App durch); Request mit falschem AUD → 403 von App-Middleware
  • Error path: Falscher Tunnel-Token im Secret → cloudflared zeigt unauthenticated in Logs, Better-Stack-Alarm
  • Error path: 522 Connection Timed Out → DNS-Record fehlt oder zeigt falsch; 530 Origin DNS Error → Tunnel-ID stimmt nicht (gleiche Cheatsheet wie Fargate-Tunnel)
  • Integration: App-Container restart während User-Session → cloudflared bleibt connected, User bekommt 502 für laufende Requests, danach wieder grün

Verification:

  • Cloudflare Zero-Trust-Dashboard zeigt Tunnel <kunde-slug>-app mit 4 aktiven Connections
  • DNS-Lookup <kunde-slug>.agenticventures.de zeigt CF-Anycast-IPs (proxied=true)
  • App-Logs zeigen JWT-Validation-Success für Browser-Login + Service-Token-Calls
  • AUD-Mismatch-Test gibt 403 zurück

Aktive Bauzeit: 3-5h Claude (Tunnel-Anlage + Sidecar + Middleware + Doku) + 30-60 Min Marvin (Identity-Provider mit Kunden-IT abstimmen, DNS-Records bestätigen)


  • Unit 7: Operations — Better Stack Log-Drain + Cost-Alarme + Sub-Prozessor-Pflege

Goal: Better Stack Account anlegen, Compose-Logging-Driver an Better Stack koppeln, Hetzner Cost-Limit + Notifications, Better-Stack-Heartbeats für DR-Test + Backup-Status. Better Stack als neuer Sub-Prozessor in Subprozessor-Liste.

Requirements: R11, R13

Dependencies: Unit 2-6 (alle Services existieren und produzieren Logs)

Files:

  • Modify: ~/source/<kunde-slug>-app/docker-compose.yml (Logging-Driver json-file mit max-size: 10m, max-file: 3 plus Better-Stack-Vector als zusätzlicher Service ODER Better-Stack-direkter Log-Drain via Syslog)
  • Create: ~/source/<kunde-slug>-app/monitoring/heartbeats.md (Liste aller Heartbeats: WAL-G-Backup, pg_dump, DR-Test, App-Health, cloudflared-Health)
  • Modify: intern/capabilities/hetzner/av-<kunde-slug>.md (§Monitoring mit Better-Stack-Endpoint + Alarm-Liste)
  • Modify: Kunden-AVV: Better Stack als Sub-Prozessor eintragen
  • Modify: intern/firma/stack.md (Better Stack als neues Tool eintragen mit AVV-Status)

Approach:

  • Better Stack Account: Marvin legt an (Free-Tier wahrscheinlich ausreichend für 1 Kunde, Pro ab 25€/Mo bei größerem Volumen). EU-Hosting prüfen + AVV abklären. Sources im Dashboard: 1× “App Logs”, 1× “DB Logs”, 1× “Cloudflared Logs”, 1× “WAL-G Logs”.
  • Log-Forwarding: Compose-Service mit vector oder fluent-bit als Sidecar der Docker-Logs aus /var/run/docker.sock liest + an Better-Stack-Endpoint schickt. Alternativ: Better-Stack-eigener Agent auf VM.
  • Cost-Limit Hetzner: Hetzner Cloud Console → Project → Settings → Cost Limit. Soft-Limit 80% Schwelle → Email-Alarm. Hard-Limit 100% Schwelle → Server-Stop-Optionale. Für Industriekunde ab ~150 €/Mo Budget, Soft bei 120 €, Hard bei 180 €.
  • Better-Stack-Heartbeats (reduziert, Lean-MVP):
    • “backup-success” — kombinierter Heartbeat bei erfolgreichem WAL-G + pg_dump nightly (beide müssen klappen, sonst kein Heartbeat); Alarm wenn 25h nicht eingegangen
    • “app-uptime” — HTTP-Check auf https://<kunde-slug>.agenticventures.de/health alle 60s, Multiregion (EU+US), Alarm bei 3 Fails
    • Deferred zu 2. Industriekunde: separate Heartbeats pro Layer (Volume-Snapshot, DR-Test, age-Sanity-Check-Cron). 1-Kunde-MVP braucht das nicht — alarm-müdigkeit vermeiden, ein konsolidierter Backup-Heartbeat reicht.
  • PII-Filter im Log-Drain (Security-Audit-P2 F-13): App-Logs können Stack-Traces + Request-Payloads enthalten die unbeabsichtigt PII leaken. Vector oder Fluent-Bit als Compose-Service mit Pre-Forwarding-Transform: bekannte PII-Patterns (Email, IBAN, Telefon, IP-Adressen wenn nicht Audit-Pflicht) maskieren bevor an Better Stack. Maskierungs-Pattern in monitoring/log-redaction.yaml dokumentiert für Audit-Beweis.
  • Cloudflare AI Gateway als zukünftige Erweiterung (siehe cloudflare-capability-map) für Cost-Cap auf Mistral-Calls — nicht in 1. Iteration, im Verlauf vermerken.
  • Sub-Prozessor-Eintrag (Coherence F5): Kunden-AVV-Update mit Better Stack als neuer Sub-Prozessor läuft parallel zu Better-Stack-Aktivierung in dieser Unit — nicht erst in Unit 8. AVV-Anhang oder extern/shared/<kunde>/avv/sub-prozessoren.md: Hetzner Online GmbH (DE), Mistral AI SAS (FR), Cloudflare Inc. (US, DPF), Better Stack (Region + AVV-Status). Wenn Better-Stack-AVV nicht rechtzeitig: temporär json-file-Logging only, Better Stack post-Sprint nachziehen.

Patterns to follow:

  • Cost-Alarm-Pattern aus av-becker.md §Cost-Alarm (110 USD-Schwelle, 80%/100% Actual + 100% Forecasted)
  • Heartbeat-vs-Push-Pattern: Push für „Job hat geklappt”, Pull für „Service ist erreichbar”

Test scenarios:

  • Happy path: Log aus App-Container erscheint in Better Stack innerhalb 30s
  • Happy path: WAL-G-Heartbeat geht jeden Morgen ein; Better-Stack-Dashboard zeigt “Last Heartbeat: X min ago” grün
  • Edge case: Better Stack Endpoint nicht erreichbar — Compose-Logging puffert lokal in json-file-Driver (max-size 10m), kein App-Verlust
  • Error path: WAL-G-Cron schlägt fehl (z.B. Object Storage Down) → Heartbeat fehlt → Better-Stack-Alarm an hello@marvinkuehlmann.com nach 25h
  • Error path: Hetzner-Kosten überschreiten Soft-Limit → Email an hello@; bei Hard-Limit (wenn aktiviert) Server gestoppt
  • Integration: Künstlich App auf 500 setzen → Uptime-Monitoring registriert nach 3 Fails (3 Min) → Alarm

Verification:

  • Better Stack Dashboard zeigt alle 4 Source-Streams aktiv
  • 3 Heartbeats grün (wal-g, pg-dump, dr-test) — initial direkt nach Setup einmal manuell triggern damit Status nicht „Pending” ist
  • Uptime-Monitor <kunde-slug>.agenticventures.de zeigt 100% Uptime nach 24h
  • Hetzner Cost-Alert konfiguriert, Test-Threshold mal künstlich überschreiten + Email-Erhalt prüfen
  • Sub-Prozessor-Liste hat alle 4 Anbieter mit AVV-Status

Aktive Bauzeit: 3-4h Claude (Vector-Sidecar + Heartbeat-Hooks + Doku) + 30-60 Min Marvin (Better-Stack-Account + AVV abklären + Cost-Limit setzen)


  • Unit 8: Compliance-Onboarding + Lessons-Learned-Capture

Goal: Alle Compliance-Schritte für DSGVO-strengen Industriekunden durchgehen (8-Punkt-Checkliste), EU-AI-Act-Risikoklassifizierung, Hosting-Konzept fürs Angebot, age-Encryption-Keys an Kunde-CTO übergeben, lessons learned ins Vault als Pattern-File. Capability-Doku finalisieren.

Requirements: R14, R15, R16, R17

Dependencies: Unit 1-7 abgeschlossen (Stack steht + dokumentiert)

Files:

  • Create: extern/outbound/<kunde-slug>/2026-XX-YY-hosting-konzept.md (Hosting-Architektur-Doku für den Kunden mit Datenfluss-Diagramm, Sub-Prozessor-Liste, DSGVO-Argumentation)
  • Create: extern/outbound/<kunde-slug>/2026-XX-YY-eu-ai-act-risikoklassifikation.md (Risk-Assessment laut eu-ai-act-pflichten)
  • Create: extern/shared/<kunde-slug>/avv/sub-prozessoren.md (Hetzner + Mistral + Cloudflare + Better Stack mit AVV-Status)
  • Modify: Kunden-AVV (Pflicht-Klauseln aus anthropic-datenschutz §8-Punkt-Checkliste)
  • Create: intern/wissen/prozesse/hetzner-lean-customer-stack.md (Pattern-File analog mcp-hosting-fargate-tunnel.md — Architektur, Pre-Run-Setup, Cost-Modell, Stolperer-Cheatsheet, Lessons aus dem Bau)
  • Modify: intern/capabilities/hetzner/_index.md (Status auf „live mit erstem Industriekunden” + Cross-Ref auf Pattern-File)
  • Modify: intern/firma/produkt-bundle.md (Branchen-Templates-Tabelle: Industrie-Eintrag konkretisieren mit dem nun realen Stack)
  • Modify: _index §Verlauf (Abschluss-Eintrag mit allen Phase-Ergebnissen)

Approach:

  • DSGVO-Pflicht-Checkliste (8 Punkte aus anthropic-datenschutz):
    1. DPA mit Kunde unterschrieben (Auftragsverarbeitungsvertrag UG i.G. ↔ Kunde)
    2. Sub-Processor-Liste an Kunde übergeben (Hetzner, Mistral, Cloudflare, Better Stack)
    3. DPA mit jedem Sub-Prozessor (Hetzner-AVV + Mistral-DPA via Trust Center + Cloudflare-DPF + Better-Stack-AVV)
    4. Datenstandort mit Kunde abgestimmt + dokumentiert (alle EU, kein US außer Cloudflare-Edge unter DPF)
    5. VVT-Eintrag beim Kunden (Verzeichnis Verarbeitungstätigkeiten)
    6. TOMs (Technische Organisatorische Maßnahmen) — Hetzner-TOMs als Dokument, plus eigene TOMs (Verschlüsselung, Zugriffsregeln, Backup-Strategie)
    7. Datenminimierung (kein PII in Prompts wenn vermeidbar — Use-Case-spezifisch im App-Design)
    8. PII-Filter (Presidio o.ä. wenn Use-Case PII enthält)
  • EU-AI-Act-Risikoklassifizierung laut eu-ai-act-pflichten:
    • Decision-Tree durchgehen (Annex I + III + Profiling-Falle + Vier-Ausnahmen-Art. 6 Abs. 3)
    • Industrie-B2B-Vertrieb = strukturell NICHT Hochrisiko (kein Profiling natürlicher Personen)
    • Anbieter-vs-Deployer-Pflichten-Matrix dokumentieren
    • KI-Kompetenz-Schulungs-Modul anbieten (5 Bausteine im Angebot)
    • GPAI-Anbieter (Mistral) als Auftragsverarbeiter im Hosting-Konzept dokumentieren
  • age-Encryption-Multi-Recipient-Übergabe (Security-Audit-P0 F-01 + Coherence F6):
    • Recipient 1: Marvin-Laptop-Public-Key (Daily Operations)
    • Recipient 2: Marvin-Recovery-Public-Key (offline, im Safe der UG)
    • Recipient 3: Kunden-CTO-Public-Key (übergeben mit schriftlicher Anweisung „nur bei Marvin-Ausfall, Vier-Augen-Prinzip Pflicht”)
    • Übergabe-Protokoll konkret: schriftlich, digital signiert von Marvin + Kunden-CTO. Drei Kopien: (a) Kunde-CTO-Email (verschlüsselt), (b) extern/shared/<kunde-slug>/keys/uebergabe-protokoll-YYYY-MM-DD.md im Vault, (c) Marvin-1Password unter Audit-Trail. Klausel: „Recovery-Key darf nur unter Vier-Augen-Prinzip verwendet werden, jeder Use ist Marvin zu melden binnen 24h.”
    • Fallback bei Kunden-Schlüssel-Verlust: Manuelle Eskalation zu Marvin-Recovery-Safe (nicht automatisch — Sicherheits-Feature). Dokumentierte Recovery-Anleitung im Pattern-File-Skeleton.
  • age-Key-Lifecycle-Policy (Security-Audit-P0 F-01, D-PLAN-8 verschärft):
    • Rotation alle 12 Monate als Default + sofort bei (a) Mitarbeiter-Austritt, (b) vermuteter Kompromittierung eines Recipient-Devices, (c) Verlust eines Recipient-Devices.
    • Add-then-remove-Pattern: neuen Key als zusätzlicher Recipient ergänzen, 30 Tage Übergang, dann alten Key aus Recipient-Liste entfernen. Bestehende Backups bleiben mit altem Key entschlüsselbar (re-encrypt nur bei expliziter Compromise-Evidence).
    • Compromise-Response-Playbook: 1) sofort neuen Key generieren + als Recipient ergänzen, 2) alle neuen Backups gegen alten + neuen Key encrypten (Doppel-Recipient für Übergang), 3) bestehende WAL-G-Snapshots prüfen ob betroffen, 4) nach 30 Tagen alten Key entfernen, 5) Kunde-CTO + Marvin-Recovery informieren, 6) Incident-Doc in extern/shared/<kunde-slug>/incidents/.
    • Audit-Log aller age-Decrypt-Operations in Better Stack mit Operator-ID + Reason + Timestamp + 7y-Retention. Decrypts laufen nur über dokumentierte Skripte die loggen (kein Ad-hoc-CLI-Decrypt ohne Log-Eintrag).
  • Hosting-Konzept-Dokument an Kunde: Architektur-Diagramm aus diesem Plan adaptieren, alle drei LLM-Optionen aus dem ADR transparent zeigen (Mistral default, Self-Hosted-LLM-Eskalation, Bedrock-Hybrid bei Claude-Wunsch), Sub-Prozessor-Liste, Datenfluss, TOMs.
  • Pattern-File-Skeleton (Lean, scope-7): intern/wissen/prozesse/hetzner-lean-customer-stack.md als Skeleton anlegen mit Architektur-Mermaid + Cost-Modell + Sub-Prozessor-Liste + Cross-Refs auf die ADRs. Stolperer-Cheatsheet + Lessons-aus-Bau kommen NICHT in 1. Iteration, sondern als Folge-Iteration nach 1-2 Wochen Real-Run — Pattern-Files brauchen echten Bau-Schmerz, der erst beim Live-Run sichtbar wird. CLAUDE.md Regel 12 („Wissen einfangen”) ist erfüllt durch Skeleton + Marker für Folge-Iteration.
  • Sub-Prozessor-Liste — Daten-Pfad + Engineering-Tooling getrennt (Security-Audit-P1 F-08): Zwei Sektionen in extern/shared/<kunde-slug>/avv/sub-prozessoren.md:
    • (a) Daten-Pfad — Hetzner Online GmbH (DE), Mistral AI SAS (FR), Cloudflare Inc. (US, DPF), Better Stack (Region per AVV). Kunden-Daten fließen durch.
    • (b) Engineering-Tooling — GitHub (Code-Repos OHNE Kunden-Daten, ggf. Anthropic-Backed Code Search), 1Password (Secret-Storage AV-intern), Anthropic Claude Code (Engineering-Workflow). Explizite Aussage „Kunden-Daten fließen NICHT durch”. Plus: Git-Hooks gegen Geheimnisse + Gitleaks-Scanner.
  • EU-AI-Act Deployer-Onboarding-Checkliste (Security-Audit-P2 F-12): konkrete Sign-Off-Liste für Kunde: (a) KI-Verantwortlichen benannt (Name + Rolle), (b) AI-Literacy-Policy erhalten + unterschrieben, (c) AVV unterschrieben, (d) Sub-Prozessor-Liste an Endkunden weitergegeben sofern relevant, (e) Eskalations-Pfad für KI-Output-Probleme dokumentiert. Mit Sign-off-Datum, archiviert in extern/shared/<kunde-slug>/compliance/.
  • Cloudflare-Edge-Eskalations-Schema (Security-Audit-P2 F-09): im Hosting-Konzept drei Stufen explizit nennen: Stufe 1 = CF-DPF (default), Stufe 2 = CF Data Localization Suite (~20 USD/Mo, EU-only-Routing), Stufe 3 = Self-Hosted-Reverse-Proxy ohne CF. Vor Vertragsunterschrift mit Kunde abklären welche Stufe gewollt ist und das im AVV verankern.

Patterns to follow:

Test scenarios:

  • Test expectation: none — Compliance + Doku, kein Code. Aber: Compliance-Vollständigkeit ist verifizierbar (Checkliste durchgehen, alle 8 Punkte abgehakt + Cross-Refs gesetzt)

Verification:

  • 8 Punkte DSGVO-Checkliste in extern/outbound/<kunde-slug>/2026-XX-YY-hosting-konzept.md alle ✅
  • EU-AI-Act-Klassifikation dokumentiert mit Begründung + Entscheidungstree-Output
  • Sub-Prozessor-Liste vollständig
  • 3 age-Recipients eingerichtet, Übergabe-Protokoll archiviert
  • Pattern-File intern/wissen/prozesse/hetzner-lean-customer-stack.md existiert mit allen Sektionen
  • intern/capabilities/hetzner/_index.md Status zeigt „live” mit Cross-Ref auf Pattern-File
  • intern/firma/produkt-bundle.md Industrie-Template-Zeile aktualisiert

Aktive Bauzeit: 1-2 Tage Marvin (DSGVO-Schritte mit Kunde, Pattern-File-Lessons, Hosting-Konzept-Schreibe) + 2-4h Claude (Doku-Files, Cross-Refs, Pattern-File-Skeleton)

System-Wide Impact

  • Interaktions-Graph:
    • Kein bestehender AWS-Stack wird angefasst — Becker, VF, agents-platform, Voice-POC bleiben unverändert auf AWS
    • Bestehender Hetzner-privat@-Account könnte berührt sein (Unit 1 Klärung)
    • Cloudflare-Account: neue Tunnel + neue DNS-Records + neues Access-Application (parallel zu bestehenden mcp-vf.agenticventures.de-Tunnel)
    • Scalekit nicht betroffen (MCP-Schicht ist nicht Teil des MVP)
  • Fehler-Ausbreitung:
    • App-Server-Crash: cloudflared bleibt connected (eigener Container), App-Container restart. User sieht 502 für laufende Requests, danach grün. Better-Stack-Alarm
    • DB-Server-Crash: App liefert 500 (DB nicht erreichbar). Better-Stack-Alarm. Wiederherstellung: Hetzner-Server-Restart automatisch via systemd; bei Volume-Problem → DR-Restore aus WAL-G
    • Mistral-Outage: LiteLLM retry mit Backoff, dann 503 von App. Better-Stack-Alarm. Hybrid-Fallback (z.B. Claude via Bedrock) ist bewusst NICHT in MVP — wenn der Kunde 100% EU-Garantie hat ist Hybrid eh kein Pfad
    • Object Storage-Outage: WAL-G-Pushes failen → archive backlog → Postgres logged Warning. App-Audit-Log async; kurzfristig pufferbar im Speicher, bei längerem Ausfall verlieren wir Audit-Daten → Better-Stack-Alarm
    • Cloudflare-Outage: nichts erreichbar von außen, Tunnel down. Selten, dokumentiert, kein eigenes Mitigations-Pattern in 1. Iteration
  • State-Lifecycle-Risiken:
    • Object-Lock-Compliance-Mode ist unumkehrbar — Setup-Fehler kostet 7 Jahre. Pflicht: Test-Bucket mit 1-Tag-Retention zuerst (Unit 3)
    • WAL-G-Encryption mit verloren-gegangenem age-Key = Backups unbrauchbar. Multi-Recipient-Pattern + 3 separate Schlüssel-Lager (Unit 8) ist die Mitigation
    • pgbouncer-Session-Mode vs Transaction-Mode: bei falscher Wahl ORM-Probleme (Prepared Statements, Session-State). Default Session-Mode, dokumentieren falls auf Transaction-Mode umgestellt wird
  • API-Surface-Parität:
    • App-API ist Kunden-spezifisch — kein bestehendes API-Pattern wird verändert
    • LiteLLM-Adapter-Pattern in App ist 1:1 wie bei Open-WebUI-AWS-Setup (gleiche Provider-Prefix-Konvention)
  • Integration-Coverage:
    • Cross-Layer-Szenarien die Unit-Tests nicht abdecken: WAL-G-Restore-Probe + App-Reconnect (Unit 4 Integration), JWT-Validation-Round-Trip (Unit 6 Integration), Audit-Log-End-to-End (Unit 5 Integration)
  • Unveränderte Invarianten:
    • AWS-Stack bleibt unverändert (Becker, VF, agents-platform, Voice-POC)
    • Bestehende MCP-Pipeline (mcp-vf-hosted) bleibt unverändert
    • Cloudflare-DNS-Zone agenticventures.de wird ergänzt, nicht refactored — bestehende Records bleiben
    • LLM-ADR + Runner-ADR + Cloudflare-DSGVO-ADR werden umgesetzt, nicht geändert

Decision-Points — wo Marvin im Loop bleibt

PunktWas Marvin entscheidetWann
Vor Sprint-StartMistral DPA-Anfrage via Trust Center (Security-Audit-P1 F-05) — SLA typisch 2-8 Wochen, kann Sprint-Abschluss blockieren. Drei Pfade: (a) Sprint mit synthetischen/anonymisierten Daten bis DPA da, (b) Brücken-Pfad Bedrock-EU mit Anthropic-DPA als Übergang, (c) Kunden-Briefing dass Go-Live an DPA hängtparallel zu Unit 1
Vor Unit 1 ✅ entschieden 2026-05-13Hetzner-Account-PfadOption A: privat@-Account auf hello@-UG migrieren (Profile + Billing + Payment auf UG umstellen, Mayday + bestehende Workloads laufen weiter unter UG-Verbuchung)Sprint-Start
Vor Unit 2Server-Location (nbg1 vs fsn1) — beide DE, gleicher Compliance-Scorenach Unit 1
Vor Unit 2SSH-Zugriffs-Pattern: fixiert auf Tailscale (D-PLAN-13). Marvin entscheidet nur: Tailscale-Cloud-Service (Default, MFA-fähig, billig) vs Wireguard-self-hosted (mehr Wartung, gleiche Effekte)mit Unit 2
Vor Unit 3Test-Bucket mit Object-Lock 1-Tag-Retention testen vor Production-Bucket (irreversibel!)mit Unit 3
Vor Unit 3 (Object-Lock-Aktivierung)PII-Klassifikation aus Lead-Briefing (D-PLAN-12) — Object-Lock-Compliance-Mode erst aktivieren NACH Verifikation. Wenn PII verifiziert ausgeschlossen: Compliance-Mode an. Wenn PII drin: erst Presidio sicherstellenmit Lead-Briefing
Vor Unit 3Object-Lock-Retention-Werte-Double-Check (Security-Audit-P3 F-14): Screenshot der Bucket-Konfig + zweite Verifikation. 2555 Days = 7y. 25550 wäre 70y irreversibelmit Unit 3
Vor Unit 5Use-Case-spezifische App-Code-Decisions (CRUD-Modell, RAG-Pipeline, Internal-Chat) — hängt am Lead-Briefingnach Lead-Briefing
Vor Unit 5Mistral DPA via Trust Center anfragen (Standardvorlage)parallel zu Unit 5
Vor Unit 6Identity-Provider für Cloudflare Access (Kunden-IdP vs Google vs OIDC)mit Kunden-IT
Vor Unit 6Domain-Wahl: <kunde-slug>.agenticventures.de (default, AV-Domain) vs Kunden-eigene Subdomain (extra DNS-Delegation)mit Lead-Briefing
Vor Unit 7Better Stack Account-Region + Tier-Wahl (Free vs Pro), AVV-Klärungparallel
Vor Unit 7Hetzner Cost-Limit-Schwellen (Soft + Hard), Email-Verteilermit Unit 7
Vor Unit 8DSGVO-Anwalts-Review für Kunden-AVV (separates Budget) — Standard-Template oder Custommit Unit 8
Vor Unit 8Age-Recovery-Key-Übergabe an Kunde-CTO mit Übergabe-Protokollmit Unit 8

Was Claude (Agent) NICHT kann — Hand-Off-Punkte

  • Hetzner Cloud Console-Klicks (nur via lazyants-MCP + hcloud CLI, was meistens reicht — aber Account-Verwaltung + Cost-Limits + Object-Storage-Bucket-Anlage UI-only)
  • 1Password / Bitwarden-Eingaben für Secrets
  • Mistral La Plateforme Console für API-Key-Generation + DPA-Anfrage
  • Cloudflare Zero-Trust-Dashboard für Identity-Provider-Setup + Access-Application-Konfig (CF-API geht bis Tunnel + Access-Policy, aber IdP-Setup ist UI-only)
  • Better Stack Account-Anlage + Source-Konfig
  • Kunden-AVV-Vorbereitung + Anwalts-Review
  • Age-Key-Übergabe an Kunde-CTO (physisch / verschlüsselte Email)
  • VVT-Eintrag beim Kunden
  • Lead-Briefing mit Kunde

→ Bei diesen Punkten stoppt Claude und gibt Marvin konkrete Anleitung mit Deep-Links (Console-URL + Pfad-Beschreibung, laut CLAUDE.md Regel 16).

Risks & Dependencies

RiskMitigation
Object-Lock-Compliance-Mode falsch konfiguriert (irreversibel)Test-Bucket mit 1-Tag-Retention vor Production-Bucket (Unit 3 Schritt 0), Werte-Double-Check vor Bucket-Erstellung (Screenshot + Review)
Unmaskierte PII landet im 7y-Object-Lock-Audit-Bucket (DSGVO-Art-17-Killer) — Security-Audit-P0 F-02Presidio als PII-Filter DEFAULT-ON in Unit 5 (auch wenn Lead-Briefing PII verneint), Object-Lock-Aktivierung GATED auf Lead-Briefing-PII-Klassifikation (D-PLAN-12)
Audit-Bucket-Access-Key-Leak = 7y-Vollzugriff auf alle LLM-Volltexte — Security-Audit-P0 F-03Layered Encryption: Audit-Files VOR Upload age-encrypted (Multi-Recipient), App-Server nur PUT-Key, Read-Access nur via Marvin-IAM mit MFA, S3-Key-Rotation 90d (D-PLAN-14)
age-Key-Verlust ohne Rotation/Audit-TrailRotation alle 12 Monate + bei Austritt/Kompromittierung, Compromise-Response-Playbook, Audit-Log aller Decrypt-Operations in Better Stack (D-PLAN-8 verschärft, Unit 8)
DR-Test validiert nur Daily-Key — Kunden-CTO-Recovery-Key wird nie getestet (Multi-Recipient-Versprechen leer)DR-Test-Rotation quartalsweise mit allen 3 age-Recipients inkl. dokumentiertem Test mit Kunde-CTO (Unit 4)
Mistral DPA-SLA blockt Sprint-Abschluss (Security-Audit-P1 F-05)Pre-Sprint-Decision-Point + Brücken-Pfade (synthetische Daten, Bedrock-EU-Übergang, Kunden-Briefing)
pgBackRest-Lerneffekt verloren — neuer Stack WAL-G hat eigene LernkurveDR-Test sonntäglich automatisiert; quartalsweise echter Full-Restore-Lauf dokumentiert
age-Key-Verlust = Backups unbrauchbarMulti-Recipient-Pattern (Marvin-Laptop + Marvin-Safe + Kunde-CTO)
Mistral 429 Rate Limit bei Production-LastScale-Tier paid statt Free-Tier; Pre-Production-Last-Test laufen bevor Customer-Demo
Cloudflare-US-Mutter-Problematik beim Industriekunden-AuditData Localization Suite (~20 USD/Mo, Pro-Plan) als Eskalation, dokumentiert im Hosting-Konzept
Hetzner-Account-Klärung verzögert Sprint-StartUnit 1 ist explizit erste Aktion, andere Units blockiert
Use-Case-Unklarheit blockt App-Code in Unit 5App-Skeleton ist Use-Case-agnostic (FastAPI + LiteLLM + Postgres + Cloudflare-JWT) — Use-Case-spezifischer Code kommt in Folge-Iteration nach Lead-Briefing
Better Stack als neuer Sub-Prozessor verzögert AVV-Vorbereitungparallel zu Unit 7 starten; falls AVV nicht rechtzeitig: temporär json-file-Logging only, Better Stack post-Sprint nachziehen
Hetzner Object Storage hat keine Replication — Single-Region-RiskOff-Site-Sync via rclone nach Sekundär-Region (nbg1 → hel1), siehe Unit 4
WAL-G-archive_command blockt Postgres bei Object-Storage-Outagearchive_timeout = 60s (akzeptables Risiko-Fenster), WAL-Backlog-Alarm via pg_stat_archiver
Coolify (aus Mayday-Stack) wird nicht eingesetzt — eigenes Compose-Mgmtbewusste Entscheidung (D-PLAN-1-adjacent): native Compose ist standardisierter, Coolify-Lock-In vermeiden

Documentation / Operational Notes

  • Pattern-File: intern/wissen/prozesse/hetzner-lean-customer-stack.md als Lessons-Learned-Sammelstelle (Unit 8). Pflicht laut CLAUDE.md Regel 12 („Wissen einfangen”)
  • Capability-Bereich: intern/capabilities/hetzner/ als 5-File-Set (Unit 1) — analog intern/capabilities/aws/
  • Hosting-Konzept an Kunde unter extern/outbound/<kunde-slug>/ ablegen, als PDF-Anhang zum Angebot
  • VVT-Eintrag beim Kunden — Marvin liefert Vorlage mit Sub-Prozessor-Liste + TOMs + Datenflüssen
  • Monitoring-Runbook: wie Better-Stack-Alarm bedient wird (DR-Test-Fail, Backup-Fail, App-Down, Mistral-Outage) — Teil von Unit 8 Pattern-File
  • Rollback-Strategie: kein Rollback bei Industriekunde-MVP weil kein „vorher”-Stack. Bei kritischem Bug: App-Image-Tag rollen, Mistral-Modell-Wechsel via Env-Var (LiteLLM-Pattern), letzten WAL-G-Snapshot wiederherstellen

Sources & References

Verlauf

  • 2026-05-13: Plan angelegt nach /ce:brainstorm-Phase. 2 Strategie-Entscheidungen aus Brainstorm (D1 Zweite Spur, D2 Lean-MVP) bestätigt. 4 parallele Recherche-Agents durchgelaufen (Vault-Wissen, Vault-Patterns, Best-Practices Mai 2026, Framework-Docs Mai 2026). 11 zusätzliche Plan-Decisions (D-PLAN-1 bis D-PLAN-11) auf Basis der Recherche getroffen. Hauptbefunde: Hetzner-Account existiert bereits (privat@), Terraform raus aus 1. Iteration, pgBackRest archiviert → WAL-G, Mistral Medium 3 als Default-Modell, LiteLLM SDK-Mode statt Proxy, Cloudflare-Tunnel-Pattern 1:1 von Fargate übertragbar.

  • 2026-05-13 (Document-Review): document-review-Skill mit 4 Personas parallel durchgelaufen (Coherence, Security, Scope-Guardian erfolgreich; Feasibility-Agent stalled nach 600s — Themen ohnehin durch andere Reviews abgedeckt). Auto-Fixes angewendet: drei P0-Security-Fixes (D-PLAN-12 PII-Filter Default-On + Object-Lock-Gate, D-PLAN-14 Audit-Bucket Layered Encryption, age-Rotation-Policy + Compromise-Response in Unit 8), zwei P1-Security-Fixes (D-PLAN-13 SSH via Tailscale wegen Lissabon-Remote, JWT-Replay-Schutz + Failure-Logging in Unit 6), Mistral-DPA als Pre-Sprint-Decision-Point. Scope-Reduktion: DR-Test sonntäglich-Auto → quartalsweise Multi-Key-Rotation, Off-Site-Sync nbg1→hel1 deferred, Mistral-Routing-Logik raus, Pattern-File auf Skeleton. Coherence-Fixes: R8 nur in Unit 5 (raus aus Unit 3), Better-Stack-Subprozessor-Eintrag parallel in Unit 7. Plan-Status: Iteration 1 „Live-Stack” — Routing-Logik, Service-Token, Auto-DR-Test, Off-Site-Sync, Vault-Eigenbetrieb sind für Folge-Iterationen geparkt.

  • 2026-05-13 (Unit 1 Bauphase via /ce:work): Capability-Doku-Skeleton angelegt — intern/capabilities/hetzner/ mit 4 Files (_index.md, _context.md, projects.md, storage.md) als 1:1-Spiegel von intern/capabilities/aws/-Bereich. CLAUDE.md Routing-Tabelle um 4 Hetzner-Eintrage erweitert (Capability, Storage, Projects, Plan-Verweis). intern/firma/stack.md-Hetzner-Zeile aktualisiert. Account-Pfad entschieden: Option A — privat@-Account auf hello@-UG migrieren (Profile + Billing + Payment-Method auf UG, Mayday + bestehende Workloads laufen unter UG-Verbuchung weiter). Hetzner-MCP-Empfehlung verschoben von lazyants auf dkruyt/mcp-hetzner (108 Stars, in offizieller hetznercloud/awesome-hcloud-Liste, Python + stdio+SSE, 27 Tools deckt MVP-Bedarf ab); lazyants als optionale Erweiterung wenn LB/Floating-IPs/Certs-Coverage später gebraucht. Object-Storage-Lücke dokumentiert: kein Hetzner-MCP spricht S3-API, nutzen aws CLI mit Hetzner-Endpoint (Stack-Mirror-konsistent zu Becker). Marvin-Hand-Off-Punkte aus Unit 1 noch offen: (1) Hetzner-Account-Profil + Billing + Payment auf UG umstellen (Console-Aktion), (2) dkruyt/mcp-hetzner installieren + in Claude Code registrieren, (3) Erstes Industriekunden-Project anlegen sobald Lead-Briefing durch. Sobald Project existiert: av-<industriekunde-slug>.md analog intern/capabilities/aws/av-becker.md mit Bestand-Doku anlegen.

  • 2026-05-13 (Becker-Stack-Mirror-Adoption): Nach Review des laufenden Becker-Projekts (bas-twin/, Sprint 1 Tag 2 von 21) den Plan auf Becker-Stack-Mirror umgestellt. Frühere Python/FastAPI/LiteLLM-Annahme ersetzt durch Next.js 15 + Vercel AI SDK + Drizzle + pg-boss + Better Auth + Monorepo (D-PLAN-15). Vercel AI SDK als TypeScript-Provider-Adapter erfüllt LLM-ADR-Pflicht analog LiteLLM, ist gratis Open Source, nicht an Vercel-Hosting gebunden. Multi-Tenant tenant_id + RLS from Day One ergänzt (D-PLAN-16) — Becker-Lesson, null Mehraufwand. Open WebUI als optionaler Compose-Service hinzu (D-PLAN-17) wenn Use-Case Multi-User-Chat braucht (perspektivisch auch Becker-Erweiterung als Folge-Schritt). Architektur-Diagramm aktualisiert (Web + Worker statt FastAPI, ClamAV + Open WebUI gestrichelt als optional), Unit 5 komplett auf Monorepo-Skeleton mit apps/web + apps/worker + packages/{db,llm,shared,ui} umgeschrieben, Audit-Hook von Python-CustomLogger auf TypeScript-Wrapper in packages/llm/ migriert, PII-Filter via Presidio-REST oder JS-Library, Cross-Refs auf bas-twin/-Repo + Becker-Pattern-Files (stage-idempotenz-pgboss-upsert, code-stil-ai-optimiert) ergänzt. Code-Reuse-Erwartung: 60-70% des Becker-Repo-Skeleton übertragbar auf neuen Industriekunden (Build-Config, DB-Setup, Auth, Worker-Skeleton, Test-Pattern, ce-review-Baseline).

  • 2026-05-13 (Hetzner-MCP-Smoke-Test, Unit 1 finalisiert): dkruyt/mcp-hetzner via op run-Wrapper in ~/.claude.json registriert (1Password-Reference op://Personal/<item>/credential, Item-ID pu5tfghpcg26g6nxou6mozlnxm). Smoke-Test via mcp__hetzner__list_locations lieferte 6 erwartete Datacenter (fsn1, nbg1, hel1 in EU-Central + ash, hil, sin) — Token + MCP-Server funktionieren. Capability-Status-Files hochgestuft: intern/capabilities/mcps/hetzner.md von installed-pending-token auf active, intern/capabilities/repos/mcp-hetzner.md von installed auf active, intern/capabilities/mcps/_index.md Hetzner-Zeile aus „Configured” in „Aktive MCPs” verschoben. Unit 1 damit codeseitig komplett — verbleibender Marvin-Hand-Off (Account-Profil + Billing + Payment-Method auf UG via Hetzner-Console) läuft asynchron parallel zu Unit 2 und blockiert MVP-Bau nicht (Industriekunden-Projects sind eigene Mandanten + eigene Tokens, Billing-Adresse-Wechsel rückwirkend ok).

  • 2026-05-13 (Pfad A — Becker als Pilot-Mandant, Phase-Trennung): Nach Smoke-Test-Pruefung des realen AWS-Becker-Bestands (av-becker.md) festgestellt: AWS hat nur Daten-Skeleton + Service-Account + KMS + Bedrock-Setup, kein Compute, kein laufender Workload, Buckets leer. „Migration” als Begriff irrefuehrend — sowohl Becker als auch ein Lead-Industriekunde waeren Greenfield. Marvin’s Entscheidung: Pfad A — Becker zuerst (D-PLAN-18). Zusaetzlich git pull auf bas-twin/main: Alex’ Commit 2188806 (Aufwandsschluessel v15 — 4-Sheet-Aufbau, 1-5-Bewertung, 39 Faktor-Werte) ist neuer Stand — reines Spec-Update, kein Code. Wichtige Entdeckung: packages/llm/src/bedrock.ts ist direkt Bedrock-gewrappt, nicht Vercel AI SDK — D-PLAN-15 Stack-Mirror ist Future-State. Daher D-PLAN-19 Phase-Trennung: Phase 1 Hetzner-Infra leer/bereit jetzt, Phase 2 LLM-Refactor + App-Deploy nach Sprint-1-Wrapup (Stage 5 PDF PR #23). Mistral-DPA-Wartezeit faellt in Phase-1-Window.