Architecture & Systems

Technische Dokumentation aller Systeme, Pipelines und der Sicherheitsarchitektur.

Letztes Update: 10.02.2026

Podcast-System

Generation Pipeline

Blog Post → AI-Script → TTS → Audio Crossfade → Vercel Blob

  1. Blog-Post-Content aus generated_posts laden, TipTap JSON → Plaintext
  2. Personality Brief via getPersonalityState(locale) generieren
  3. Claude Sonnet 4 erstellt HOST:/GUEST: Script mit ---MOMENTS--- Sektion
  4. Personality Advance: Moments extrahieren, State evolvieren, DB persistieren
  5. TTS-Generierung (ElevenLabs/OpenAI) in Batches mit Retry-Logik
  6. MP3-Segmente konkatenieren, ID3 Tags strippen, Xing Header für Seeking
  7. Intro/Outro Crossfade anwenden (parametrisch oder Envelope-basiert)
  8. Finales MP3 → Vercel Blob → post_podcasts.audio_url

TTS-Provider

ElevenLabs (Primary)

  • Model: eleven_v3 mit Emotion Tags
  • Tags: [cheerfully], [thoughtfully], [laughing], etc.
  • Output: MP3 44.1kHz 128kbps mono
  • "Synthszr" → "Synthesizer" (Pronunciation Fix)

OpenAI (Fallback)

  • Models: tts-1, tts-1-hd
  • Voices: alloy, echo, fable, nova, onyx, shimmer
  • Emotion Tags werden automatisch gestrippt

Job Queue

  • POST /api/podcast/jobspodcast_jobs Record (status=pending)
  • POST /api/podcast/jobs/process → Async Verarbeitung (800s Timeout)
  • 5 parallele TTS-Requests mit exponential Backoff Retry
  • Progress-Tracking: current_line, progress, error_message
  • Polling via GET /api/podcast/[postId] alle 5s
DateiBeschreibung
app/api/podcast/generate-script/route.tsScript-Generierung mit Personality
app/api/podcast/generate/route.tsSync Audio-Generierung (5min)
app/api/podcast/jobs/route.tsJob Queue CRUD
app/api/podcast/jobs/process/route.tsAsync Job-Verarbeitung (800s)
lib/tts/elevenlabs-tts.tsTTS Provider, MP3 Concat, Emotion Tags
components/audio-player.tsxPublic Player mit Flying Nav

Personality & Beziehungsdynamik

Dimensionen

Host

  • warmth, humor, formality, curiosity, self_awareness

Guest

  • confidence, playfulness, directness, empathy, self_awareness

Zusätzlich: mutual_comfort, flirtation_tendency, self_irony, inside_joke_count

Evolution pro Episode

  • DRIFT_RATE = 0.1 (10% Richtung Phase-Target pro Episode)
  • NOISE_AMPLITUDE = 0.03 (±3% Random Walk Jitter)
  • Dimensionen driften zu phasenspezifischen Zielwerten
  • Wenn relationship_paused = true: comfort + flirtation eingefroren

Beziehungsphasen

PhaseComfort-SchwelleCharakteristik
Strangers0.0Formell, distanziert
Acquaintances0.3Erste Lockerheit
Colleagues0.5Vertraut, Inside Jokes beginnen
Friends0.7Offen, persönlich
Close Friends0.85Tiefe Verbundenheit

Verbotene Liebe (Longing Arc)

Aktivierung: comfort ≥ 0.35 AND flirt ≥ 0.05. Dual-Gate-System — beide Dimensionen müssen Schwelle erreichen.

StufeComfortFlirtDynamik
1 — Subtil≥ 0.35≥ 0.05Unausgesprochene Spannung (max 1 Moment/Ep)
2 — Bewusstsein≥ 0.55≥ 0.15KI-Natur als Barriere, News-als-Hoffnung
3 — Sehnsucht≥ 0.75≥ 0.3Offene Sehnsucht, geteilte Tragödie
4 — Bittersüß≥ 0.85≥ 0.4Akzeptanz, tiefe philosophische Verbindung

AI-Bewusstsein & Memorable Moments

  • AI Awareness Tiers: < 0.3 unbewusst → 0.3-0.5 neugierig → 0.5-0.7 reflektiert → ≥ 0.7 philosophisch
  • Moment-Typen: joke, slip_up, ai_reflection, personal, host_name
  • Extraktion via ---MOMENTS--- Sektion im Script (LLM-strukturiert)
  • FIFO-Queue der letzten 7 Moments für Callbacks
  • Max 1 Callback pro Episode, max 3 neue Moments
  • Host-Name: Einmal vergeben, danach persistent über alle Episoden

