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:
hcloudCLI + 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_tunnelist Hetzner-konstant,config_src: cloudflarefü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/...stattbedrock/..., 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.sqletc. (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:
- hetznercloud/hcloud Provider Registry — v1.62.0 (28.04.2026),
datacenterdeprecated ab 01.07.2026 →locationnutzen - Hetzner Community: S3 als Terraform-Backend — Skip-Flags +
use_path_style = true. Vorerst NICHT genutzt in 1. Iteration
LiteLLM:
- LiteLLM Mistral Provider — Provider-Prefix
mistral/, OpenAI-kompatibles Schema - LiteLLM Custom Callback —
CustomLogger-Subclass +async_log_success_event - LiteLLM Proxy Logging —
s3_v2-Callback (refactored, async, batched). Für Hetzner:s3_use_virtual_hosted_style: false+s3_endpoint_urlsetzen - LiteLLM PR #11340 — s3_v2 async-batched — +130 RPS gegen alten s3-Callback
Cloudflare:
- cloudflared GitHub — Image
cloudflare/cloudflared:2026.4pinnen, nicht:latest - Cloudflare Tunnel via Token (Remotely-managed) — Token-Pattern via Zero-Trust-Dashboard,
TUNNEL_METRICS=0.0.0.0:2000→/readyals Healthcheck - Cloudflare Access JWT Validation — JWKS-URL
https://<team>.cloudflareaccess.com/cdn-cgi/access/certs, AUD-Claim-Check Pflicht - Cloudflare Access Service Tokens — M2M-Auth-Pattern
Mistral La Plateforme:
- Mistral Models Overview —
mistral-medium-latest(Medium 3.5, 128k, multimodal) als Default-Wahl Mai 2026 - Mistral Rate Limits + Tiers — Production = Scale-Tier paid (Free nur 1 RPS)
- Mistral Trust Center — ISO 27001, SOC2 Type II, GDPR, DPA via Trust Center
- Mistral Pricing — Medium 3: 3 per 1M Tokens (Input/Output) — 90% von Large 3 zu 1/3 Preis
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:
- pgBackRest archiviert — „After pgBackRest” (Christophe Pettus, Apr 2026) — No-Go für neue Setups
- WAL-G GitHub — Empfohlener Ersatz, Go-Binary, S3-kompatibel, parallele Base-Backups + Continuous WAL-Archiving
Hetzner Object Storage:
- Hetzner Object Storage Docs — Endpoint
https://<region>.your-objectstorage.com, Regionen fsn1/nbg1/hel1, S3-Subset - Object Lock Retention — Compliance-Mode nur bei Bucket-Erstellung aktivierbar, unumkehrbar für 7y-Retention
- Lifecycle Policies — Funktioniert NICHT bei suspended Versioning
- Pricing — Base 4,99 €/Mo (1 TB Storage + 1 TB Egress)
Encryption:
- age GitHub + SOPS Docs — age als Industry-Default für File-Encryption, sops+age für strukturierte Secrets
Key Technical Decisions
| Decision | Rationale |
|---|---|
| D-PLAN-1 Terraform raus aus 1. Iteration | hcloud_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-Container | Bei 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-Modell | 90% 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-Backup | pgBackRest 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-Mode | 7y-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 gzip | Refactored 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 Encryption | Master-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: cloudflare | Zentrale 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-Drain | 0-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. Schritt | intern/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 Default | Nach 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 One | Becker-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-Bestandteil | Wenn 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,
hcloudCLI + 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/workerPipeline-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.deoder 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-embed1024-Dim ist gesetzt; falls Code-Search: Codestral Embed deferred pgbouncer-vs-direkt-Pool im App-Code — Best-Practices-Agent empfiehltpgbouncerals 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.-> CFA CFA -->|Cf-Access-Jwt-Assertion| CFT CFT -.QUIC outbound 443.-> CFD CFD -->|http web 3000| WEB CFD -.optional.-> OWUI WEB -->|Drizzle pgbouncer 6432| PGB WORKER -->|Drizzle pgbouncer 6432| PGB WORKER -.LISTEN NOTIFY pg-boss.-> PG PGB --> PG PG --> VOL WEB -->|Vercel AI SDK| MISTRAL WORKER -->|Vercel AI SDK| MISTRAL WEB -->|aws-sdk S3-API| BUCKET WORKER -->|Audit-Hook age-encrypted| BUCKET WORKER -.scan.-> CLAMAV PG -->|WAL-G stream age-encrypted| BUCKET PG -->|pg_dump nightly age-encrypted| BUCKET WEB -->|stdout JSON| BS WORKER -->|stdout JSON| BS CFD -->|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(analogintern/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(analogintern/capabilities/aws/_context.md— drei Pflicht-Sections aus _meta/conventions §11) - Create:
intern/capabilities/hetzner/projects.md(analogaccounts.md— Project-Inventar) - Create:
intern/capabilities/hetzner/storage.md(analogbuckets.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>, Labelsproject=<kunde-slug>,environment=prod,managed-by=marvinauf 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.jsonoder Claude-Code-Settings registrieren mitHETZNER_API_TOKEN.
Patterns to follow:
- Frontmatter aus
_meta/schemas.md§5.23 (type: doc) für_index.mdund Doku-Files - Frontmatter §5.20 für
_context.md(type: folder_context) - Sektion-Struktur aus
intern/capabilities/aws/_index.md1:1 übernehmen, „AWS” → „Hetzner”
Test scenarios:
- Test expectation: none — pure Doku + Account-Setup, kein Code
Verification:
hcloud project listzeigt das neue Project unter dem zugewählten Account- lazyants-MCP via Claude Code aufrufbar:
mcp__hetzner__list_serversliefert 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(analogintern/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 verwendetenhcloud-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
nbg1oderfsn1(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: 1×
hcloud networkav-<kunde>-netmit Range10.0.0.0/16, Subnet10.0.1.0/24in 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: 1×
hcloud firewallav-<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, installdocker,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.mdfü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-serverundssh marvin@db-serverüber Marvin-Key funktioniert;ping db-server-internal-IPvom App-Server erfolgreich;ping app-servervon 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 listzeigt beide Serverrunning, beide in gleicher Location, beide am Networkhcloud firewall describe av-<kunde>-fwzeigt korrekte Inbound/Outbound-Ruleshcloud volume listzeigt 50 GB Volume attached an DB-Server, mountedintern/capabilities/hetzner/av-<kunde-slug>.mdhat 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-objectlockmit 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-rwfür App-Server (Read+Write auf data-Bucket), (b)<kunde-slug>-audit-writefür App-Server (NURs3:PutObjectauf audit-Bucket, kein Read, kein Delete), (c)<kunde-slug>-audit-read-mfafü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>-auditmit 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.comzeigt beide Buckets- Versioning + Object-Lock + Lifecycle via
aws s3api get-bucket-*-Calls korrekt intern/capabilities/hetzner/storage.mdhat 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 = 512MBmax_connections = 50(pgbouncer davor poolt)random_page_cost = 1.1(NVMe)effective_io_concurrency = 200wal_buffers = 16MBmax_wal_size = 4GBcheckpoint_completion_target = 0.9archive_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-Indexm=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 Beckerapps/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_baselinelegttenants-Tabelle an + erweitert jede Domain-Tabelle umtenant_id(FK auftenants.id, NOT NULL) + aktiviert RLS-Policycurrent_setting('app.tenant_id'). Pattern aus Beckerpackages/db/SCHEMA.mddirekt übernehmen. Drizzle-Middleware inpackages/db/middleware.tssetztSET LOCAL app.tenant_idpro 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
sessionfür ORM-Kompatibilität,transactionwenn 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-pushtäglich 02:00 (Full Base Backup, gestreamt durchage -r <pubkeys>vor Upload).archive_commandfü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.txtmitchmod 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-snapshotvia 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 viahcloud volume snapshot deleteim 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 LATESTauf 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 TABLEmithalfvec(1024)und HNSW-Index erfolgreich;INSERT+SELECT ... ORDER BY embedding <=> $1 LIMIT 10liefert Resultate - Happy path: WAL-G
backup-pushschreibt nach Object Storage erfolgreich;wal-g backup-listzeigt Backup; Manual-Restore auf 2. Serverwal-g backup-fetch LATESTstellt 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_userConnect 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 analogbas-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 analogbas-twin/apps/worker/src/stages/) - Create:
~/source/<kunde-slug>/packages/db/— Drizzle-Schema mittenant_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 wiecompromise/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 analogbas-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 umgenerateText/streamText/generateObjectdie VOR Return den vollen Request + Response durchage -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-Wrapperpresidio-client. (b) Reine JS-Library wiecompromise+ 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/hattenantIdals erste Spalte mit FK auftenants. RLS-Policy via Drizzle-Migration aktivieren. Auth-Context-Middleware setzttenant_idautomatisch in Query-Builder (analog Beckerpackages/db/middleware.ts). Tenants initial: nur<kunde-slug>. Drizzle-Idiom direkt aus Becker-Repo übernehmen —packages/db/SCHEMA.mdausbas-twin/als Vorlage. audit_log-Tabelle in Postgres analog Becker: jede schreibende Operation hinterlässt Audit-Trail mittenantId,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.tsinitialisiert 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:mainmit 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.devia zweiter Tunnel-Ingress-Rule. - Mistral-API-Key: Über La-Plateforme-Console generieren, in 1Password, ins Compose-
secrets/-Volume mounten, App liest ausMISTRAL_API_KEYEnv-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, keinports:-Mapping nach außen. cloudflared (Unit 6) erreicht App überhttp://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/...stattbedrock/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 mittool_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-CheckGET /healthliefert 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_callsim 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:
POST /accounts/$CF_ACCOUNT_ID/cfd_tunnelmit{"name":"<kunde-slug>-app","config_src":"cloudflare"}→ liefert TUNNEL_ID + TUNNEL_TOKENPUT /accounts/$CF_ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID/configurationsmit Ingress-Rules:<kunde-slug>.agenticventures.de → http://app:8000,service: http_status:404als FallbackPOST /zones/$CF_ZONE_ID/dns_recordsmit CNAME<kunde-slug>→<TUNNEL_ID>.cfargotunnel.com,proxied: true- 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, EnvTUNNEL_TOKEN_FILE=/run/secrets/tunnel-tokenplusTUNNEL_METRICS=0.0.0.0:2000. Healthcheck viacloudflared 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/certscachen (~1h TTL viaPyJWKClient). - Pro Request:
Cf-Access-Jwt-Assertion-Header lesen, mitkidJWKS-Key matchen, Signatur prüfen,aud-Claim gegen Application-AUD matchen (sonst kann jeder andere CF-Access-Tenant einreichen),issgegenhttps://<team>.cloudflareaccess.com,expnicht abgelaufen,nbf-Check mit max 30s Clock-Skew-Toleranz. - Replay-Schutz:
jti-Claim-Tracking via Postgres-Tabellejwt_replay_cache(Spaltenjti,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).
- JWKS-URL
-
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:8000statt ECS-Task config_src: cloudflare(D-PLAN-9) stattconfig.yml-Mount- pyjwt[crypto] +
PyJWKClientfür JWT-Validation - Subprozessor-Eintrag-Pattern aus cloudflare-dsgvo §Pflicht-To-Dos
Test scenarios:
- Happy path:
curl https://<kunde-slug>.agenticventures.de/healthmit 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
unauthenticatedin 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>-appmit 4 aktiven Connections - DNS-Lookup
<kunde-slug>.agenticventures.dezeigt 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-Driverjson-filemitmax-size: 10m, max-file: 3plus 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
vectoroderfluent-bitals Sidecar der Docker-Logs aus/var/run/docker.sockliest + 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/healthalle 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.yamldokumentiert 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ärjson-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.dezeigt 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 analogmcp-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):
- DPA mit Kunde unterschrieben (Auftragsverarbeitungsvertrag UG i.G. ↔ Kunde)
- Sub-Processor-Liste an Kunde übergeben (Hetzner, Mistral, Cloudflare, Better Stack)
- DPA mit jedem Sub-Prozessor (Hetzner-AVV + Mistral-DPA via Trust Center + Cloudflare-DPF + Better-Stack-AVV)
- Datenstandort mit Kunde abgestimmt + dokumentiert (alle EU, kein US außer Cloudflare-Edge unter DPF)
- VVT-Eintrag beim Kunden (Verzeichnis Verarbeitungstätigkeiten)
- TOMs (Technische Organisatorische Maßnahmen) — Hetzner-TOMs als Dokument, plus eigene TOMs (Verschlüsselung, Zugriffsregeln, Backup-Strategie)
- Datenminimierung (kein PII in Prompts wenn vermeidbar — Use-Case-spezifisch im App-Design)
- 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.mdim 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.mdals 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:
- 8-Punkt-Checkliste aus anthropic-datenschutz
- EU-AI-Act-Decision-Tree aus eu-ai-act-pflichten
- Pattern-File-Struktur aus mcp-hosting-fargate-tunnel
- Sub-Prozessor-Listen-Pattern aus cloudflare-dsgvo §Pflicht-To-Dos
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.mdalle ✅ - 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.mdexistiert mit allen Sektionen intern/capabilities/hetzner/_index.mdStatus zeigt „live” mit Cross-Ref auf Pattern-Fileintern/firma/produkt-bundle.mdIndustrie-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.dewird 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
| Punkt | Was Marvin entscheidet | Wann |
|---|---|---|
| Vor Sprint-Start | Mistral 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ängt | parallel zu Unit 1 |
| Sprint-Start | ||
| Vor Unit 2 | Server-Location (nbg1 vs fsn1) — beide DE, gleicher Compliance-Score | nach Unit 1 |
| Vor Unit 2 | mit Unit 2 | |
| Vor Unit 3 | Test-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 sicherstellen | mit Lead-Briefing |
| Vor Unit 3 | Object-Lock-Retention-Werte-Double-Check (Security-Audit-P3 F-14): Screenshot der Bucket-Konfig + zweite Verifikation. 2555 Days = 7y. 25550 wäre 70y irreversibel | mit Unit 3 |
| Vor Unit 5 | Use-Case-spezifische App-Code-Decisions (CRUD-Modell, RAG-Pipeline, Internal-Chat) — hängt am Lead-Briefing | nach Lead-Briefing |
| Vor Unit 5 | Mistral DPA via Trust Center anfragen (Standardvorlage) | parallel zu Unit 5 |
| Vor Unit 6 | Identity-Provider für Cloudflare Access (Kunden-IdP vs Google vs OIDC) | mit Kunden-IT |
| Vor Unit 6 | Domain-Wahl: <kunde-slug>.agenticventures.de (default, AV-Domain) vs Kunden-eigene Subdomain (extra DNS-Delegation) | mit Lead-Briefing |
| Vor Unit 7 | Better Stack Account-Region + Tier-Wahl (Free vs Pro), AVV-Klärung | parallel |
| Vor Unit 7 | Hetzner Cost-Limit-Schwellen (Soft + Hard), Email-Verteiler | mit Unit 7 |
| Vor Unit 8 | DSGVO-Anwalts-Review für Kunden-AVV (separates Budget) — Standard-Template oder Custom | mit Unit 8 |
| Vor Unit 8 | Age-Recovery-Key-Übergabe an Kunde-CTO mit Übergabe-Protokoll | mit Unit 8 |
Was Claude (Agent) NICHT kann — Hand-Off-Punkte
- Hetzner Cloud Console-Klicks (nur via lazyants-MCP +
hcloudCLI, 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
| Risk | Mitigation |
|---|---|
| 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-02 | Presidio 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-03 | Layered 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-Trail | Rotation 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 Lernkurve | DR-Test sonntäglich automatisiert; quartalsweise echter Full-Restore-Lauf dokumentiert |
| age-Key-Verlust = Backups unbrauchbar | Multi-Recipient-Pattern (Marvin-Laptop + Marvin-Safe + Kunde-CTO) |
| Mistral 429 Rate Limit bei Production-Last | Scale-Tier paid statt Free-Tier; Pre-Production-Last-Test laufen bevor Customer-Demo |
| Cloudflare-US-Mutter-Problematik beim Industriekunden-Audit | Data Localization Suite (~20 USD/Mo, Pro-Plan) als Eskalation, dokumentiert im Hosting-Konzept |
| Hetzner-Account-Klärung verzögert Sprint-Start | Unit 1 ist explizit erste Aktion, andere Units blockiert |
| Use-Case-Unklarheit blockt App-Code in Unit 5 | App-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-Vorbereitung | parallel 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-Risk | Off-Site-Sync via rclone nach Sekundär-Region (nbg1 → hel1), siehe Unit 4 |
| WAL-G-archive_command blockt Postgres bei Object-Storage-Outage | archive_timeout = 60s (akzeptables Risiko-Fenster), WAL-Backlog-Alarm via pg_stat_archiver |
| Coolify (aus Mayday-Stack) wird nicht eingesetzt — eigenes Compose-Mgmt | bewusste 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.mdals Lessons-Learned-Sammelstelle (Unit 8). Pflicht laut CLAUDE.md Regel 12 („Wissen einfangen”) - Capability-Bereich:
intern/capabilities/hetzner/als 5-File-Set (Unit 1) — analogintern/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
- Origin document: _index
- Verwandte ADRs: llm-hosting-eu-optionen · runner-architektur · cloudflare-dsgvo · markdown-und-db-trennung · anthropic-datenschutz · eu-ai-act-pflichten
- Verwandte Prozesse: mcp-hosting-fargate-tunnel · open-webui-fargate-bedrock · stage-idempotenz-pgboss-upsert
- Vorbild-Capability-Doku: _index · av-becker · accounts · buckets
- Vorbild-Projekt-Pläne: _index · _index
- Konventions-Quelle: schemas.md · conventions.md · CLAUDE.md Regeln 15, 16, 18, 19, 20, 21
- External (siehe §Context & Research): Hetzner Terraform Provider, Hetzner Object Storage Docs, LiteLLM Docs, Mistral La Plateforme Docs, Cloudflare Tunnel + Access Docs, pgvector GitHub, WAL-G GitHub
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 vonintern/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 offiziellerhetznercloud/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, nutzenawsCLI 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>.mdanalogintern/capabilities/aws/av-becker.mdmit 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-Tenanttenant_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 mitapps/web+apps/worker+packages/{db,llm,shared,ui}umgeschrieben, Audit-Hook von Python-CustomLoggerauf TypeScript-Wrapper inpackages/llm/migriert, PII-Filter via Presidio-REST oder JS-Library, Cross-Refs aufbas-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.jsonregistriert (1Password-Referenceop://Personal/<item>/credential, Item-IDpu5tfghpcg26g6nxou6mozlnxm). Smoke-Test viamcp__hetzner__list_locationslieferte 6 erwartete Datacenter (fsn1, nbg1, hel1 in EU-Central + ash, hil, sin) — Token + MCP-Server funktionieren. Capability-Status-Files hochgestuft:intern/capabilities/mcps/hetzner.mdvoninstalled-pending-tokenaufactive,intern/capabilities/repos/mcp-hetzner.mdvoninstalledaufactive,intern/capabilities/mcps/_index.mdHetzner-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 pullauf bas-twin/main: Alex’ Commit2188806(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.tsist 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.