---
title: Durrani Launch-Gates (KAR-611)
type: project
tags: [durrani, launch, blocker, gates, csp, preview]
date: 2026-05-25
status: aktiv
related: [[preview-readiness]], [[tasks]], [[p1-review-gate]], [[decision-templates]], [[csp-enforce-plan]], [[re-measure-runbook]]
---

> **Planungs-Docs:** [[decision-templates]] (Vorlagen je Owner-Gate) · [[gate-deliverables]] (Daten-Formulare/Texte/Checklisten — Kais füllt nur aus) · [[csp-enforce-plan]] (Go-Plan Nonces) · [[re-measure-runbook]] (sofort messbar nach CMS-Befüllung).
>
> **Entscheidungen bestätigt (2026-05-25):** Resend primär · KADi erster Launch = ehrlicher **Anfrage/Telefon-Fallback** (nie „bestätigt" suggerieren) · Umami aus · Fotos-Shooting vor Prod.

# Durrani — Launch-Gates (KAR-611)

> Status 2026-05-25: **Preview technisch akzeptiert** (`durrani-web.aseckzai.workers.dev`, OpenNext/Workers, Git-Integration). **Produktion blockiert** durch die offenen Gates unten. Kein Prod-Deploy, keine Domain, keine echten Secrets im Repo. Code-seitig ist P1 fertig (siehe [[p1-review-gate]]); offen sind echte Daten/Accounts/Entscheidungen + Härtung.

## Launch-Gates (jeder muss grün, bevor Prod)

| Gate | Status | Owner | Was fehlt / Definition of Done |
|---|---|---|---|
| **Legal-Daten** (Impressum/Datenschutz) | 🔴 | Kais + Anwalt | Verantwortlicher, USt-IdNr./Kleinunternehmer, ggf. Register in `lib/legal/config.ts`; echte Anbieterdaten in CMS-Location; **anwaltliche Schlussprüfung**. KAR-609. |
| **Kontakt-Mail (Resend)** | 🟡 | Kais | Code fertig (KAR-619, Provider-Layer, dev-Default). Prod: echtes Resend-Konto + `MAIL_PROVIDER=resend` + `RESEND_API_KEY` + `CONTACT_TO_EMAIL`/`CONTACT_FROM_EMAIL` im Dashboard. Verifizieren: echte Mail kommt an. |
| **GloriaFood-Account** | 🔴 | Kais | Echte `NEXT_PUBLIC_GLORIAFOOD_URL/CUID/RUID`; click-to-load live testen; CSP-Domains (fbgcdn/*.gloriafood.com) bei Aktivierung ergänzen. |
| **KADi live vs. Mock** | 🔴 | Kais | Entscheidung: KADi live (`KADI_MODE=live` + `KADI_API_URL` + `KADI_TENANT_ID`, echte Booking-ID statt client-Mock-Ref) ODER Reservierung bewusst als Anfrage/Telefon-Fallback kennzeichnen. Kein Fake-Live. KAR-603/598. |
| **Umami-Analytics** | 🟡 | Kais | Nur wenn sauber self-hosted/EU; sonst zum Launch AUS lassen (nicht halbgar). `NEXT_PUBLIC_UMAMI_*` im Dashboard. KAR-608. |
| **Echte CMS-Daten + Re-Measure** | 🔴 | Kais + Aria | CMS mit echten Inhalten füllen + `CMS_API_URL` setzen → Re-Measure (s.u.). Bis dahin Fallback-Pfad (kein JSON-LD, „nicht verfügbar"-Menu). |
| **CSP Report-Only → Enforce** | 🟢 | Aria | **DONE auf Preview** (commits `aaec9cb`+`2409e74`): enforcing CSP + per-request Nonce (Middleware), keine Wildcards, Maps/GloriaFood/Umami feature-separiert. Live verifiziert: 0 CSP-Violations, 0 Console-Errors, BP 92→96. `upgrade-insecure-requests` weggelassen (brach WebKit auf http; HSTS deckt HTTPS). KAR-606. |
| **Schema-Lücken (ReserveAction/areaServed)** | 🟢 | Aria | **DONE** (commit `37cb13e`): ReserveAction + areaServed (Gelnhausen+Umkreis) im Restaurant-JSON-LD; erscheinen mit echtem CMS. Kein Fake-Rating. |
| **Fotos** | 🟡 | Kais | Echtes Shooting vor Prod bevorzugt (Premium); Preview läuft mit Slot/Fallback. No-Photo-Launch nur bewusste Notlösung. |
| **WebKit/Safari real device** | 🟡 | Kais | CI deckt WebKit ab; zusätzlich auf echtem iOS gegenchecken. |
| **Prod-Deploy + Domain** | 🔴 | Kais | Erst nach allen grünen Gates: R2 incrementalCache (ISR), Domain-Mapping, Prod-Env. Bewusst keine `restaurant-durrani.de`-Verbindung bisher. |

## CSP Enforce-Plan mit Nonces (Plan — Bau braucht Freigabe)
Aktuell: `Content-Security-Policy-Report-Only` (lib/security/headers.mjs). WebKit/Lighthouse zeigen die Report-Only-Meldungen (Next-Inline-Bootstrap vs `script-src 'self'`) als Console-Noise (BP 92).
**Plan (Next 15 + OpenNext/Workers, nonce via Middleware):**
1. Es existiert bereits eine `middleware.ts` (next-intl). Dort pro Request eine Nonce erzeugen (`crypto.getRandomValues`/`randomUUID` — Worker-kompatibel) und in einem Request-Header (`x-nonce`) setzen.
2. Enforcing-CSP-Header bauen mit `script-src 'self' 'nonce-{nonce}'` (+ `strict-dynamic` optional). Next propagiert die Nonce automatisch an seine `<script>`-Tags, wenn die CSP-Header-Nonce vorhanden ist.
3. `frame-src 'self' https://www.google.com` bleibt; GloriaFood-Domains nur bei aktivem Embed.
4. `upgrade-insecure-requests` jetzt ergänzen (im Enforce-Modus wirksam); `report-to`/`report-uri` setzen.
5. `style-src 'unsafe-inline'` für v1 belassen (Next/Tailwind Inline-Styles; Style-Nonce ist Zusatzaufwand) — als Rest-Risiko dokumentieren.
6. Header-Key von `...-Report-Only` auf `Content-Security-Policy` umstellen.
7. **Verifikation:** next-intl-Middleware + Nonce sauber verketten (nicht doppeln); E2E muss 0 CSP-Console-Errors zeigen → danach den Benign-CSP-Filter in `e2e/_helpers.ts` entfernen. headers.test.ts erweitern.
**Risiko:** mittlere Änderung (Middleware-Merge, Worker-Krypto, mögliche Inline-Style-Restmeldungen). Vor Umsetzung Kais-Freigabe.

## Re-Measure-mit-echtem-CMS — Checkliste (wenn CMS_API_URL gesetzt)
- [ ] Speisekarte rendert echte Kategorien/Gerichte/Preise/Diät/Allergene (kein Fallback)
- [ ] JSON-LD erscheint (Restaurant/Menu) — bisher bewusst leer im Fallback
- [ ] **`/de/speisekarte` SEO** erneut (Beobachtung #1: meta-description-Flag) — mit dichtem Content prüfen
- [ ] CLS Mobile+Desktop auf echter Speisekarte (Beobachtung #2: Desktop 0.132 war Single-Run-Varianz)
- [ ] LCP mit echtem Hero-Foto (sobald Shooting da)
- [ ] 0 interne Felder, canonical/hreflang/OG = echte Domain, keine Sprach-Mischung

## FINALE LAUNCH-GATE-LISTE (2026-05-25, nach autonomem Block)

### ✅ Fertig (Aria, Code-seitig erledigt — kein Handeln von dir nötig)
- **CSP Enforce live** auf Preview: enforcing Header + per-request Nonce, keine Wildcards, Maps/GloriaFood/Umami feature-getrennt. Live: 0 Violations, 0 Console-Errors, BP 92→96.
- **Schema**: ReserveAction + areaServed (Gelnhausen + Main-Kinzig-Umkreis) im Restaurant-JSON-LD; kein Fake-Rating. Erscheint sobald CMS befüllt.
- **Honest-Anfrage-Copy** DE/EN: „Tisch anfragen", Success „Anfrage erhalten — noch nicht verbindlich". Nie „bestätigt", solange KADi nicht live.
- **Mail-Provider-Layer** (Resend-ready, dev-Default), **Reservation/Contact/Availability Worker-APIs** (alle 201/200 live verifiziert), **Local-SEO/AI-Audit** (internal linking, entity clarity, machine-readability — kein Spam, keine Doorways).
- **Finale Preview-QA**: Lighthouse A11y 100 / SEO 100 / BP 96 / Perf Desktop 100, Mobile 98; CLS Home 0; axe clean; WebKit+Chromium grün in CI; canonical/hreflang/OG = workers.dev; Secret-Scan sauber.

### 📝 Was DU liefern musst (echte Daten/Inhalte — fülle nur aus, Templates in [[gate-deliverables]])
- **CMS-Inhalte**: echte Gerichte/Preise/Allergene/Diät, Öffnungszeiten, Anbieterdaten der Location, Hero-/Gericht-Fotos.
- **Legal-Daten**: Verantwortlicher, USt-IdNr. oder Kleinunternehmer-Hinweis, ggf. Register → Formular in [[gate-deliverables]].
- **Fotos**: echtes Shooting (Premium-Anspruch) — Brief liegt bereit. No-Photo-Launch wäre bewusste Notlösung.

### 🔑 Externe Accounts (du legst an, Keys NUR ins Cloudflare-Dashboard, nie ins Repo)
- **Resend** (Kontakt-Mail): Konto + API-Key + `CONTACT_TO/FROM_EMAIL`.
- **GloriaFood** (Online-Bestellung): echtes Konto → `NEXT_PUBLIC_GLORIAFOOD_URL/CUID/RUID`; bei Aktivierung CSP-Domains (fbgcdn/*.gloriafood.com) ergänzen (1 Zeile, vorbereitet).
- **KADi** (falls live statt Anfrage-Fallback): `KADI_MODE=live` + `KADI_API_URL` + `KADI_TENANT_ID`.
- **Umami** (optional): nur wenn EU-self-hosted; sonst zum Launch aus.

### ⚖️ Rechtlich zu prüfen (NICHT von Aria final freigegeben)
- Impressum + Datenschutz durch **Anwalt** schlussprüfen (Texte als Entwurf vorbereitet, nicht final). KAR-609.
- Cookie/Analytics-Hinweis nur nötig, falls Umami aktiviert wird.

### ⛔ Prod-blockiert (kein Launch, bevor diese grün sind)
- Legal anwaltlich final · echte CMS-Daten + Re-Measure · Mail real verifiziert (Testmail kommt an) · KADi-Entscheidung (live ODER bewusster Anfrage-Fallback, kein Fake-Live) · Domain-Mapping + Prod-Env + R2 incrementalCache (ISR).

### ✨ Nice-to-have (nach Launch, kein Blocker)
- GloriaFood live · Umami EU · echtes iOS-Safari-Device-Gegencheck · Menü-Bilder via next/image (KAR-618) · `/de/speisekarte` SEO-Re-Check mit dichtem Content.

## Beobachtungen (kein Blocker, monitoren)
1. **`/de/speisekarte` SEO 91** — Lighthouse flaggt „meta-description missing"; Tag ist vorhanden+valide (139 Zeichen, curl-verifiziert). Beim Re-Measure mit echtem CMS-Content erneut prüfen.
2. **Desktop-Speisekarte CLS 0.132** — Single-Run-Varianz (Mobile 0.001, Home 0; Playwright-Observer real ~0). Nicht als Blocker behandeln, solange kein sichtbarer Shift reproduzierbar.

## Lessons Learned
- **Stale Dev-Server auf Port** maskierte den canonical-Fix lokal (~10 Turns) → Memory `feedback_check_stale_dev_server_on_port`. Bei „Fix wirkt nicht trotz korrektem Code" zuerst Port/Zombie prüfen.
- **Cloudflare Workers/OpenNext:** Modul-Level-`const process.env.X` wird vor Env-Injection gelesen → für Public-URLs `NEXT_PUBLIC_*` (build-inlined) nutzen, nicht Runtime-Server-Vars.

---

## GATE-FINALISIERUNG (2026-05-25, autoritativ)

> Owner · Status · DoD · Risiko · nächster Schritt. Klar getrennt: **Launch-Blocker** (muss grün vor Prod) vs **Nice-to-have** (post-Launch).

### 🔴 Launch-Blocker — brauchen Kais-Daten/Entscheidung
| Gate | Owner | Status | DoD | Risiko | Nächster Schritt |
|---|---|---|---|---|---|
| **Allergene/Zusatzstoffe** | Kais/Küche | 🔴 needs_data | alle 62 Zeilen `allergen-matrix.csv` befüllt + `confirmed` | rechtlich (LMIV-Pflicht), Abmahnrisiko | CSV ausfüllen (Legende: [[allergen-matrix-README]]) |
| ~~**Öffnungszeiten**~~ ✅ | Kais | 🟢 confirmed | **ENTSCHIEDEN 2026-05-25: Variante B (Schluss 22:30).** `openingHours` gesetzt, `openingHoursStatus=confirmed`, `openingHoursSpecification` aktiv (7 Slots: Di–Sa + 2× So); SiteStatus rendert | — | erledigt |
| **Legal** (Impressum/Datenschutz) | Kais + Anwalt | 🔴 needs_lawyer | anwaltlich final, Verantwortlicher + USt-IdNr bestätigt, Alt-Tracking entfernt | rechtlich (Impressumspflicht/DSGVO) | Anwalt beauftragen ([[cms-gate-decisions]] §4) |
| **Prod-CMS + echte Daten** | Kais + Aria | 🔴 | CMS prod-deployed, `CMS_API_URL` gesetzt, Re-Measure | — | nach Daten-Gates |
| **Prod-Deploy + Domain** | Kais | 🔴 | R2-ISR, Domain-Mapping, Prod-Env | — | ganz zuletzt |

### 🟡 Daten/Entscheidung — kleiner, aber vor Launch
| Gate | Owner | Status | DoD | Risiko | Nächster Schritt |
|---|---|---|---|---|---|
| Halal/Vegan pro Gericht | Kais | 🟡 | `halalStatus`/`veganStatus=confirmed` | Fehlinfo für Gäste | in Allergen-Matrix mit-erfassen |
| Kategorienamen (K-Serie/Menüs) | Kais | 🟡 | `categoryStatus=confirmed` | kosmetisch | Namen bestätigen |
| #61-Duplikat-Zuordnung | Kais | 🟡 | eine Kategorie final | kosmetisch | bestätigen (sichtbar in `reviewNotes`) |
| Event-Datum Weihnachtsfeier | Kais | 🟡 | echtes Datum statt Platzhalter | saisonal | Datum liefern |
| KADi Launch-Modus ✅ | Kais | 🟢 entschieden | **ENTSCHIEDEN: Launch v1 = Widget/Anfrage-Fallback** (ehrliche Copy live, „Anfrage erhalten — nicht verbindlich"). KADi deep/live = **späterer Enhancement-Block**; Architektur bleibt KADi-live-ready (`KADI_MODE=live`) | „Mock als Live" ausgeschlossen | live-Integration später (eigener Block) |
| Kontakt-Mail (Resend) | Kais | 🟡 | Konto + Key + Testmail kommt an | — | Resend-Konto anlegen |
| GloriaFood (Bestellen) | Kais | 🟡 | Konto + CUID/RUID, CSP-Domain ergänzen | — | Konto anlegen |

### ✨ Nice-to-have — post-Launch
| Item | Owner | Status | Hinweis |
|---|---|---|---|
| Foto-Shooting (Hero/Food) | Kais | 🟡 | Premium; Preview läuft mit Fallback. Shotlist+Alt-Texte ready: [[foto-shotlist]] |
| EN-Übersetzung | Kais/Aria | optional | aktuell `translationStatus=fallback_from_de`; für Premium empfohlen |
| Umami-Analytics | Kais | optional | nur EU-self-hosted; Launch aus |
| Menü-Bilder next/image | Aria | 🟡 | KAR-618 |

### 🟢 Fertig (Aria-seitig, kein Kais-Input nötig)
CSP-Enforce live · Schema ReserveAction/areaServed · **menuNumber (public, gerendert)** · **translationStatus (EN-Fallback markiert)** · Review-Felder (admin-only, Test-gepinnt) · Legal-Global (Status) · Draft-Import (92 Docs, Dev-CMS) · Contract-Härtung (113 Tests) · Re-Measure-Pipeline (CLS 0, 0 Leaks) · Allergen-Matrix + Gate-Vorlagen.

### UPDATE (2026-05-25, autonomer Block 3)
- **Reservierungs-Widget Premium-Rebuild** (durrani-web `892dc9d`, gepusht): Step-Indikator, Party-Chips, gruppierte Slot-Chips (Mittag/Abend, ≥44px), Öffnungszeiten-Filter + Closed-Day-State („… bitte anrufen"), sichtbarer Telefon-Fallback, Non-Binding-Banner, emotionaler Success, hilfreiche Errors. Anfrage-Modus bleibt (kein iframe, KADi-live-ready). `lib/reservation/hours.ts` (+Test, 6 Cases). Re-Measure: CLS 0, 0 Console/Hydration/CSP/Third-Party/axe auf allen Seiten inkl. Reservierung DE/EN; Closed-Day (Mo) verifiziert ohne Hydration-Fehler. Plan: [[reservation-widget-plan]].
- **Allergen-Autofill**: `allergen-matrix-autofill.csv/.md` + `allergen-review-summary.md` — 73 Kandidaten/51 Gerichte, je Begründung + Confidence (63 high/3 med/7 low), 15 hohe-Unsicherheit + 9 zwingend-Bestätigung. Kandidaten auch im CMS in `reviewNotes` (admin-only). **0 confirmed, nichts erfunden**, allergensStatus=needs_review.
- **Legal-Entwürfe**: prüffähige Impressum/Datenschutz/Cookie-Drafts + Datenflüsse + Löschkonzept: [[legal-drafts]] (`legalStatus=needs_lawyer_review`, nicht final). Cookie-Empfehlung: kein Banner (technisch vermeidbar; Umami aus, click-to-load).

### UPDATE (2026-05-25, Block 4) — Speisekarte sichtbar + Allergen-Disclaimer
- **Statisches Menü-Fallback** (durrani-web `f1c7969`, gepusht/deployed): echte 62-Gerichte-Karte aus der Alt-Website als `lib/cms/menu-static.ts` → Preview zeigt die volle Speisekarte OHNE Prod-CMS (resilient; CMS gewinnt wenn verbunden). Live verifiziert: 62 Gerichte/Nummern/Preise + 62 JSON-LD MenuItems + 0 Leaks; CI 4/4 grün.
- **Allergen-Disclaimer** auf der Karte: „Allergene & Zusatzstoffe bitte beim Personal erfragen" — ehrlicher Interim, da Allergene `needs_review` (nie geraten/öffentlich bestätigt). Kandidaten intern in `reviewNotes`.
- **Legal-Volldraft**: [[legal-drafts]] (Impressum/Datenschutz/Cookie + Datenflüsse + Löschkonzept), `needs_lawyer_review`, nicht final.

### SEO/AI — Final-Check (gegen CMS-Build verifiziert 2026-05-25)
✅ JSON-LD Speisekarte: 1 Menu + 11 MenuSection + **62 MenuItem** + 62 Offer · ✅ Home: Restaurant (LocalBusiness-Subtyp) + **ReserveAction** + OrderAction + **areaServed** (11 Städte) + GeoCoordinates + PostalAddress + servesCuisine + **acceptsReservations** + **hasMenu** (→ /speisekarte) · ✅ **kein aggregateRating** (keine Fake-Ratings) · ✅ keine Doorway-Seiten (areaServed im Schema, nicht als Fake-Ortsseiten) · ✅ keine Keyword-Walls (echter Content) · canonical/hreflang(de,en)/OG korrekt · `openingHoursSpecification` bewusst absent bis Zeiten bestätigt.