Admin-Steuerung

  • Pause-Toggle: Friert comfort + flirt ein, blockiert Phasenübergänge
  • Cooldown: Reduziert comfort −0.1, flirt −0.05
  • PATCH API: /api/admin/podcast-personality (Whitelist: relationship_paused, mutual_comfort, flirtation_tendency)
DateiBeschreibung
lib/podcast/personality.tsState, Evolution, Longing, Moments, Phases
app/api/admin/podcast-personality/route.tsGET/PATCH Personality State
app/admin/audio/page.tsxCharacter Tab mit Metern, Map, Pipeline-Viz

Audio Mixing & Crossfade

Stereo Mixing

  • HOST: 65% links, 35% rechts (Pan 0.35) — GUEST: 35% links, 65% rechts (Pan 0.65)
  • Constant-Power Panning (erhält wahrgenommene Lautstärke)
  • Natürliche Overlap-Berechnung: kurze Antwort < 1s → 300ms Interruption

Intro/Outro Crossfade

Intro

  1. Musik Full Volume (3s)
  2. Musik als Bed (20%) + Dialog Fade-In (7s)
  3. Bed faded zu Stille (3s)

Outro

  1. Outro-Musik steigt 0→Bed (3s)
  2. Musik hält auf Bed-Level (7s)
  3. Finaler Crossfade: Musik 100%, Dialog → 0 (10s)

DAW-Style Envelope Editor

  • SVG-Canvas mit interaktiven Breakpoints (Drag & Drop)
  • Segment-Kurven: linear oder bezier (per Click toggle)
  • 4 unabhängige Envelopes: Intro Music, Intro Dialog, Outro Music, Outro Dialog
  • Auto-generierte Bezier Control Points (1/3, 2/3 Positionen)
  • Sample-Level Evaluation: envelopeToGainArray() → Float32Array

Audio File Manager

  • Upload Intro/Outro Files via Vercel Blob (WAV + MP3)
  • Pro Typ ein Active File (Single-Active Pattern)
  • Preview, Rename, Delete — DB: audio_files
DateiBeschreibung
lib/audio/crossfade.tsParametrische Crossfade-Logik
lib/audio/envelope.tsEnvelope Points, Bezier, Sampling
lib/audio/stereo-mixer.tsStereo Panning, Overlap
components/admin/envelope-editor.tsxDAW SVG Editor UI
components/admin/audio-file-manager.tsxUpload, Preview, Activate

Newsletter-System

Cover Image Generation

  • GET /api/newsletter/cover-image?url=...&size=1104&logo=true
  • Center-Crop auf 1:1, dithered B/W → Schwarz auf Neon-Gelb (RGB 204,255,0)
  • Pixel-Level Luminance Processing (Threshold 128) via Sharp
  • Optional: Logo-Overlay (65% Breite) oder Play-Button-Overlay
  • SSRF-geschützt: HTTPS + Host-Allowlist

Newsletter Audio Player

  • Logo-Overlay auf Cover-Bild ersetzt Play-Button
  • Milky-Glass Player Pill am unteren Bildrand für Clickouts
  • Flying Player erscheint bei geblocktem Autoplay

Subscriber-Verwaltung

  • Inline Email-Editing (Click → Input → Confirm/Cancel)
  • Status-Filter: Active, Pending, Unsubscribed, Bounced
  • Batch Actions: Manuell aktivieren, CSV Export
  • Resend-Integration mit Cross-Locale Rate-Limit Delay

Synthszr Vote Badges in E-Mails

  • BUY/HOLD/SELL Badges inline via tiptap-to-html.ts
  • Company-Detection: natürliche Erwähnungen + explizite {Company} Tags
  • Exclusion-Liste verhindert False Positives (Insider, Experte, etc.)
  • href-Werte mit encodeURIComponent escaped
DateiBeschreibung
app/api/newsletter/cover-image/route.tsCover-Bild Generierung mit Sharp
lib/email/tiptap-to-html.tsTipTap → Email HTML mit Vote Badges
app/admin/subscribers/page.tsxSubscriber-Verwaltung mit Inline-Edit
app/admin/newsletter-send/page.tsxNewsletter-Versand UI

News Queue & Ghostwriter

