ZWEI VORGÄNGE, ZWEI ROUTEN. Die AUSKUNFT (`GET …/export`) stellt alles zusammen, was der Betrieb über eine Person gespeichert hat: Stammdaten (inklusive Geburtstag und Land), Einwilligungen, Reservierungen, Wartelisteneinträge, die Metadaten der KI-Sitzungen, das Marketing-Versandprotokoll und die Durchläufe der Marketing-Automatisierungen. Das LÖSCHBEGEHREN (`POST …/anonymize`) leert den Personenbezug überall dort — unumkehrbar.
MARKETING GEHÖRT ZUR AUSKUNFT. `consent.marketingConsents` listet jeden ausdrücklichen Einwilligungsnachweis je Kanal (`EMAIL`, `SMS`) mit Zweck, Fassung (`consentVersion`), dem WORTLAUT, dem der Gast zugestimmt hat (`consentText`), Herkunft und den Zeitpunkten von Anfrage, Bestätigung und Widerruf. `marketingMessages` ist das Versandprotokoll: jede Kampagnen- oder Automatisierungsnachricht mit Kanal, Name der Kampagne bzw. Automatisierung (`null`, wenn inzwischen gelöscht), Empfängeradresse, Status, Versand-, Zustell-, Öffnungs-, Klick-, Abmelde-, Bounce- und Beschwerdezeitpunkten sowie den Rohereignissen OHNE IP-Adresse (`events[]` mit `type`, `occurredAt`, `client`, `automated` und `url` — bei einem Klick die Zieladresse des geklickten Links, sonst `null`). `marketingFlowRuns` listet jeden Durchlauf einer Automatisierung, auch ohne verschickte Nachricht: `name` (`null`, wenn die Automatisierung inzwischen gelöscht ist), `status` mit `statusLabel`, `startedAt`, `completedAt`, `reservationId` und `context` mit den aus der Reservierung übernommenen Angaben (`reservationDate`, `reservationTime`, `reservationPartySize`, `reservationAreaName`; ohne Reservierung ein leeres Objekt). Nach Ablauf der Aufbewahrungsfrist des Betriebs sind `recipient` und das Verhalten (Öffnungen, Klicks, Bounce, Beschwerde, Abmeldung) geleert, die Ereignisliste ist leer und beendete Durchläufe fehlen — dann gibt es diese Daten schlicht nicht mehr.
DIE AUSKUNFT IST DIE DICHTESTE ZUSAMMENSTELLUNG PERSONENBEZOGENER DATEN, die dieses Haus erzeugen kann. Sie verlangt deshalb `guests:read` UND die Handlung `action:guests.personal` (sie enthält Notizen, Sperrgrund und No-Show-Zähler — ohne die Handlung wäre sie der Umgehungsweg um genau diese Schranke) und zieht vom Stundenkontingent des Schlüssels ab.
WAS NICHT DRINSTEHT: die Nachrichteninhalte der KI-Sitzungen. Sie enthalten regelmässig Aussagen über DRITTE („meine Frau isst kein Gluten") — die Auskunft an eine Person darf keine Auskunft über eine andere sein. Deshalb nur die Metadaten der Sitzungen, genau wie im Dashboard-Export.
TELEFON-AGENT: NICHT ÜBER DIE API. Gespräche, Transkripte, Rückfragen und Gesundheitsangaben aus dem Telefon-Agenten liegen in Modulen, die ein API-Schlüssel nie bekommt. Die Auskunft über diese Route enthält sie deshalb nicht, und die Anonymisierung erfasst sie nicht – sie fallen mit ihren Fristen (Transkripte und Anrufernummern nach höchstens 90 Tagen, Zusammenfassungen nach höchstens 365, Gesundheitsangaben spätestens 30 Tage nach dem Reservierungsdatum). Eine vollständige Auskunft samt Telefondaten erstellt der Betrieb im Dashboard mit „Export (JSON)“ im Gastprofil; „Löschen (DSGVO)“ dort löscht die Telefondaten mit.
`complete` SAGT, OB DIE AUSKUNFT VOLLSTÄNDIG IST. Je Teilliste gibt es eine Obergrenze (1 000 Reservierungen, 500 Wartelisteneinträge, 500 KI-Sitzungen, 500 Einwilligungsnachweise, 1 000 Marketing-Nachrichten mit je höchstens 50 Ereignissen, 500 Automatisierungs-Durchläufe). Wird eine erreicht, steht `complete: false` — und dann ist die Auskunft ausdrücklich unvollständig und darf nicht als vollständig weitergegeben werden.
ANONYMISIERT WIRD, NICHT GELÖSCHT, und das ist die wichtigste Entscheidung dieses Bereichs. Der Weg im Dashboard löscht die Gastzeile; weil die Verknüpfungen auf „leeren" stehen, verlieren damit ALLE Reservierungen ihre Zuordnung. Berichte, Auslastungsstatistik und Besuchszählung bekommen Löcher, die niemand mehr schliesst. Hier bleibt die Zeile stehen und wird GELEERT: der Personenbezug ist weg, die Zählung bleibt.
DREI SCHRANKEN, WEIL ES KEIN ZURÜCK GIBT. Erstens `guests:write` UND `action:guests.personal`. Zweitens das ausgeschriebene Wort `{"confirm":"anonymisieren"}` im Rumpf — eine Adresse allein löst nichts aus, damit ein Vorschauabruf, ein verirrter Wiederholversuch oder eine falsch gebaute Schleife ins Leere treffen. Drittens ist `Idempotency-Key` PFLICHT: die Wiederholung nach einem Netzabbruch darf nicht die zweite Hälfte einer halben Löschung sein — und die Idempotenzzeile ist derzeit der einzige dauerhafte Nachweis, DASS gelöscht wurde. Dazu ein eigenes Stundenkontingent von zwanzig Vorgängen: mehr sind kein Betrieb, sondern ein Zwischenfall.
WAS DIE ANONYMISIERUNG ANFASST: die Gastzeile (Name, Mail, Telefon, Firma, Geburtstag, Land, Notizen, Merkmale, Sperre, Einwilligungen, Newsletter-Token), die Reservierungen (Name, Mail, Telefon, Gastwunsch; `anonymizedAt` wird gesetzt), die Wartelisteneinträge, die Notizen der Tischbelegungen, die KI-Sitzungen samt ihrer Nachrichteninhalte und die MARKETING-DATEN: Versandprotokoll mit Empfängeradresse, Öffnungs- und Klickereignisse, laufende und beendete Automatisierungsdurchläufe und ihre Auslöser werden gelöscht; bei den Werbe-SMS-Aufträgen des Gastes werden Telefonnummer und personalisierter Text geleert — die Aufträge selbst bleiben ohne Personenbezug für die Guthabenabrechnung stehen, ein noch offener geht nicht mehr hinaus. Die Zahlen stehen in der Antwort unter `anonymized.marketingMessages`, `marketingEvents` und `marketingFlowRuns`. Der Vorgang schreibt über Modulgrenzen hinweg, OHNE die Rechte dieser Module zu verlangen — das ist bewusst: eine „Löschung", die Name und Mailadresse an jeder Buchung stehen lässt, meldet Erfolg und hat nichts gelöscht. Er kann dabei in keiner Richtung etwas offenlegen oder anlegen; er entfernt ausschliesslich.
ZÄHLER UND DURCHSCHNITTE BLEIBEN STEHEN. `totalVisits`, `cancelCount`, `noShowCount` und `averagePartySize` sagen nach der Anonymisierung nichts mehr über einen Menschen aus, tragen aber die Auslastungsrechnung des Hauses. Sie zu nullen hiesse, die Statistik rückwirkend zu fälschen — und das verlangt Art. 17 nicht.
EIN ZWEITES LÖSCHBEGEHREN IST EIN ERFOLG, KEIN KONFLIKT. Ist die Zeile bereits geleert, antwortet die Route mit 200 und `anonymized.alreadyAnonymized: true`. Ein zweites Ticket zum selben Menschen darf nicht wie ein Fehler aussehen. Es holt dabei nach, was eine ältere Anonymisierung noch nicht kannte: Marketing-Daten und Geburtstag/Land werden auch dann entfernt (`anonymized.marketingMessages` nennt die Zahl).