# Entwicklungsplan: Meldeverfahren v2 (Selbstbedienungs-Flow „PM melden") **Stand:** 22.07.2026 **Grundlage:** `Lösch- und Änderungsanfragen.md` (Entscheidungsachse, Free/Paid-Matrix, §11 Self-Service-Ausbau) und `Pressemitteilung melden.md` (Vier-Spuren-Flow) **Ziel:** Portal übernimmt den gesamten Post-Publication-Zustand selbst (connektar = write-once). Vier Spuren: Kleine Korrektur (frei), DSGVO-Anonymisierung (frei, Admin-Gate), Depublizierung (kostenpflichtig), Änderung (kostenpflichtig, KI-Freigabelauf). --- ## 0. Ist-Stand: Was bereits existiert (Bauen auf, nicht neu) Die Codebasis enthält bereits ein **Meldeverfahren v0.1** und fast alle benötigten Bausteine. Der Plan ist ein **Ausbau**, kein Neubau. | Baustein | Vorhanden | Relevante Dateien | |---|---|---| | Melde-Queue (DSGVO / Persönlichkeitsrecht / Meldung) | ✅ v0.1: Intake → Admin-Queue, manuelle Erledigung | `app/Models/LegalRequest.php`, `app/Enums/LegalRequestType.php`, `app/Enums/LegalRequestStatus.php` | | Öffentliche Meldeformulare (Honeypot, Rate-Limit, DSA-Eingangsbestätigung `M-{id}`) | ✅ `melden/{way}` + PM-gebunden `/release/{slug}/rechtliches` | `app/Http/Controllers/Web/ReportFormController.php`, `app/Http/Controllers/LegalRequestController.php`, `routes/legal.php` | | Admin-Queue „Recht & Compliance" (`can:compliance:manage`) | ✅ Status-Handling (open/in_progress/resolved/rejected), aber **keine Ausführungs-Aktionen** (kein Anonymisieren/Depublizieren aus der Queue) | `resources/views/livewire/admin/legal-requests/{index,show}.blade.php`, `routes/admin.php` | | Magic-Link-Infrastruktur (sha256-Token, purpose, payload, TTL, Consume) | ✅ purposes `login`, `press_release_access` | `app/Models/MagicLink.php`, `app/Services/Auth/MagicLinkGenerator.php`, `app/Http/Controllers/Auth/MagicLinkConsumeController.php`, `app/Console/Commands/PurgeMagicLinks.php` | | Depublizierung + Tombstone | ✅ `depublish()` → Archived + `depublished_at`, Detail-URL liefert **HTTP 410** Tombstone | `app/Services/PressRelease/PressReleaseService.php:394`, `app/Http/Controllers/Web/ReleaseDetailController.php`, `resources/views/web/release-tombstone.blade.php` | | Bezahlte Depublizierung (Stripe-Checkout + Webhook-Fulfillment) | ✅ aber nur **eingeloggt** (`routes/customer.php:52`) | `app/Services/Billing/StripeCheckoutService.php:452`, `app/Listeners/ProcessStripeWebhook.php:475`, `app/Enums/SinglePurchaseType.php` | | Credits (Ledger, atomar) | ✅ | `app/Services/Billing/CreditWalletService.php` | | KI-Prüfung (grün/gelb/rot, Audit) | ✅ Klassifikation + Routing bei Erst-Freigabe; Re-Check nach Edit **ohne** Status-Wirkung | `app/Jobs/ClassifyPressRelease.php`, `PressReleaseService::routeByClassification():176`, `reprocessAfterContentChange():150`, `app/Models/KiAudit.php` | | Hinterlegte Pressekontaktadresse | ✅ `Contact.email` (Pivot `press_release_contact`), Fallback `Company.email` | `app/Models/Contact.php`, `app/Models/Company.php` | | Audit/Verlauf | ✅ `ActivityLog::record()` + `PressReleaseStatusLog` (mit `source`) | `app/Services/ActivityLog.php`, `app/Models/PressReleaseStatusLog.php` | | connektar-Eingang | ✅ Legacy-API (`store()` → Review/KI bzw. Sofort-Publish), Bulk-Import mit `LegacyImportMap`-Skip-Guard | `app/Http/Controllers/Api/Legacy/LegacyPressReleaseController.php`, `app/Services/Import/PressReleaseImporter.php` | **Fehlende Kernstücke (= dieser Plan):** Re-Sync-Sperre als PM-Flag, Anonymisierungs-Engine (Wortgrenzen-Replace + Diff), Magic-Link-Verifikation für das Meldeverfahren, Self-Service-Portal mit den vier Spuren, Admin-Ausführungsaktionen in der Queue, Gast-Checkout für Depublizierung/Änderung, Reversibilitätsfenster, verschlüsselte Alt-Wert-Snapshots mit Löschfrist. --- ## 1. Phasenübersicht Reihenfolge nach Nutzen und Abhängigkeit: Zuerst die Bausteine, die den **manuellen Interimsprozess** sofort absichern (Re-Sync-Sperre, Anonymisierung im Admin), dann Verifikation, dann die Self-Service-Spuren. ``` Phase 1 Datenmodell + Re-Sync-Sperre ✅ umgesetzt 22.07.2026 Phase 2 Anonymisierungs-Engine + Admin-Ausführung ✅ umgesetzt 22.07.2026 Phase 3 Magic-Link-Verifikation + Intake-Trichter ✅ umgesetzt 22.07.2026 Phase 4 Spur A — Kleine Korrektur (self-service) ✅ umgesetzt 22.07.2026 (panel-first) Phase 5 Spur B — DSGVO-Einreichung (self-service → Admin-Gate) Phase 6 Spur C — Depublizierung (Gast-Checkout + Reversibilitätsfenster) Phase 7 Spur D — Änderung (Gast-Checkout + KI-Freigabelauf) Phase 8 Querschnitt-Abschluss: Kommunikation, Auslieferungs-Checks, Doku ``` Jede Phase ist eigenständig deploybar; nach Phase 2 ist der Interims-Triage-Prozess (§4 des Konzepts) technisch vollständig unterstützt. ### Stand 22.07.2026 abends — was noch fehlt (Einstieg für morgen) **Phasen 1–4 sind umgesetzt und getestet** (Details in den Phasen-Abschnitten). Offen: 1. **Phase 5 — Spur B strukturiert:** Das DSGVO-Anliegen im verifizierten Web-Bereich ist bisher Freitext; geplant ist das strukturierte Schwärzungsformular (Felder + Varianten) mit Pflicht-Diff aus `AnonymizationService::preview()`, das den fertigen Schwärzungsauftrag in die Admin-Queue legt (Admin-Editor vorbefüllt). 2. **Phase 6 — Spur C Gast-Checkout:** 35-€-Checkout für Magic-Link-Verifizierte (ohne Login); Reversibilitätsfenster-Hinweis („umkehrbar bis …", `legal.reversal_window_days`); **`lockSync()` auch im bestehenden Webhook-Fulfillment `fulfillDepublication()` nachrüsten** (bezahlte Panel-Depublizierung setzt die Sperre bisher nicht!). 3. **Phase 7 — Spur D Checkout:** 10 € (`billing.change_price_cents` anlegen), Gast + Panel; nach Zahlung KI-Freigabelauf auf Pending-Payload (grün/gelb → live, rot → kein Verbrauch, erneut einreichbar). Ersetzt den Interim-Weg „Change-Anfrage in die Queue". 4. **Spur A im Web-Pfad:** Korrektur-Formular (CorrectionService-Engine) auch für Magic-Link-Verifizierte statt Queue-Anfrage; Bestätigungsmail an die Kontaktadresse. 5. **Phase 8 — Querschnitt:** connektar-Template-Stopp in allen Texten prüfen, `noindex` im Tombstone verifizieren, zweistufige Eskalations-Mailtexte (Ablehnung mit Aufsichtsbehörden-Hinweis), Doku-Abgleich. 6. **Ops:** Dev-DB-Migration (`sync_locked_at`-ALTER auf `press_releases`, FULLTEXT-bedingt langsamer Tabellen-Copy) lief am Abend des 22.07. noch; danach `php artisan migrate` für die restlichen Migrationen (`magic_links`-Enum) ausführen. Test-DB ist vollständig migriert, alle 41 Legal-Tests grün. 7. **Konfig-Feinjustierung (22.07. abends):** `correction_max_distance_percent` auf **5 %** und `corrections_per_week` auf **1** verschärft (statt 15 %/2 aus E5). Dazu im Service die **Mini-Edit-Ausnahme**: Änderungen ≤ 2 Zeichen gelten immer als klein — sonst würde bei kurzen Titeln ein einzelner Tippfehler die Prozent-Grenze reißen. --- ## Phase 1 — Datenmodell + Re-Sync-Sperre ✅ **umgesetzt (22.07.2026)** **Warum zuerst:** „Jede Mutation setzt die Re-Sync-Sperre" ist strukturell zwingend (§7), sonst überschreibt ein Re-Import jede Anonymisierung — das wäre ein DSGVO-Verstoß nach erfolgter Löschung. ### 1.1 Migrationen ✅ - ✅ `press_releases`: `sync_locked_at` (nullable timestamp) + `sync_lock_reason` (string) — `2026_07_22_151506_add_sync_lock_to_press_releases_table.php`. **Umsetzungshinweis:** Wegen des FULLTEXT-Index kann MySQL hier nicht INSTANT arbeiten (voller Tabellen-Copy, ~175k Zeilen); die Migration nutzt deshalb **ein kombiniertes ALTER** für beide Spalten statt zweier Blueprint-Einzelstatements und verzichtet auf `after()`-Positionierung. Merkregel für spätere Migrationen an `press_releases`: Spalten-Adds immer bündeln. - ✅ `legal_requests` erweitert — `2026_07_22_151507_add_meldeverfahren_fields_to_legal_requests_table.php`: - Neue `LegalRequestType`-Cases: `Correction` (Spur A), `Depublication` (Spur C), `Change` (Spur D). `Dsgvo` bleibt Spur B. - Neue Spalten: `verified_at`, `verification_method` (Enum `LegalRequestVerificationMethod`: `magic_link`/`manual`/`logged_in`), `single_purchase_id` (FK, Spur C/D), `executed_at`. - Diff-/Schwärzungsdaten kommen in `payload` (json, existiert) — Nutzung ab Phase 4/5. - ✅ Neue Tabelle `press_release_redaction_snapshots` (`2026_07_22_151507_create_..._table.php`) + Modell `PressReleaseRedactionSnapshot` mit `capture()` (Cast `encrypted:array`, `purge_after` aus `config('legal.snapshot_retention_days')`), Scope `dueForPurge()` und Factory. → Reversibilität ohne Klartext-Altwerte im Log; Test sichert ab, dass der Klartext nie unverschlüsselt in der DB liegt. ### 1.2 Re-Sync-Sperre durchsetzen ✅ - ✅ `PressRelease::lockSync(string $reason)` + `isSyncLocked()` — idempotent (erste Sperre bleibt maßgeblich), schreibt Verlaufs-Eintrag (neuer `ActivityType::SyncLock`, Badge rot). Wird von **jeder** Meldeverfahren-Mutation aufgerufen (Phasen 2, 4, 5, 6, 7). - ✅ `PressReleaseImporter`: gesperrte PMs werden **auch bei `--force`** übersprungen (Guard `isSyncLocked()` vor `updateOrCreate`, zählt als „Übersprungen"). - ℹ️ `LegacyPressReleaseController`: kein Guard nötig — die Legacy-API hat heute **keinen Update-Pfad** (`store()` legt immer neu an, `show()` liest nur). Sollte je ein Update-Endpunkt entstehen, gehört der `sync_locked_at`-Check (→ `409`/`423`) dorthin. ### 1.3 Tests ✅ (`tests/Feature/Legal/SyncLockTest.php`, 5 Tests grün) - ✅ `lockSync()`: Felder gesetzt, Verlaufs-Eintrag, idempotent (zweiter Aufruf ändert nichts). - ✅ Importer überspringt gesperrte PM trotz `--force` (Schwärzung bleibt erhalten); ungesperrte PM wird weiter aktualisiert. - ✅ Snapshot: Klartext liegt nie unverschlüsselt in der DB, `purge_after` aus Config, `dueForPurge()`-Scope. - Angrenzende Bestandstests (Importer-Datumslogik, LegalRequest, ActivityLog — 23 Tests) weiter grün; Pint sauber. --- ## Phase 2 — Anonymisierungs-Engine + Admin-Ausführung ✅ **umgesetzt (22.07.2026)** **Ziel:** Der Admin kann aus der bestehenden Queue (`admin/legal-requests/show`) eine DSGVO-Anfrage tatsächlich **ausführen** statt nur „resolved" zu klicken. Das trägt sofort die Referenzfälle Huraux/Gleue/Rambo. ### 2.1 Service `App\Services\PressRelease\AnonymizationService` ✅ - ✅ `preview(PM, Begriffe, Ersetzungswert)`: Pflicht-Vorschau ohne Mutation — Treffer + Nachher-Wert je Feld (`title`, `subtitle`, `text`, `boilerplate_override`, `keywords`; `teaser_*` sind Integer und entfallen). Genutzt von Admin (Phase 2), wiederverwendbar im Self-Service (Phase 5). - ✅ Ersetzung über Wortgrenzen: **Unicode-Lookarounds** (`(?published()->forPortal(...)`; Vorbilder: `tests/Feature/Billing/DepublicationTest.php`, `tests/Feature/LegalRequestTest.php`, `tests/Feature/Admin/AdminPressReleaseActionsTest.php`). - Kritische Invarianten als eigene Tests: (a) DSGVO-Pfad zieht **nie** Credits/Checkout, (b) jede Mutation setzt `sync_locked_at`, (c) Importer/API respektieren die Sperre, (d) ActivityLog enthält keine Klartext-Altwerte, (e) Magic-Link geht nie an Formular-Adresse. - `vendor/bin/sail bin pint --dirty` vor jedem Abschluss. ## Änderungshistorie | Datum | Änderung | |---|---| | 22.07.2026 | Erstfassung auf Basis Ist-Stand-Analyse der Codebasis (Meldeverfahren v0.1, Magic-Link-, Depublizierungs- und Billing-Infrastruktur) | | 22.07.2026 (rev. 2) | Entscheidungen E1–E8 bestätigt und eingearbeitet: Spur D = 10 €, Depublizierung = 35 €; kein Verbrauch bei KI-rot; Reversibilitätsfenster 14 Tage / Snapshot-Frist 30 Tage / Levenshtein 15 % jeweils per Config; Magic-Link nutzt bestehende Mechanik; RSS/Feeds als künftiges Feature vorgemerkt | | 22.07.2026 (rev. 3) | **Phase 1 umgesetzt:** 3 Migrationen, `lockSync()`/`isSyncLocked()`, Importer-Guard (auch `--force`), `LegalRequestType`-Cases + `LegalRequestVerificationMethod`, Snapshot-Modell mit `capture()`/Factory, `ActivityType::SyncLock`, Config E3–E5 in `config/legal.php`, 5 neue Pest-Tests grün. Erkenntnisse: Legacy-API hat keinen Update-Pfad (API-Guard entfällt vorerst); Spalten-Adds an `press_releases` wegen FULLTEXT-Index immer als ein kombiniertes ALTER (voller Tabellen-Copy, kein INSTANT) | | 22.07.2026 (rev. 4) | **Phase 2 umgesetzt:** `AnonymizationService` (Unicode-Wortgrenzen-Replace, Pflicht-`preview()`, transaktionales `execute()` mit Snapshot/Kontakt-Detach/Bild-Löschung/Sperre/Sitemap-Invalidierung), Admin-Queue-Panel Anonymisieren/Depublizieren mit Pflicht-Vorschau, `ActivityType::Anonymization`, `snapshots:purge` + Scheduler, 9 neue Pest-Tests grün | | 22.07.2026 (rev. 5) | Admin-Schwärzungs-Editor: kompletter PM-Inhalt als kopierbare Vorschau (vorher/nachher in einem Block), Ersetzungswert vorbefüllt. **Phase 3 umgesetzt:** Intake `melden/pm` (Kosten-Tabelle vorab, Honeypot, IP+PM-Rate-Limits), `createReportLink` + Enum-Migration `press_release_report`, Mailable, atomare Verifikation mit Session-Marker, Anliegen-Auswahl erzeugt verifizierte Queue-Anfragen (Bremse 1 offene Anfrage/PM/Typ), toter-Kanal-Fallback auf manuelles Formular, Owner-Einstieg aus dem Kunden-Panel, Lang-Keys de/en, 10 neue Pest-Tests grün | | 22.07.2026 (rev. 6) | **Phase 4 umgesetzt (panel-first, Entscheidung: sofort live + rückgängig machbar):** `CorrectionService` mit Hybrid-Levenshtein-Gate (Zeichen kurz / Wörter lang, strip_tags-Basis), Panel-Abschnitt „Änderung & Korrektur" über der Depublikation (ein Einstieg, System stuft klein/groß ein; groß = Interim-Queue-Anfrage bis Phase-7-Checkout), Wochen-Bremse per Config, Snapshot-Restore im Admin (Korrektur + Anonymisierung), `ActivityType::Correction`, `WebCaches::forgetSitemaps()` extrahiert, 11 neue Pest-Tests grün | | 22.07.2026 (rev. 7) | Konfig verschärft (5 % / 1 Korrektur pro Woche) + Mini-Edit-Ausnahme ≤ 2 Zeichen im Gate; Abschnitt „Stand & was noch fehlt" für den Wiedereinstieg ergänzt; Tagescommit |