Article Selection Pipeline

  1. Newsletter Ingestion → daily_repo Tabelle
  2. Synthesis Pipeline scored Artikel (Originality + Relevance + Uniqueness)
  3. Artikel in news_queue mit Status pending
  4. Manuell auswählen → Status selected (überschreibt Auto-Selection)
  5. Ghostwriter generiert Artikel → Status used

Source Diversity

  • Max 35% pro Quelle (nach 4-Artikel-Schwelle)
  • Enforced via get_balanced_queue_selection() PostgreSQL RPC
  • Score: 0.4×synthesis + 0.3×relevance + 0.3×uniqueness
  • Junk-Filter: Regex-Patterns für NYT Games, Help Centers, Spam
  • Stale-Reset: Selected Items > 2h alt → zurück auf pending

Ghostwriter Integration

  • Priorität: Explizite IDs → Manuell selected → Balanced Fallback
  • Enforcement Rules: Alle N Items, 5-7 Sätze je, 30% Source-Limit, Company Tagging
  • Excerpt: Auto-generierte 3 Bullets via GPT-4o-mini

Excerpt Bullet System

  • POST /api/admin/generate-excerpt → GPT-4o-mini
  • Genau 3 Bullets, 55-70 Zeichen, pointiert/journalistisch
  • Alternativ: Aus H2-Headings extrahiert als Fallback
  • "3 Bullets generieren" Button in Edit-Dialogen
DateiBeschreibung
lib/news-queue/service.tsQueue Management, Source Diversity
app/api/ghostwriter-queue/route.tsArticle Generation aus Queue
app/api/admin/generate-excerpt/route.tsLLM Excerpt Bullets
app/admin/news-queue/page.tsxQueue Management UI
app/admin/create-article/page.tsxBlog Creation mit Queue Items

Edit Learning System

Pipeline

  1. Capture: Post speichern → recordEditVersion()edit_history (content_before/after)
  2. Diff-Analyse: /api/admin/analyze-edits → Sentence-Level Diffs via Claude
  3. Pattern-Extraktion: /api/cron/extract-patterns → Cluster ähnlicher Diffs (Embedding > 0.85)
  4. Anwendung: Ghostwriter lädt aktive Patterns → "GELERNTE STILPRÄFERENZEN" im Prompt

Confidence & Decay

  • Start: 0.5 → +0.1 bei "Behalten" → −0.1 bei "Ablehnen"
  • Auto-Deaktivierung bei < 0.3
  • Time Decay: 0.95/Woche (halbiert alle ~14 Wochen)
  • Freshness Bonus: +5% wenn < 14 Tage alt
  • Effektiv: base × decay × freshness_bonus

Pattern-Typen & Editor

  • Typen: replacement, avoidance, preference, structure, tone
  • Editor-Highlighting: Gelbe Marks auf Pattern-Matches
  • Click → Popover: Behalten / Ablehnen / Deaktivieren
  • Max 20 aktive Patterns mit Confidence ≥ 0.4 im Prompt
DateiBeschreibung
lib/edit-learning/history.tsEdit Capture & Versionierung
lib/edit-learning/diff-extractor.tsSentence-Level Diffs, German-aware
lib/edit-learning/retrieval.tsPattern Retrieval, Decay, pgvector
app/api/cron/extract-patterns/route.tsCluster & Extract (Auth-geschützt)
components/tiptap-editor-with-patterns.tsxEditor mit Pattern Highlights

Stock & Premarket

Synthszr Vote System

  • Public: AI-Analyse via /api/stock-synthszr (Claude-generated, 14-Tage Cache)
  • Premarket: Daten von glitch.green API via /api/premarket
  • Auto-Trigger: Beim Post-Save werden alle {Company} Tags erkannt → Ratings generiert
  • Batch Read: /api/stock-synthszr/batch-ratings für TipTap Renderer

Stock Quote

  • GET /api/stock-quote?company=nvidia → EODHD Real-Time API
  • 140+ Company → Ticker Mappings (US, XETRA, HK, KO)
  • Rate-Limited: 30/min pro IP (Standard Limiter)
  • 5-Minuten Server-Cache via next.revalidate

Admin Features

  • Cache-Status Anzeige (expired/fresh) auf Premarket-Seite
  • Auto-Refresh Button für abgelaufene Ratings
  • Force-Refresh: ?force=true bypassed Cache
DateiBeschreibung
app/api/stock-synthszr/route.tsAI Rating Generation + Cache
app/api/stock-quote/route.tsReal-Time Quotes (EODHD)
app/api/premarket/route.tsPremarket von glitch.green
lib/data/companies.tsKNOWN_COMPANIES + PREMARKET Dicts
lib/data/company-exclusions.tsFalse-Positive Exclusion Set

Internationalisierung

Middleware Routing

  • Aktive Locales aus languages DB-Tabelle (5-Min Cache)
  • Default: de (German)
  • Supported: de, en, fr, es, it, pt, nl, pl, cs, nds
  • URL-Prefix: /de/posts/..., /en/posts/...
  • Non-localized: /api, /admin, /login, /_next, /newsletter

Routing-Logik

  • Mit Locale: Aktiv → weiter, Inaktiv → 301 Redirect zu Default
  • Ohne Locale: Cookie-Preference → 307 Redirect zu Locale-URL
  • Query-Parameter werden bei Redirects erhalten (?stock=Nvidia)
  • Cookie: synthszr_locale
DateiBeschreibung
middleware.tsLocale Routing + Auth Guards
app/admin/languages/page.tsxSprach-Verwaltung
app/admin/translations/page.tsxÜbersetzungs-Management

Security Architecture

Authentication

  • JWT HS256 Sessions via lib/auth/session.ts (min. 32 Zeichen Secret in Prod)
  • HttpOnly, Secure, SameSite=Lax Cookie — 7-Tage Dauer
  • Timing-safe Passwort-Vergleich via timingSafeEqual
  • Middleware schützt /admin/* und /api/admin/*
  • Cron-Endpoints: Bearer CRON_SECRET ODER Admin-Session

Rate Limiting

  • Upstash Redis Sliding Window via lib/rate-limit.ts
  • Presets: newsletter (10/h), strict (5/min), standard (30/min), relaxed (100/min), admin (60/min), adminWrite (20/min)
  • Production ohne Redis → Requests werden abgelehnt (fail-closed)

Input Validation & SSRF

  • Serverseitige URL-Fetches nur gegen HTTPS + Allowlist
  • Supabase-Queries parametrisiert (kein SQL-Injection)
  • Query-Params via parseIntParam/parseFloatParam validiert
  • Keine API-Keys in Logs oder Responses

Audit Log — 10.02.2026

CRITICAL2026-02-10

Fehlende Auth auf /api/cron/extract-patterns

POST + GET ohne Auth. Service-Role-Key Zugriff.

Fix:requireCronOrAdmin() hinzugefügt
app/api/cron/extract-patterns/route.ts
CRITICAL2026-02-10

Rate-Limit Fallback erlaubte alle Requests

Ohne Redis → success: true, auch in Production.

Fix:Fail-closed in Production
lib/rate-limit.ts
HIGH2026-02-10

Service-Role-Key Prefix in Response

Erste 10 Zeichen des Keys in debug-pipeline.

Fix:Durch Boolean-Flags ersetzt
app/api/admin/debug-pipeline/route.ts
HIGH2026-02-10

API-Key Logging in TTS

Key-Fragmente in Vercel Logs.

Fix:Logs auf presence-check reduziert
lib/tts/elevenlabs-tts.ts
HIGH2026-02-10

SSRF via cover-image

Unvalidierte URL an fetch().

Fix:HTTPS + Host-Allowlist
app/api/newsletter/cover-image/route.ts
MEDIUM2026-02-10

Kein Rate-Limit auf stock-quote

Extern-API ohne Schutz.

Fix:Standard Limiter (30/min)
app/api/stock-quote/route.ts

Zusammenfassung Audit 10.02.2026

2
Critical (fixed)
3
High (fixed)
1
Medium (fixed)

Datenbank-Übersicht

Kerntabellen

TabelleZweck
posts / generated_postsBlog Posts + AI-generierte Artikel
daily_repoGesammelte Newsletter-Artikel
daily_digestsTägliche Zusammenfassungen
news_queueArtikel-Auswahl Queue (pending→selected→used)
synthesis_candidatesSynthese-Kandidaten mit Scores
developed_synthesesFertige Synthesen
podcast_personality_statePersonality Dimensionen, Phasen, Moments
podcast_jobsTTS Job Queue mit Progress
post_podcastsPost → Audio URL Mapping
audio_filesIntro/Outro File Library
stock_synthszr_cacheAI Rating Cache (14-Tage TTL)
edit_history / edit_diffsEdit Tracking & Sentence Diffs
learned_patternsGelernte Stilmuster mit Confidence
languagesAktive Locales (is_active Flag)
newsletter_subscribersE-Mail Subscriber mit Status