- dev/roadmap.md neu: alle offenen Punkte aus Plänen, Audits und Protokollen, gegen Code und Git abgeglichen (Stand v1.7.0) - Abgeschlossenes ins Archiv: 2026-09-01 → archive/2026-09-nutzergewinnung, 2026-09-11-audit → archive/2026-09-audit, lagacy-rechnungen → archive/legacy-rechnungen, Deploy-Protokoll v1.1.0, Umami-Snippets, Go-Live-Roadmap vom 20.07. - Aktiv bleiben eigene Ordner bildrechte/ (Plan Fassung 2, Decision-Update) und changelog/ (Decision-Update Changelog) - dev/README.md als Ordnerkarte neu, Verweise und zwei Code-Kommentare auf die neuen Pfade Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 KiB
Umsetzungs-Audit zur Entwicklung nach dem Audit vom 18.09.2026
Prüfdatum: 25.09.2026
Geprüfter Code: d99f55e5556d601373a1bd251051c015cc9eda91
Vergleich: fb03fb166d08fe891ae5ab3a221c8a232b7c8366..d99f55e5556d601373a1bd251051c015cc9eda91 — 28 Commits, 271 geänderte Dateien, 16.950 hinzugefügte und 644 entfernte Zeilen einschließlich Dokumentation und weiterer Assets.
Grundlagen: letzter Umsetzungs-Audit, Entwicklungsplan mit Block 7 und Entscheidungen E7–E9, Bildkennzeichnungsplan, Fassung 2 einschließlich Fortschreibung vom 25.09., Decision-Update vom 22.09., ursprünglicher Audit.
Auftrag: Änderungen seit dem letzten Audit, deren dokumentierte Umsetzung und die aktuelle Entwicklung prüfen. Ausschließlich diesen neuen Audit erstellen. Keine Änderungen an Anwendung, Tests, Konfiguration, Datenbestand oder vorhandenen Dokumenten.
1. Ergebnis und Einordnung
Die Entwicklung hat erhebliche Fortschritte gemacht. Mehrere Restfehler des letzten Audits sind gezielt korrigiert: automatisch erworbene Firmenrechte werden gemäß der revidierten Entscheidung entzogen, ungenutzte Fortify-Verwaltungsrouten sind gesperrt, Kontingent-Altwerte werden konsistent übernommen und API-Portalwechsel berücksichtigen vorhandene Kategorien. Die neuen Bildfunktionen haben gemeinsame Services, private Originale, versionierte Bestätigungen, Vorprüfungen, Korrekturprotokolle und ein separates verschlüsseltes Archiv erhalten. Die Aufteilung in dokumentierte Entwicklungsblöcke hilft bei der Nachverfolgung.
Eine vollständige fachliche Abnahme der neuen Abläufe ist dennoch nicht gerechtfertigt. Die meisten verbleibenden Probleme liegen zwischen einzelnen, für sich plausiblen Schritten:
- Die öffentliche Bildauslieferung berücksichtigt die neu eingeführten Bildfreigaben nicht durchgängig. Ein Admin-Upload an einer bereits veröffentlichten Meldung kann vor Kundenbestätigung erscheinen; ein zurückgehaltenes Bild bleibt über die Medienroute abrufbar.
- Die Archivlöschung kann einen inzwischen gesetzten Legal Hold übergehen. Fehlgeschlagene Dateilöschungen können außerdem als erfolgreiche Löschung protokolliert werden.
- KI-Ergebnis und zugehöriger Audit werden getrennt gespeichert. Dadurch bleibt trotz der neuen Hashprüfungen ein enges Fenster für eine automatische Veröffentlichung eines ungeprüften Inhaltsstands.
- Die Kundenbestätigung, Veröffentlichungssnapshots und einzelne Anonymisierungswege erfüllen die dokumentierten Anforderungen noch nicht vollständig.
- Neue beziehungsweise weiterentwickelte Auswertungen verwenden teilweise unterschiedliche Grundgesamtheiten oder lückenhafte Zeitintervalle.
- Die vorhandene Schutzprüfung gegen eine falsche Testdatenbank greift erst nach dem Laravel-Testaufbau und damit für
RefreshDatabasezu spät.
Dieser Bericht enthält 22 priorisierte Befunde: 4 × P1 und 18 × P2. Die Anzahl bezeichnet weder 22 neue Regressionen noch 22 beobachtete Produktionsvorfälle. Enthalten sind neue Fehler, präzisierte Restbefunde und im aktuellen Zusammenhang erstmals erkannte Bestandsfehler. Parallelitäts- und Storage-Fehlerfälle sind anhand ihrer konkreten Ausführungsreihenfolge belegt, in diesem Audit aber nicht dynamisch reproduziert worden. N21 setzt ausdrücklich einen veralteten Cachewert voraus; N22 setzt eine tatsächlich falsch aufgelöste Datenbankkonfiguration voraus.
Bewertungsmaßstab
| Kennzeichnung | Bedeutung |
|---|---|
| P1 | Vor Abnahme beziehungsweise Aktivierung des betroffenen Sicherheits-, Veröffentlichungs-, Lösch- oder Testablaufs beheben. |
| P2 | Konkreter Fehler bei einem nachvollziehbaren Eingabe-, Zustands-, Fehler- oder Parallelitätsfall; gezielt nacharbeiten. |
| Im Code umgesetzt | Änderung und vorhandene Absicherung sind nachvollziehbar; kein unabhängig bestätigter grüner Testlauf dieses Audits. |
| Teilweise | Wesentliche Umsetzung vorhanden, aber ein fachlicher Endzustand oder Fehlerfall bleibt offen. |
| Bewusst aufgeschoben | Nach der aktuellen Entscheidung nicht Bestandteil der jetzt geschuldeten Umsetzung. |
| Betriebsnachweis offen | Code, lokale Dokumentation oder die angebundene Datenbank beweisen den Produktivzustand nicht. |
Die Bewertung des Beweisarchivs und der Bestätigungen ist ein technischer Abgleich mit den projektintern beschlossenen Regeln. Sie ist keine Prüfung der rechtlichen Wirksamkeit von AGB, Aufbewahrungsfristen oder Einwilligungen.
2. Vorgehen in abgeschlossenen Prüfschritten
- Vergleichsbasis und Entscheidungen: letzten Audit, Block 7, Commitfolge, aktuelle Bildplanung und Decision-Update abgeglichen; ältere Entscheidungen von späteren Änderungen getrennt.
- Restbefunde U01–U13: betroffene Services, Jobs, Policies, Middleware, Migrationen und Regressionstests erneut gelesen.
- Bildaufnahme: Formular, API, Admin-Upload, Dateiprüfung, Originalspeicherung, Metadatenauswertung, Vision-Vorprüfung und Rechtebestätigung verfolgt.
- Bildausgabe und Lebenszyklus: Einreichung, Veröffentlichung, direkte Medienroute, Credits, OpenGraph, Snapshot, Korrektur, Entfernung und Entwurfspurge als zusammenhängende Abläufe geprüft.
- Archiv und Bestätigungen: Verschlüsselung, Zugriffswege, Legal Hold, Fristlöschung, Tombstones, Restore-Abgleich und Anbindung an Anonymisierung/Korrektur geprüft.
- Feeds, Canonical und Berichte: Feed-Auswahl, Sichtbarkeitsangaben, Reader-Gruppierung, Canonical-Lebenszyklus, Alterskohorten und Indexverteilung geprüft.
- Entwicklungsqualität und Betrieb: dokumentierte Teststände mit ihrem jeweiligen Zeitpunkt abgeglichen; Testaufbau und installierten Laravel-Code gelesen; ausschließlich lesende Umgebungs- und Datenbanknachweise eingeholt.
- Konsolidierung: Befunde zusammengeführt, Doppelzählungen gleicher Ursachen reduziert und konkrete Abnahmetests je Befund formuliert.
Die Laravel-Review-Regeln aus .agents/skills/laravel-best-practices/SKILL.md wurden angewendet. Restbefunde im Backend, Archiv und Feed-/Berichtsbereich wurden zusätzlich getrennt gegengelesen. Framework-Verhalten wurde anhand der installierten Quellen und der versionsbezogenen Boost-Dokumentation geprüft.
2.1 Maßgebliche Entscheidungen
- E7 ersetzt die frühere Entzugsentscheidung E2: Eine rein automatisch erzeugte Firmenzuordnung wird beim Wegfall der Grundlage entfernt. Eine automatisch hochgestufte manuelle Mitgliedschaft darf auf ihre Teamrolle zurückfallen.
membermuss deshalb nicht nachträglich zur Leserolle umgebaut werden. - E8: Nicht verwendete native Fortify-2FA-Verwaltungsrouten werden abgeschaltet; die Challenge bleibt erhalten.
- E9: U05–U07 sind bis zur Nutzung des Newsletter-Kampagnenmoduls ausdrücklich aufgeschoben. Das ist keine neue Regression und keine pauschale Sperre des gesamten Projekts.
- Bildplan Fassung 2 und Decision-Update: Unsigned Metadaten liefern in Phase 1 nur eine Vorbelegung. Eine vertrauensgebundene C2PA-Sperre gehört in Block I.
algorithmicallyEnhancedsoll keinen öffentlichen KI-Hinweis erzeugen. - O1 im fortgeschriebenen Bildplan: Bilder nie veröffentlichter, abgelehnter Einreichungen bleiben zunächst für die Überarbeitung erhalten. Sie sollen später beim Purge beziehungsweise beim Löschen entfernt werden. Die ältere Forderung nach sofortiger Löschung bei jeder Ablehnung wird hier nicht als aktuelle Sollvorgabe verwendet.
- O2: Zustimmung je Konto und Dokumentversion mit gesondertem API-Endpunkt und HTTP 428 vor einer Einreichung ohne erforderliche Annahme.
- Block I und J: C2PA-Verifikation und Newsroom-Galerien sind spätere Arbeitspakete. Ihr Fehlen ist kein Umsetzungsfehler dieser Phase.
- Block K: Unbrauchbare Modellantworten sollen als Fehler protokolliert werden und den Upload passieren lassen. Das Modell soll keine verbindliche Bildrechteprüfung und keine automatische Änderung der KI-Kennzeichnung vornehmen.
2.2 Grenzen der Verifikation
Der alte Entwicklungsplan dokumentiert 1.930 grüne Tests und einen übersprungenen Test nach Block 7, PHPStan ohne Meldung und saubere Formatierung. Der Bildplan dokumentiert 2.037 grüne Tests nach Block E sowie spätere gezielte Testzahlen für F, G/H, K und die Adminansicht. Daraus folgt kein bestätigter Gesamtlauf auf dem hier geprüften HEAD vom 25.09.
vendor/bin/sail ps meldete, dass Docker oder Podman nicht läuft. Der gezielte Versuch
vendor/bin/sail artisan test --compact tests/Feature/Images/PressReleaseImageUploaderTest.php tests/Feature/Images/ImageLifecycleTest.php tests/Feature/Archive/EvidenceArchiveTest.php
endete ebenfalls vor dem Teststart mit Exit-Code 1 und Docker or Podman is not running. Es wurden keine Tests als bestanden gewertet und keine Umgehung über direktes PHP verwendet. PHPStan, Build, Formatter, Browserbedienung, reale parallele MySQL-Prozesse und Queue-Abbrüche wurden nicht neu ausgeführt. Es wurden auch keine zusätzlichen Testdateien oder Prüfscripts angelegt.
Die reguläre Testkonfiguration setzt SQLite :memory: und eine synchrone Queue. Sie bildet weder MySQL-Sperrverhalten noch tatsächliche Queue-Zustellung vollständig ab. Zudem korrigiert N22 die zu positive Aussage des letzten Audits, der zusätzliche Guard schütze zuverlässig vor einer gecachten Anwendungsdatenbank: Seine Position im Testaufbau erfüllt dieses Ziel nicht.
Boost war für Anwendungsinformationen, Dokumentationssuche und lesende Datenbankabfragen erreichbar, obwohl Sail-Befehle nicht ausführbar waren. Diese beiden Nachweiskanäle sind getrennt zu bewerten.
3. Abgleich des letzten Audits und der neuen Entwicklungsblöcke
3.1 Sämtliche Restbefunde U01–U13
| Alt-ID | Ergebnis dieser Nachprüfung | Begründung / verbleibender Punkt |
|---|---|---|
| U01 | Im Code behoben gemäß E7 | ContactAccessService entfernt alte auto-Zuordnungen und unterscheidet auto-upgrade. Tests prüfen Entzug, eigene Autorenschaft und erhaltene manuelle Rechte. |
| U02 | Teilweise behoben | Ergebnis-UPDATE ist an den Hash gebunden; automatisches Publish prüft unter Sperre. Ergebnis und Audit entstehen aber getrennt, siehe N03. |
| U03 | Ursprünglicher Ablauf behoben; Parallelitätsrest | Rote Review-Rechecks werden jetzt abgelehnt. Ändert sich der Inhalt nach dem Ergebnis-UPDATE, kann die neue Ablehnung den falschen Stand treffen, siehe N04. |
| U04 | Im Code behoben | Positive NULL-Altverbräuche werden in der Migration an den Monat gebunden; der Service zählt NULL im laufenden Monat korrekt weiter. Migration in der angebundenen Datenbank eingetragen. |
| U05 | Offen, bewusst aufgeschoben nach E9 | Vollständig fehlgeschlagene Kampagne endet weiterhin als Sent; Resume verlangt Sending. Vor Nutzung des Kampagnenmoduls bearbeiten. |
| U06 | Offen, bewusst aufgeschoben nach E9 | Harter Abbruch während des Empfängeraufbaus kann weiterhin unvollständigen Sending-Bestand hinterlassen. |
| U07 | Offen, bewusst aufgeschoben nach E9 | Fehler nach Transportübergabe können weiterhin den Empfängerclaim freigeben und Wiederholung auslösen. |
| U08 | Im Migrationscode behoben | Dublettenbereinigung bevorzugt Sent statt ausschließlich kleinster ID; Zustandskombinationen sind getestet. Migration in der angebundenen Datenbank eingetragen. |
| U09 | Im Code behoben gemäß E8 | Middleware sperrt sieben native Fortify-Verwaltungsrouten auch bei passwortbestätigter Session. Challenge bleibt erreichbar. |
| U10 | Ursprünglicher Fehler im Code behoben | Tatsächlicher Portalwechsel prüft auch die unverändert mitgeführte Kategorie; Tests decken unzulässige, passende und gemeinsame Kategorien ab. |
| U11 | Teilweise behoben | Vertragsbeendigung und Activity-Events sind unter Sperre erneut geprüft. Benachrichtigungsphase bleibt mehrfach einplanbar, siehe N14. |
| U12 | Ursprünglicher Verlust vor Einplanung behoben | Persistente offene Marker erlauben Wiederaufnahme nach Queue-Fehler. Einmalige Einplanung ist dadurch noch nicht garantiert, siehe N14. |
| U13 | Doppelzählung behoben; bedingte Wiederanlauflücke | Zeitbudget, DB-Markenvergleich und transaktionale Fortschreibung verhindern den alten Doppelzählungsfall. Ein veralteter Cachewert kann Folgeläufe stoppen, siehe N21. |
Die ursprünglichen F01–F28 sind im Audit vom 18.09. vollständig zugeordnet. Dieser Bericht schreibt deren relevante Restpunkte über U01–U13 fort; unveränderte Bereiche werden nicht allein wegen ihres Alters erneut als fehlerhaft eingestuft.
3.2 Neue Entwicklung und Umsetzungsqualität
| Bereich | Nachvollziehbar umgesetzt | Abnahmegrenze |
|---|---|---|
| A: Datenmodell und Uploader | Gemeinsamer v2-Uploader, privates Original mit Hash, unveränderliche Bestätigungstexte, getrennte Herkunfts- und Kundenangabe, Upload-Grenzen. | Zustandsprüfung im letzten Lock und Fehlerbehandlung nach Commit fehlen, N07/N08. |
| B: Kunden-/Adminoberfläche | v2-Manager, explizite Angaben, Magic-Link-Anforderung, Nachweisdetails und Admin-Korrekturwege. | Bestätiger und zulässiger PM-Status nicht passend autorisiert, N05; angezeigte Textversion nicht gebunden, N06. |
| C: API | v2-Pflichtfelder, gemeinsamer Service, explizite Textversion, Legacy-Bildupload abgeschaltet. | Statuswechsel während Upload/Prüfung, N07. |
| D: Öffentliche Kennzeichnung | Gemeinsame Credit-Erzeugung, Herkunft der Angaben, sichtbare Nachweiszeilen und Ausgabeanpassungen. | Öffentliche Bildfreigabe nicht einheitlich, N01. |
| E: Lebenszyklus | Publikationsmarker, Snapshot-Service, normaler Entfernungsweg, Backfill/Bereinigungsbefehle, private Nachweisroute. | Snapshot nicht vollständig konsistent, N09; Anonymisierung und Purge nicht vollständig angebunden, N12/N13. |
| F: Beweisarchiv | Separate Disk und Verschlüsselung, Hashprüfung, Zugriffsprotokoll, Zweckangabe, Fristen, Legal Hold, Tombstones und Restore-Command. | Hold-Race, stille Storage-Fehler und nicht atomare fachliche Anbindung, N02/N10/N11/N12. |
| G: Dokumentannahme | Versionierte Annahmen, gemeinsamer Submit-Trait, API 428 und Annahme-Endpunkt. | Browser bestätigt bei Versionswechsel unter Umständen ungesehenen neuen Text, N06. |
| H: OG und Teaser | Eigene OG-Variante mit eingebranntem Nachweis, Neuaufbau bei Korrektur, Teaser-Nachweise, lokale Schriftdateien. | Snapshot-/Metadatenübereinstimmung nicht gesichert, N09; Cache- und Ressourcenverhalten zusätzlich prüfen. |
| K: Bildvorprüfung | Verkleinerte, bereinigte Prüffassung, Stufenmodell, Hashbindung des Ergebnisses, Queue/Reject/Hint/Error, keine Modell-Sperre der KI-Angabe. | Antwortschema unvollständig validiert, N20; Laufzeit unter realen Uploads ungemessen. |
| Admin-Bilderliste vom 25.09. | Liste und zugehörige Tests vorhanden; gezielte UI-Fortschreibung. | Browser-/Tastaturabnahme nicht wiederholt; gemeinsame Bild- und Archivfehler wirken auch hier. |
| RSS und Feed-Protokoll | Feed-Auswahl begrenzt, Soft Deletes/Sichtbarkeit berücksichtigt, Newsroom-Prüfung vor Cache, Reader-Erkennung und Aufbewahrung ergänzt. | Reader-Zusammenfassung vermischt Feed-Identitäten, N16. |
| Canonical und Sichtbarkeit | Gemeinsamer Resolver, Zuordnung, Cache-Invalidierung/Warmup und Tests erweitert. | Lebenszyklus nach Wegfall der Erstquelle und Kundenanzeige inkonsistent, N17/N19. |
| Auswertungen | Altersreferenz korrigiert, Indexverteilung und Quellenbefund erweitert. | Kohortenlücken und unterschiedliche Grundgesamtheiten, N15/N18. |
4. Priorisierte Rest- und Folgebefunde
N01 — P1: Öffentliche Bildauslieferung umgeht den neuen Freigabestatus
Belege: app/Models/PressReleaseImage.php:281, app/Http/Controllers/Web/PressReleaseImageMediaController.php:18, app/Http/Controllers/Web/Concerns/AuthorizesPressReleaseMediaAccess.php, app/Services/PressRelease/Images/PressReleaseImageUploader.php:174, resources/views/livewire/components/press-release-images-manager-v2.blade.php:453.
Befund: publiclyVisible() prüft nur withheld_at, nicht eine ausstehende Rechtebestätigung. Die direkte Medienroute prüft wiederum ausschließlich die Öffentlichkeit der zugehörigen PM und verwendet den Bildfilter überhaupt nicht. Admins dürfen Bilder an bereits veröffentlichten Meldungen ergänzen; ihr Upload setzt bewusst noch keine Kundenbestätigung.
Konkrete Fälle: Ein Admin lädt ein unauffälliges Bild an einer öffentlichen PM hoch. rights_confirmed_at bleibt NULL, withheld_at ebenfalls. Das Bild kann bereits in der öffentlichen Ausgabe erscheinen. Wird ein Bild dagegen zur Prüfung zurückgehalten, verschwindet es aus der gefilterten Seitenansicht, bleibt aber unter seiner bekannten direkten Medienadresse öffentlich abrufbar. Der bestehende öffentliche Cache-Header kann eine bereits ausgelieferte Fassung zusätzlich weiter verfügbar halten.
Auswirkung: Die im Plan zugesagte Sperre bis zur Kundenbestätigung beziehungsweise Freigabe wird nicht über alle Ausgabewege durchgesetzt. Der Befund betrifft ausgelieferte Bildvarianten; er behauptet keinen öffentlichen Zugriff auf das separat gesicherte Original.
Empfehlung: Einen gemeinsamen fachlichen Zustand für öffentlich auslieferbare Bilder definieren und in Seitenabfragen, OG-Auswahl und Mediencontroller anwenden. Für v2 müssen Bestätigung und fehlende Zurückhaltung zusammenpassen; der bewusst anders behandelte Altbestand muss explizit bleiben. Den Fall „Bild zu bereits öffentlicher PM hinzufügen“ als eigene Freigabe behandeln.
Abnahmetest: Admin-Upload an Published-PM ist vor Kundenbestätigung auf Seite, OG und direkter Medienroute gesperrt. Ein zuvor öffentliches, danach zurückgehaltenes Bild ist bei neuen Serverabrufen gesperrt. Berechtigte interne Vorschau bleibt gesondert testbar; bestätigte freigegebene Bilder funktionieren weiter.
N02 — P1: Archiv-Purge kann einen inzwischen gesetzten Legal Hold übergehen
Belege: app/Services/Archive/EvidenceArchive.php:164, :195, :269; vorhandene Hold-Tests in tests/Feature/Archive/EvidenceArchiveTest.php.
Befund: Der Purge lädt fällige Einträge vorab. tombstone() verwendet anschließend die geladenen Instanzen ohne lockForUpdate() und ohne erneute Prüfung von Hold, Frist oder Löschzustand. setLegalHold() schreibt unabhängig davon.
Ablauf: Purge selektiert einen fälligen Eintrag → Admin setzt erfolgreich Legal Hold → Purge arbeitet mit dem alten Objekt weiter, löscht die Payload und setzt deleted_at. Die Zeile kann anschließend zugleich Legal Hold und eine bereits gelöschte Payload ausweisen.
Auswirkung: Irreversibler Verlust eines Nachweises trotz inzwischen aktivierter Aufbewahrungssperre. Ein vor dem Purge gesetzter Hold funktioniert; genau der konkurrierende Übergang fehlt in der Absicherung.
Empfehlung: Hold-Änderung und Löschentscheidung über dieselbe Zeilensperre koordinieren. Vor der Dateilöschung den aktuellen Datensatz und sämtliche Löschbedingungen erneut prüfen. Eine Transaktion um ein unverändert altes Objekt genügt nicht.
Abnahmetest: Zwei getrennte DB-Verbindungen: Auswahl durch Purge, danach bestätigter Hold, anschließend Fortsetzung des Purge. Payload und aktiver Eintrag müssen erhalten bleiben. Auch Fristverlängerung und zwei Purge-Läufe gegeneinander testen.
N03 — P1: Erste KI-Freigabe ist zwischen Ergebnis-UPDATE und Audit nicht atomar
Belege: app/Jobs/ClassifyPressRelease.php:103, :145; app/Services/PressRelease/PressReleaseService.php:307, :393; app/Console/Commands/PublishScheduledPressReleases.php:66.
Bezug: Präzisierter Rest von U02; die neu eingebauten Hashprüfungen sind grundsätzlich eine Verbesserung.
Befund: Das bedingte UPDATE speichert die grüne Klassifikation für H1. Der Audit mit dem zugehörigen Hash wird danach separat angelegt. Die Veröffentlichung akzeptiert einen fehlenden Audit-Hash aus Gründen der Legacy-Kompatibilität.
Ablauf: Bei einer fälligen, erstmals klassifizierten Review-PM schreibt der Job Grün für H1. Vor dem Audit-INSERT speichert ein Editor H2. Der Scheduler findet die grüne PM. Mangels Audit-Hash passiert H2 die Prüfung und kann veröffentlicht werden, sofern die übrigen Gates erfüllt sind. Erst danach entsteht der H1-Audit.
Auswirkung: Ein neuer Zwischenzustand wird wie ausdrücklich erlaubter auditloser Altbestand behandelt. Das Problem ist damit nicht nur eine Kritik an der dokumentierten Legacy-Ausnahme.
Empfehlung: Ergebnis und zugehörigen Prüfnachweis gemeinsam atomar speichern. Automatische Freigabe an einen eindeutig aktuellen, persistenten Nachweis binden; Legacy-Fälle separat kennzeichnen. Dasselbe Schreibmuster beim Content-Score mitprüfen.
Abnahmetest: Änderung nach erfolgreichem Ergebnis-UPDATE, aber vor Audit-INSERT; zeitgleich Scheduler-Publish ausführen. H2 darf nicht mit H1-Freigabe erscheinen. Der bestehende Test für eine Änderung vor dem UPDATE deckt dieses Fenster nicht ab.
N04 — P2: Roter Recheck kann inzwischen korrigierten Inhalt ablehnen
Belege: app/Jobs/ClassifyPressRelease.php:157, :187; app/Services/PressRelease/PressReleaseService.php:206, :519.
Bezug: Ursprünglicher Stillstand aus U03 behoben; der neue automatische Ablehnungsweg hat eine zusätzliche Parallelitätslücke.
Befund und Ablauf: Rot für H1 wird erfolgreich gespeichert. Danach korrigiert ein Editor den Text zu H2. Der Recheck lädt die PM neu, prüft aber nur Review und Manual Hold. reject() prüft unter seiner Sperre ebenfalls nur den Status, nicht den erwarteten Inhalts-Hash. H2 wird mit der Begründung für H1 abgelehnt. Ein später grüner route:false-Recheck hebt diese Ablehnung nicht automatisch auf.
Auswirkung: Korrigierte Einreichungen landen fälschlich im Ablehnungszustand; Status und Prüfgrund beziehen sich auf verschiedene Fassungen.
Empfehlung: Den erwarteten Hash bis in die automatische Ablehnungs-/Hold-Entscheidung mitführen und dort unter Sperre prüfen. Überholte Entscheidungen verwerfen und den aktuellen Stand neu bewerten.
Abnahmetest: Editoränderung nach rotem Ergebnis-UPDATE, vor Reject-Lock. Anschließend grünen Recheck ausführen und den endgültigen PM-Status sowie die zugehörige Begründung prüfen.
N05 — P2: Nachträgliche Bildbestätigung prüft die falsche Berechtigung
Belege: resources/views/livewire/customer/press-releases/show.blade.php:117, app/Policies/PressReleasePolicy.php:39, :98, app/Services/PressRelease/Images/CustomerImageConfirmation.php:33, :74.
Befund: Der Magic-Link wird an den Kontoinhaber der Meldung geschickt. Die eigentliche Aktion verlangt jedoch die allgemeine PM-Bearbeitungsberechtigung. Diese erlaubt Teammitgliedern die Bearbeitung von Draft/Rejected/Review, verweigert normalen Kunden aber Published. Der Bestätigungsservice selbst vergleicht den Kunden nicht mit dem vorgesehenen Kontoinhaber.
Konkrete Fälle: Der Kontoinhaber folgt dem angeforderten Bestätigungslink für ein Admin-Bild an seiner bereits veröffentlichten PM und erhält beim Bestätigen 403. Ein anderes berechtigtes Firmenmitglied kann dagegen bei einer nicht veröffentlichten PM die Aktion ausführen, obwohl die dokumentierte Bestätigung an den Kontoinhaber adressiert ist.
Auswirkung: Der erlaubte Admin-Bildtausch kann vom richtigen Kunden nicht abgeschlossen werden; zugleich ist die Identität des vorgesehenen Bestätigers nicht durchgängig abgesichert.
Empfehlung: Eine eigene Berechtigung für die konkrete Bildbestätigung einführen: erwarteter Kunde, konkrete PM/Bildzuordnung, ausstehende Bestätigung und geeigneter Zustand. Falls vertretungsberechtigte Teammitglieder fachlich erwünscht sind, diese Regel ausdrücklich ändern und protokollieren; nicht zufällig von update erben.
Abnahmetest: Richtiger Kontoinhaber bestätigt einen Admin-Upload an Published-PM erfolgreich; fremdes Firmenmitglied und anderer Kunde scheitern nach der geltenden Regel; bereits bestätigtes oder inzwischen ersetztes Bild lässt sich nicht erneut als aktuelles Bild bestätigen.
N06 — P2: Browserbestätigungen sind nicht an die angezeigte Textversion gebunden
Belege: resources/views/livewire/components/press-release-images-manager-v2.blade.php:230, :333; resources/views/livewire/customer/press-releases/show.blade.php:137; app/Livewire/Concerns/InteractsWithAiDeclaration.php:63; app/Services/Legal/LegalAcceptanceGate.php.
Befund: Die Oberfläche zeigt den beim Rendern aktuellen Bestätigungstext. Beim Speichern liest der Server jedoch erneut current() beziehungsweise die dann erforderlichen Dokumente und verbindet diese mit einem bloßen Bestätigungsboolean. Die vorher angezeigte Version wird nicht als verbindlicher Zustand mitgeführt.
Ablauf: Kunde öffnet den Dialog mit Version A. Zwischenzeitlich wird B aktiviert. Kunde bestätigt den noch sichtbaren Text A. Der Server dokumentiert B. Das betrifft Bild-Upload, nachträgliche Bildbestätigung und das allgemeine Submit-Zustimmungsgate. Die API mit ausdrücklich angegebener Version ist hier die bessere vorhandene Vorlage.
Auswirkung: Der gespeicherte Annahmenachweis kann einen Wortlaut ausweisen, den die Person nicht gesehen hat. Dies ist ein Fehler in der technischen Nachweisbindung unabhängig von einer rechtlichen Bewertung.
Empfehlung: Angezeigte IDs/Versionen gegen Manipulation geschützt mitführen, beim Submit auf Aktualität vergleichen und bei Versionswechsel den neuen Wortlaut erneut bestätigen lassen. Zusätzlich fehlende verpflichtende Dokumente als Konfigurationsfehler behandeln: Das aktuelle filter() entfernt nicht auflösbare Dokumenttypen sonst aus der Anforderung.
Abnahmetest: Dialog mit A rendern, B aktivieren, alten Dialog absenden. Es darf keine Annahme von B entstehen. Nach erneutem Anzeigen und Bestätigen muss B korrekt gespeichert werden. Fehlendes Pflichtdokument darf bei aktivem Gate nicht still passieren.
N07 — P2: Langer Upload kann den erlaubten PM-Status überholen
Belege: app/Http/Controllers/Api/V1/PressReleaseImageController.php:47, resources/views/livewire/components/press-release-images-manager-v2.blade.php:197, app/Services/PressRelease/Images/PressReleaseImageUploader.php:110, :153.
Befund: Formular/API prüfen den erlaubten Bearbeitungsstatus vor Vorprüfung, Originalspeicherung und Variantenerzeugung. Unter der abschließenden PM-Sperre prüft der gemeinsame Uploader nur, ob bereits ein Bild existiert. Der inzwischen gültige Status und die aktorspezifische Erlaubnis werden nicht erneut geprüft.
Ablauf: Kunde startet einen Upload an einer Draft-PM ohne Bild. Während der Bildprüfung wird die PM über einen zweiten Request eingereicht und gegebenenfalls veröffentlicht. Danach hängt der Upload das Bild an den inzwischen anderen Zustand. Ein ausstehender Bildprüfungsfall kann so erst nach Durchlaufen des Publish-Gates entstehen. Beim Anhängen an eine bereits veröffentlichte Meldung wird im Uploader auch kein Veröffentlichungssnapshot erzeugt.
Auswirkung: Die Regel „Kunden dürfen Bilder nur bei Draft/Rejected ändern“ ist unter Parallelität nicht zuverlässig. Veröffentlichungsnachweis und Bildfreigabe können dem tatsächlichen Bildbestand hinterherlaufen.
Empfehlung: Unter dem vorhandenen Lock den aktuellen Status und die passende Actor-Regel erneut validieren. Admin-Änderungen an Published explizit von Kundenänderungen trennen und den erforderlichen Publikations-/Bestätigungsablauf auslösen.
Abnahmetest: PM während der simulierten Vorprüfung auf Review/Published wechseln lassen. Der Kunden-Upload muss ohne verbleibende Dateien/Zeilen abbrechen. Ein erlaubter Admin-Fall benötigt dagegen den vorgesehenen vollständigen Folgeablauf.
N08 — P2: Cachefehler nach Commit löscht ein bereits gespeichertes Bildoriginal
Belege: app/Services/PressRelease/Images/PressReleaseImageUploader.php:153, :214, :221; app/Support/WebCaches.php.
Befund: Der Uploader beendet die Datenbanktransaktion und invalidiert anschließend innerhalb desselben äußeren try öffentliche Caches. Jeder dort auftretende Throwable löst die allgemeine Dateibereinigung aus. Diese entfernt Derivate und das Original, obwohl Bildzeile und gegebenenfalls Bestätigung bereits committed sind.
Ablauf: Upload an einer veröffentlichten PM wird gespeichert → Cache-Backend schlägt bei Invalidierung fehl → Catch löscht die zugehörigen Dateien → DB verweist weiter auf diese Dateien.
Auswirkung: Ein vorübergehender Cachefehler wird zu dauerhaft fehlenden Medien und verlorenem Beweisoriginal. Ein normaler Request-Retry kann zudem am bereits vorhandenen Bild scheitern.
Empfehlung: Bereinigung vor einem erfolgreichen Commit von nachgelagerten Aktionen trennen. Nach Commit keine Dateien des erfolgreich gespeicherten Bildes durch allgemeines Fehlerhandling löschen; Cache-Invalidierung separat wiederholbar behandeln.
Abnahmetest: Cache-Invalidierung nach erfolgreichem DB-Commit gezielt werfen lassen. Persistierter Bilddatensatz und Original müssen konsistent erhalten bleiben; ein erneuter Invalidierungsversuch muss möglich sein.
N09 — P2: Veröffentlichungssnapshot kann veraltet oder nur scheinbar vorhanden sein
Belege: app/Services/PressRelease/Images/PublishedImageSnapshot.php:43, :61, :79; app/Services/PressRelease/Images/ImageCorrectionService.php:103; config/filesystems.php:33.
Befund: Die Deduplizierung verwendet ausschließlich den Hash der Cover-Bildbytes. Nach einer Credit-/KI-Korrektur bleiben diese Bytes gleich; capture() kehrt früh zurück, obwohl der JSON-Nachweis eine andere Kennzeichnung enthalten müsste. Der neu aufgebaute OG-Nachweis und der gespeicherte Snapshot können auseinanderlaufen. Zusätzlich werden beide put()-Rückgaben ignoriert, bevor Pfad und Hash als erfolgreich gespeichert markiert werden.
Konkrete Fälle: Nur den Credit eines veröffentlichten Bildes ändern: öffentliche Ausgabe/OG ändern sich, Snapshot-JSON bleibt alt. Oder die lokale Disk liefert bei Bild/JSON-Schreiben false: Der Datensatz behauptet einen Snapshot; ein späterer Aufruf kann wegen identischem Hash ohne Reparatur abbrechen.
Auswirkung: Der Nachweis beschreibt nicht zuverlässig die aktuelle veröffentlichte Fassung beziehungsweise verweist auf unvollständige Dateien.
Empfehlung: Bildbytes und nachweisrelevante Metadaten gemeinsam versionieren. Beide Dateien erfolgreich schreiben und verifizieren, bevor die DB-Marke gesetzt wird. Fehlende oder unvollständige Snapshots beim erneuten Aufruf reparieren; historische Fassungen bei Bedarf getrennt erhalten.
Abnahmetest: Reine Credit- und KI-Korrektur muss passenden neuen Nachweis erzeugen. Jeweils put(false) für Bild und Sidecar simulieren; kein falscher Erfolgsmarker, anschließend erfolgreicher Wiederholungsversuch.
N10 — P2: Archiv meldet fehlgeschlagene Löschung und Tombstone-Export als Erfolg
Belege: app/Services/Archive/EvidenceArchive.php:225, :253, :269; config/filesystems.php:52; app/Console/Commands/ArchiveExportTombstones.php, app/Console/Commands/ArchivePurge.php.
Befund: Die Archive-Disk arbeitet mit throw=false. Beim Purge wird delete() nicht ausgewertet, anschließend aber deleted_at gesetzt. Beim Restore-Abgleich steigen Erfolgszähler ebenfalls unabhängig vom Löschresultat. Der Export des Tombstone-Stands ignoriert put() und meldet trotzdem die Anzahl exportierter Einträge.
Auswirkung: Eine Datei kann physisch weiter existieren, während das Archiv ihre Löschung behauptet und sie künftig vom regulären Purge ausschließt. Ein fehlender aktueller Tombstone-Export kann beim Restore unbemerkt bleiben. Scheduler-Fehlerbehandlung auf Basis des Exit-Codes greift bei solchen scheinbaren Erfolgen nicht.
Empfehlung: Dateioperationen ausdrücklich auswerten und Fehler als Fehler behandeln. Tombstone und Erfolgszähler erst nach bestätigter Löschung setzen; Teilfehler wiederholbar halten. Export atomar ersetzen und bei Fehlschlag einen Fehlerstatus liefern.
Abnahmetest: delete(false) und put(false) getrennt für Purge, Restore und Export simulieren. Keine falsche Erfolgsmeldung, kein endgültiger Ausschluss einer noch vorhandenen Payload vom Wiederholungsversuch.
N11 — P2: Fachliche Änderung und Archivnachweis können dauerhaft auseinanderfallen
Belege: app/Services/PressRelease/AnonymizationService.php:152, :160; app/Services/PressRelease/CorrectionService.php:155, :185, :239; app/Services/PressRelease/PressReleaseService.php:549, :585; app/Services/Archive/EvidenceArchive.php:54, :78, :82.
Befund: Mehrere fachliche Änderungen werden committed, bevor ihr Archiveintrag erzeugt wird. Scheitert die Archivierung, ist die Änderung bereits wirksam; eine Wiederholung kann wegen des neuen Zustands nichts mehr ändern oder den alten Vorher-Stand nicht mehr rekonstruieren. Umgekehrt schreibt record() die Payload vor der Archivzeile. Ein anschließender DB-Fehler oder äußerer Rollback kann eine nicht registrierte Datei hinterlassen.
Auswirkung: Änderungsereignisse ohne vollständigen Nachweis oder Payloads ohne verwaltete Frist. Eine bloße SQL-Transaktion um sämtliche Schritte würde die Dateisystemoperationen nicht rückgängig machen.
Einordnung: Bei fehlendem Archivschlüssel ist das derzeitige Weiterlaufen ausdrücklich implementiert und getestet. In diesem Audit wurde kein fehlender Schlüssel der Umgebung festgestellt. Auch bei korrekt konfiguriertem Schlüssel bleiben jedoch Storage-/DB-Ausfälle und fehlende Wiederholbarkeit relevant.
Empfehlung: Einen persistenten, idempotenten Archivierungsauftrag mit dem benötigten Vorher-Zustand in der fachlichen Transaktion anlegen. Payload-Erzeugung, Registrierung und Bereinigung verwaister Dateien wiederholbar gestalten. Fachliche Löschung und Archivverfügbarkeit benötigen eine ausdrücklich definierte Fehlerstrategie.
Abnahmetest: Archivdisk beim ersten Versuch ausfallen lassen, danach Wiederholung durchführen: genau ein vollständiger Nachweis mit ursprünglichem Vorher-Stand. Separat DB-Fehler nach erfolgreichem Payload-Schreiben und äußeren Rollback simulieren; keine dauerhaft unverwaltete Datei.
N12 — P2: Anonymisierung entfernt Bilder am gemeinsamen Archiv-Lebenszyklus vorbei
Belege: app/Services/PressRelease/AnonymizationService.php:135, :160; app/Services/PressRelease/Images/PressReleaseImageRemover.php:34; app/Services/Archive/PressReleaseArchiver.php; app/Http/Controllers/Admin/PressReleaseImageEvidenceController.php:42; routes/admin.php:49, :137.
Befund: Die Anonymisierung löscht Bildderivate und setzt direkt Soft Delete. Sie verwendet nicht den neuen Remover, der Original, Snapshot und Bestätigung in das Bildarchiv überführt. Der anschließend erzeugte Anonymisierungsnachweis enthält den Textänderungsstand, ersetzt aber keinen vollständigen Bildnachweis. Lokale Originale und Snapshots bleiben bestehen.
Auswirkung: Für diesen Entfernungspfad fehlen regulärer Bildarchiveintrag und dessen Fristverwaltung. Die zurückbleibenden Nachweise sind bei weiterhin vorhandener PM über die allgemeine Admin-Nachweisroute mit withTrashed() abrufbar, statt ausschließlich über den engeren Archivzugang. Eine öffentliche Auslieferung dieser Originale wird damit nicht behauptet.
Empfehlung: Die Bildentfernung im Anonymisierungsablauf an denselben zustandsabhängigen Lebenszyklus anbinden, mit korrektem Anlass und Bezug zum Antrag. Nach erfolgreicher Überführung lokale Beweisdateien entfernen und Zugriffsregeln konsistent halten.
Abnahmetest: Veröffentlichtes v2-Bild über Anonymisierung entfernen: vollständiger Bildarchiveintrag, passende Frist/Anlass, kein lokales Original mehr, Zugriff ausschließlich über vorgesehenen Archivweg. Nie veröffentlichtes Bild entsprechend der Sofortlöschregel prüfen.
N13 — P2: Aufgeschobene Löschung abgelehnter Uploads hat keinen automatischen Endpunkt
Belege: O1 und E.3 im aktuellen Bildplan; app/Console/Commands/PurgeExpiredPressReleaseDrafts.php:28, :45.
Befund: O1 erlaubt, Bilder abgelehnter, nie veröffentlichter Einreichungen zur Überarbeitung zunächst zu behalten. Der als späterer Bereinigungsweg genannte Command selektiert jedoch ausschließlich Draft, nicht Rejected.
Ablauf: Eine neue Einreichung mit v2-Bild wird abgelehnt und nie wieder bearbeitet. Auch nach Ablauf der regulären Entwurfsfrist bleibt sie im Status Rejected außerhalb der Auswahl. Nur ein zusätzliches manuelles Löschen beendet die Aufbewahrung.
Auswirkung: Die erlaubte vorübergehende Speicherung hat für diesen Zustand keine automatische zeitliche Begrenzung. Das ist nicht die Forderung, die bewusst geänderte Sofortlöschung bei Ablehnung wieder einzuführen.
Empfehlung: Abgelehnte, nie veröffentlichte Einreichungen in eine ausdrücklich definierte Bereinigung aufnehmen; Ausgangsdatum und Frist festlegen. Historische rejected-Datensätze mit früherer Veröffentlichung getrennt behandeln.
Abnahmetest: Alter Rejected-v2-Datensatz ohne frühere Veröffentlichung wird samt Dateien und Bestätigung bereinigt; frischer Rejected-Datensatz bleibt erhalten; ehemals veröffentlichter Altbestand wird nicht fälschlich hart gelöscht.
N14 — P2: Vertragsablauf kann weiterhin doppelte Kundenmails einplanen
Belege: app/Console/Commands/ExpireGrandfathering.php:151, insbesondere Queue-Einplanung vor Marker-UPDATE; tests/Feature/Billing/ExpireGrandfatheringTest.php.
Bezug: Rest von U11 bei grundsätzlich verbesserter Wiederaufnahme aus U12.
Befund: Zwei Läufe können dieselben noch offenen Benachrichtigungsmarker laden. Beide planen die Mail ein und setzen den Marker erst danach. Ein zweiter UPDATE mit null betroffenen Zeilen verhindert die bereits erfolgte Einplanung nicht. Ein Abbruch nach Queue-Insert vor Marker führt ebenfalls zur erneuten Einplanung.
Auswirkung: Doppelte Ablaufbenachrichtigungen, obwohl Vertragsbeendigung und Activity-Event inzwischen korrekt gegen Überlappung abgesichert sind.
Empfehlung: Eindeutig beanspruchbaren persistenten Benachrichtigungsauftrag verwenden und Queue-Einplanung/Zustellung mit einer Idempotenzstrategie verbinden. Ein nachträgliches whereNull() allein löst den Übergang nicht.
Abnahmetest: Zwei Läufe mit gleichzeitig offenen Markern sowie Abbruch unmittelbar nach erfolgreicher Einplanung. Der bestehende Überlappungstest mit bereits gesetztem Benachrichtigungsmarker deckt diese Situationen nicht ab.
N15 — P2: Alterskohorten lassen zwischen ihren Intervallen ganze Tage aus
Belege: app/Services/PressRelease/ViewDistributionReport.php:123; gespeicherte Kohortenauswertung.
Befund: Die untere Datumsgrenze einer Kohorte wird mit reference - (upperDays - 1) gebildet, während die nächste Kohorte erst bei reference - upperDays anschließt. Beispielsweise gehört eine 6,5 Tage alte Meldung weder zu „0–6“ noch zu „7–13“. Dasselbe gilt an den weiteren Übergängen 13/14, 20/21, 27/28, 55/56 und 90/91.
Vorhandener Datenbeleg: Die gespeicherte Auswertung enthält für Presseecho 1.804 Meldungen seit Relaunch, aber nur 1.616 in der Summe der Kohorten: 188 beziehungsweise rund 10,4 % fehlen. Für Businessportal24 stehen 1.847 Meldungen nur 1.651 Kohorteneinträgen gegenüber: 196 beziehungsweise rund 10,6 %. Diese Werte stammen aus dem Repository-Export, nicht aus einer neuen Produktionsmessung.
Auswirkung: Kohortenmittelwerte und Mediane beruhen auf unvollständigen Teilpopulationen. Die neue Korrektur des Referenzdatums gegenüber --to ist sinnvoll, schließt die Intervalllücken aber nicht.
Empfehlung: Lückenlose, nicht überlappende Intervalle verwenden, beispielsweise Alter [0,7), [7,14) usw. Die Summe über alle Kohorten gegen die zugehörige Grundgesamtheit prüfen und gespeicherte Basiswerte danach neu erheben.
Abnahmetest: Werte unmittelbar vor, auf und nach jeder Grenze sowie mittig im bislang fehlenden Tag. Jede zulässige Meldung muss genau einer Kohorte angehören; Kohortensumme muss zur Segmentanzahl passen.
N16 — P2: Reader-Bericht fasst unterschiedliche Portal-/Sprachfeeds zusammen
Belege: app/Services/PressRelease/FeedFetchReport.php:35, zum Vergleich :69.
Befund: byReader() gruppiert nach Reader, Feedtyp und Feed-Schlüssel, aber nicht nach Portal und Sprache. topFeeds() berücksichtigt diese beiden Dimensionen dagegen.
Beispiel: Derselbe Reader meldet 12 Abonnenten für den deutschen Portalfeed von Portal A und 20 für den entsprechenden Feed von Portal B. Bei sonst identischen Schlüsseln entsteht ein Feed mit 20 Abonnenten statt zwei Feeds mit einer Summe von 32 nach der dokumentierten Berechnungsregel.
Auswirkung: Feedanzahl und gemeldete Abonnentensummen sind zu klein und zwischen Berichten widersprüchlich. Auch eine korrekt berechnete Summe wäre keine Zahl eindeutig verschiedener Menschen; Reader-Abonnenten können sich überschneiden.
Empfehlung: Eine einheitliche Feed-Identität aus Typ, Schlüssel, Portal und Sprache für alle Berichte verwenden. Die Kennzahl weiterhin als gemeldete Feed-Abonnements ausweisen.
Abnahmetest: Gleicher Reader/Feed-Schlüssel über zwei Portale und zwei Sprachen; Abrufe summieren sich, Feeds bleiben getrennt, Abonnenten-Maximum gilt nur innerhalb desselben Feeds.
N17 — P2: Kundenansicht behauptet Sitemap-Aufnahme für ausgeschlossene Kopien
Belege: app/Services/PressRelease/VisibilityStats.php:159, app/Http/Controllers/Web/SitemapController.php:149, resources/views/livewire/customer/press-releases/show.blade.php:1644.
Befund: in_sitemap_since wird für jede sichtbare Meldung mit öffentlichem Link auf das Veröffentlichungsdatum gesetzt. Die tatsächliche Sitemap berücksichtigt dagegen die Canonical-Rolle und schließt Kopien aus.
Auswirkung: Der Kunde erhält für eine öffentliche Kopie eine konkrete Aufnahmebehauptung, obwohl die Sitemap sie bewusst nicht enthält. Bei Mehrportal-Veröffentlichungen ist zudem eine einzelne globale Angabe weniger präzise als die tatsächliche portalbezogene Auswahl.
Empfehlung: Die Anzeige aus derselben portalbezogenen Sitemap-Eignung ableiten. Zwischen öffentlich erreichbar, kanonische Erstquelle, sitemapfähig und tatsächlich beobachtetem Indexstatus unterscheiden. Kein historisches Aufnahmedatum behaupten, wenn nur die aktuelle Eignung bekannt ist.
Abnahmetest: Erstquelle, Kopie, Both-Meldung mit bevorzugtem Portal, entfernte Meldung und deaktivierte Canonical-Funktion jeweils mit tatsächlicher Sitemap-Ausgabe vergleichen.
N18 — P2: Indexverteilung verwendet unterschiedliche Grundgesamtheiten und nicht eindeutige letzte Checks
Belege: app/Services/SearchConsole/IndexStatusReport.php:71, :84, :113, :215.
Befund: total zählt aktuell sichtbare Meldungen des jeweiligen Portals. Die gezählten Prüfzustände filtern nach dem Join dagegen nur Soft Deletes, nicht dieselben Status-, Termin-, Gone- und Portalbedingungen. Zusätzlich bestimmt latestChecks() den jüngsten Datensatz ausschließlich über MAX(checked_at); zwei Checks mit gleichem Zeitstempel werden beide gejoint.
Konkrete Fälle: Eine geprüfte PM wird anschließend depubliziert: Gesamtzahl kann 0, Indexed-Zahl weiterhin 1 sein. Zwei Checks derselben PM in derselben gespeicherten Sekunde können die Zustandszählung verdoppeln. max(0, total - checked) verdeckt negative Restwerte, beseitigt aber keine Inkonsistenz.
Auswirkung: Die Verteilung ist kein verlässlicher Anteil der ausgewiesenen Grundgesamtheit und kann in sich widersprüchlich werden.
Empfehlung: Für Total und Zustände exakt dieselbe PM-Menge verwenden oder historische Prüfergebnisse ausdrücklich getrennt ausweisen. Den letzten Check mit einer eindeutigen sekundären Sortierung, etwa ID, bestimmen.
Abnahmetest: Prüfung vor Depublikation, Gone-Markierung und Portalwechsel; dazu zwei Checks mit identischem checked_at. Je Segment muss die Summe der Zustände einschließlich Unchecked genau Total ergeben.
N19 — P2: Canonical-Kopien werden nach Wegfall der Erstquelle nicht konsistent neu bewertet
Belege: app/Services/PressRelease/CanonicalAssigner.php:58, :87; app/Services/PressRelease/PressReleaseService.php:549; app/Models/PressRelease.php:639.
Befund: Ist die referenzierte Erstquelle nicht mehr sichtbar, fällt canonicalUrlFor() auf die eigene URL zurück. isCanonicalOn() behandelt denselben Datensatz wegen des weiterhin gesetzten Fremdschlüssels aber unverändert als Kopie. Der Wegfall einer Erstquelle stößt keine entsprechende Neubewertung aller Gegenstücke an.
Ablauf: A ist Erstquelle, B Kopie. A wird depubliziert oder Gone. B bleibt öffentlich und erhält Self-Canonical, wird aber weiter aus Sitemap/News-Sitemap ausgeschlossen. Ein nur auf veröffentlichte Paare gerichteter Zuordnungslauf repariert diesen Zustand nicht zwingend. Zusätzlich lädt die Quellauflösung ohne alle Global Scopes, während isVisibleInArchive() Soft Delete nicht selbst prüft; ein soft-gelöschtes Gegenstück ist deshalb separat abzusichern.
Auswirkung: Canonical-Aussage und Veröffentlichungs-/Sitemap-Rolle widersprechen sich nach regulären Lebenszyklusänderungen. Die dokumentierte größere Bestandszuordnung erhöht die praktische Reichweite dieses schon strukturell angelegten Problems.
Empfehlung: Canonical-Rolle als aktuellen Zustand einer Gruppe behandeln und bei Depublikation, Gone, Löschen sowie relevanten Inhaltsänderungen neu bestimmen. Sichtbarkeitsprüfung inklusive Soft Delete und Sitemap-Eignung aus derselben Regel ableiten.
Abnahmetest: A/B-Paar zuordnen, A nacheinander depublizieren, entfernen und soft-löschen. B muss eine konsistente URL und Sitemap-Rolle erhalten; kein Canonical-Ziel darf nur aufgrund einer Scope-Umgehung als öffentlich gelten.
N20 — P2: Formal unbrauchbare Vision-Antwort kann als erfolgreicher Pass erscheinen
Belege: app/Services/Image/Precheck/Drivers/OpenAiImagePrecheckDriver.php:79, app/Services/Image/Precheck/ImagePrecheckResult.php:115.
Befund: Der Treiber prüft, ob alle Kategorienamen vorhanden sind, nicht aber deren Wertstruktur. Eine Antwort mit sämtlichen Kategorien auf null besteht die Vollständigkeitsprüfung. normalize() lässt diese Werte weg und erzeugt ein ok-Ergebnis ohne Treffer. Strings werden bei hit zu Boolean umgewandelt; beispielsweise ist der String "false" in PHP wahr.
Auswirkung: Ein unbrauchbarer Modellbefund kann als erfolgreich geprüft beziehungsweise mit falschen Treffern protokolliert werden. Gemäß Block K wäre bei einer ungültigen Antwort zwar ebenfalls kein harter Upload-Stopp vorgesehen, aber ausdrücklich der Zustand Error statt eines scheinbar belastbaren Pass.
Empfehlung: Das gesamte Antwortschema vor Normalisierung strikt prüfen: Kategorieobjekte, echte Booleans, endliche numerische Konfidenz im erlaubten Bereich, optional zulässige Notiz. Schemafehler durch den vorhandenen Error-Pfad behandeln.
Abnahmetest: Alle Kategorien null, leere Kategorieobjekte, String-Booleans, nichtnumerische und außerhalb des Bereichs liegende Konfidenzen. Ergebnis muss nachvollziehbar Error sein; gültige Antworten behalten die dokumentierten Schwellenwerte.
N21 — P2, bedingt: Veraltete gecachte Fortschrittsmarke kann die Aggregation dauerhaft stoppen
Belege: app/Console/Commands/AggregatePressReleaseViews.php:86, :147, :169; app/Models/AppSetting.php:30; app/Console/Commands/PurgePressReleaseEvents.php:50.
Bezug: Die ursprüngliche Doppelzählung aus U13 wird durch DB-Sperre und gemeinsamen Commit korrekt verhindert.
Befund: Der Startwert kommt aus einem dauerhaft gecachten Setting. Unter Sperre vergleicht die Aggregation ihn strikt mit dem DB-Wert und bricht bei Abweichung ab. Dieser Abbruch korrigiert den Cache nicht. Ein paralleler Leser kann nach einer Invalidierung noch einen alten DB-Wert wieder in den Cache schreiben; der Purge-Command ist ein tatsächlicher weiterer Leser dieser Marke.
Auswirkung: Sobald ein solcher veralteter Cachewert vorliegt, starten auch spätere Läufe wieder mit derselben falschen Marke und brechen erneut ab. Neue Aufrufe bleiben bis zu einer externen Cachekorrektur unverbucht. Ein solcher Zustand wurde in der angebundenen Umgebung nicht nachgewiesen.
Empfehlung: Operative Fortschrittsmarken ungecacht aus der DB lesen oder bei Abweichung den Cache verwerfen und kontrolliert mit der aktuellen DB-Marke neu beginnen. Konflikt als wiederanlauffähigen Zustand behandeln.
Abnahmetest: Absichtlich alten Cachewert bei neuer DB-Marke vorgeben und zwei aufeinanderfolgende Aggregationsläufe ausführen. Spätestens der Folgelauf muss Fortschritt machen, ohne eine Zeile doppelt zu zählen.
N22 — P1: Testdatenbank-Guard läuft erst nach potenziell destruktivem RefreshDatabase
Belege: tests/TestCase.php:16; installierter Laravel-Code in vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:63, Concerns/InteractsWithTestCaseLifecycle.php:101, :212, RefreshDatabase.php:17, :81.
Einordnung: Der Guard existierte bereits vor diesem Änderungszeitraum. Dies ist ein neu erkannter Bestandsfehler und eine Korrektur der positiven Sicherheitsbewertung des letzten Audits, keine durch die neuen Bildfunktionen eingeführte Regression.
Befund: Tests\TestCase::setUp() ruft zuerst parent::setUp() auf. Laravel führt darin setUpTraits() aus. Ist RefreshDatabase aktiv, wird die Testdatenbank aufgefrischt, bevor der nachfolgende Projekt-Guard database.default und :memory: kontrolliert. Eine gecachte Konfiguration kann die PHPunit-ENV-Werte überlagern — gerade den im Kommentar beschriebenen Fall soll dieser Guard verhindern.
Auswirkung: Bei falsch aufgelöster DB-Konfiguration kann der Guard erst nach einem Migrations-/Datenbankreset werfen. Seine Existenz rechtfertigt daher keinen Testlauf gegen eine ungeprüfte Umgebung. Es wurde in diesem Audit kein Reset ausgeführt und kein Datenverlust beobachtet.
Empfehlung: Die effektive Verbindung nach dem Anwendungsbootstrap, aber vor jedem Datenbank-Testtrait prüfen, zum Beispiel in einem geeigneten gemeinsamen Bootstrap-/beforeRefreshingDatabase-Punkt. Weitere destruktive Traits und zusätzliche Verbindungen mit berücksichtigen. Prüfung nicht durch einen bloßen ENV-Vergleich ersetzen.
Abnahmetest: Mit isolierter, wegwerfbarer Umgebung oder einem instrumentierten Refresh-Hook nachweisen, dass eine unerlaubte Konfiguration vor jedem Migrations-/Truncate-Aufruf abbricht. Diesen Fehler ausdrücklich nicht gegen eine bestehende Entwicklungsdatenbank reproduzieren.
5. Weitere Optimierungen und offene Entscheidungen
Die folgenden Punkte sind getrennt von den 22 priorisierten Befunden aufgeführt. Sie benötigen teilweise eine fachliche Entscheidung oder Messung und werden nicht als bereits eingetretener Fehler ausgegeben.
5.1 Archivvollständigkeit und Aufbewahrung
- Kopien außerhalb des Archivs: Der normale Remover überführt Dateien ins Archiv, lässt Bestätigungs-/Korrekturzeilen in der Anwendungsdatenbank aber bestehen. Archiv-Purge löscht diese Kopien nicht. Festlegen, welche Daten nach Fristende außerhalb des Archivs noch bestehen dürfen, und die Löschregel über alle Speicherorte nachweisen. Der frühere Entwurf mit dauerhafter Aufbewahrung darf nicht still als aktuelle Regel weitergelten.
- Korrektur eines nie veröffentlichten Bildes:
ImageCorrectionService::apply()erzeugtlabel_correctedauch für nie veröffentlichte Bilder. Damit kann eine spätere Sofortlöschung des Entwurfs einen bereits angelegten mehrjährigen Archiveintrag zurücklassen. Die allgemeine Archivierung jeder Korrektur und die Regel „nie veröffentlicht: kein Archiv“ müssen für diesen Randfall ausdrücklich zusammengeführt werden. - Tombstones bei vollständigem Restore: Der Export liegt auf derselben Archivdisk. Wird nicht nur die DB, sondern auch diese Disk auf einen älteren Stand zurückgesetzt, kann der aktuelle Löschstand selbst verloren gehen. Der Restore-Ablauf braucht einen nachweislich aktuellen, vom zurückgesetzten Bestand unabhängigen Tombstone-Stand.
- Verknüpfung zum Meldeverfahren: Der fachliche Antrag sollte den zugehörigen Archiveintrag eindeutig referenzieren, damit Zugriffsprüfung, Auskunft und Wiederholung ohne Suche über Freitext funktionieren. Die geplante Verbindung ist nicht durchgängig erkennbar.
- Fehlerbehandlung weiterer Dateilöschungen: Auch lokale Bereinigung und Remover enthalten nicht ausgewertete Löschresultate. Nach N10 dieselbe Fehlerstrategie für Original, Snapshot, Sidecar und Derivate anwenden.
5.2 Leistung und Betriebsrobustheit
- Synchroner Bildcheck: Drei HTTP-Versuche mit jeweils 20 Sekunden plus Retry-Wartezeiten und Bildvorbereitung können den Request bereits auf über eine Minute bringen. Ein gemeinsames Ende-zu-Ende-Zeitbudget sowie die tatsächlichen Webserver-/PHP-/Proxy-Grenzen fehlen als Abnahmenachweis. Keine konkrete Timeout-Häufigkeit gemessen.
- Dekodieraufwand: 8.000 × 8.000 Pixel entsprechen bereits ungefähr 256 MB für einen einzelnen unkomprimierten RGBA-Puffer, ohne weitere Verarbeitungskopien. Dateigröße allein begrenzt den RAM-Verbrauch nicht. Animierte/mehrseitige Eingaben und Imagick-Ressourcenlimits mit realistischen Grenzfällen messen. Eine erst nach der Verarbeitung geprüfte Laufzeitgrenze unterbricht einen laufenden Decoder nicht.
- Report-Skalierung:
topFeeds()lädt und sortiert die gesamte Gruppierung in PHP; der Tombstone-Export lädt alle gelöschten Einträge. Bei wachsendem Bestand Aggregation, Top-N-Auswahl beziehungsweise Streaming möglichst in DB/Dateipipeline verlagern. Erst messen, dann optimieren; hier liegt kein Lasttest vor. - Warm gecachter Kategorienfeed: Die Aktivitätsprüfung liegt im Cache-Aufbau. Eine danach deaktivierte Kategorie kann bis zur nächsten Erneuerung weiterhin einen zuvor gefüllten Feed liefern. Auswahlbedingungen vor dem Cache prüfen oder passende Cache-Invalidierung bei Kategorienänderung ergänzen.
- Dry-Run-Aussage:
PurgePressReleaseEventszählt im Dry Run nicht alle im echten Lauf zusätzlich bereinigten Feed-/Crawlerdaten. Den Umfang ausdrücklich ausweisen, damit Betreiber aus dem Probelauf keine falsche Größenordnung ableiten. - Korrigierte OG-Datei unter gleicher Adresse: Der Neuaufbau löst nicht automatisch bereits gespeicherte Browser-/Plattformcaches auf. Bei Nachweiskorrekturen prüfen, ob eine geänderte URL/Version erforderlich ist. Die weitergehende Metadaten-/Versionierungsphase bleibt bewusst später vorgesehen.
5.3 Entwicklungsorganisation und Dokumentation
- Die thematischen Commits und Umsetzungsprotokolle sind hilfreich. Für Abnahmen sollte jeder Block zusätzlich den getesteten Commit, den konkret ausgeführten Befehl und noch offene Randfälle tragen. Eine historische Gesamtsuite nach Block E deckt die späteren Archiv-, Gate- und Vorprüfungsänderungen nicht automatisch ab.
- Die drei für diesen Audit vorgefundenen unversionierten Dokumente sind fachlich wichtige Quellen: Decision-Update, aktuelle Fassung 2 und ältere Fassung 1 mit Dateiendung
...2026-09-211.md. Eine eindeutige kanonische Fassung mit versionierter Entscheidungshistorie würde verhindern, dass spätere Reviews oder Deployments nach widersprüchlichen Regeln arbeiten. In diesem Audit wurden diese Dateien nicht verändert oder zur Versionsverwaltung hinzugefügt. - Der Plan formuliert bei U10 die Kategorienprüfung breiter als der Code: Der Code prüft beim tatsächlichen Portalwechsel; ein ausdrücklich erneut gesendetes, unverändertes Portal löst allein keine Prüfung aus. Der ursprüngliche Wechsel-Fehler ist behoben. Die Formulierung bei Gelegenheit präzisieren.
- Die manuelle Fokus-/Tastaturabnahme bleibt offen. Viele neue Volt-Pfade liegen außerdem außerhalb des bisherigen PHPStan-Scope; ein grüner Backend-Analyselauf ersetzt deren Interaktionstests nicht.
- Die breitere MySQL-/Worker-CI wurde bewusst verschoben. Die konkreten Parallelitätsbefunde N02–N04, N07, N14 und N21 liefern jetzt einen eng begrenzten sinnvollen Einstieg statt einer pauschalen Forderung nach zusätzlicher Infrastruktur.
6. Nachweise aus der angebundenen Umgebung
Es wurden nur lesende Anwendungsinformationen, Metadaten und aggregierte Zähler betrachtet. Keine Nutzerdatenexporte, Schlüssel oder Zugangsdaten sind Bestandteil dieses Berichts.
| Prüfung | Beobachtung | Aussagegrenze |
|---|---|---|
| Laufzeit/Pakete | Boost meldet PHP 8.4, Laravel 12.69.2, Fortify 1.39.0 und Livewire 4.4.5; Paketstand mit Lockfile abgeglichen. | Keine Aussage über jede produktive Instanz oder alte Workerprozesse. |
| Migrationsregister | 19 Einträge ab 2026_09_18_110000 vorhanden, einschließlich der zuvor fehlenden Audit-Migrationen, Kontingent-Backfill, Feed- und Bild-/Archiv-Erweiterungen. |
Belegt Einträge in dieser Datenbank; weder erfolgreicher Produktivdeploy noch vollständige fachliche Datenkorrektur folgt allein daraus. |
| Aktiver Bildbestand | 14.602 nicht soft-gelöschte Bildzeilen, davon eine mit v2-Regelversion. | Momentaufnahme der angebundenen Datenbank, keine Produktionsidentität bestätigt. |
| V2-Ausnahmefälle | Keine zurückgehaltenen Bilder, keine unbestätigten v2-Bilder und keine v2-Bilder mit Publikationsmarker ohne Snapshot in der gelesenen Aggregation. | Die Codefehler N01/N09 wurden damit nicht als bereits vorhandener Datenvorfall nachgewiesen; die sehr kleine v2-Stichprobe entkräftet sie nicht. |
| Archivbestand | Keine Archiveinträge in der gelesenen Datenbank. | Kein End-to-End-Nachweis für realen Archivbetrieb, Hold, Purge oder Restore. Bei leerer Tabelle sind SUM-Werte NULL, nicht zusätzliche Ereignisse. |
| Lokale ENV-Datei | IMAGE_RIGHTS_V2=true, IMAGE_PRECHECK=true, IMAGE_PRECHECK_STAGE=2, LEGAL_ACCEPTANCE_GATE=true. |
Dateiinhalte, nicht unabhängig aufgelöste Konfiguration aller Requests/Worker; Flags sind hier nicht generell ausgeschaltet. |
| Sail | Docker/Podman nicht verfügbar; gezielter Teststart scheitert vor Ausführung. | Keine neu bestätigten Tests, Builds oder Workerabläufe. |
Die dokumentierte Dev-Bereinigung von 10.768 Legacy-Bildern lässt sich aus der heutigen Gesamtzählung nicht als durchgeführt für genau diese Datenbank bestätigen. Sie kann auf einem anderen Stand beziehungsweise einer anderen Umgebung erfolgt sein. Für einen belastbaren Deploy-Nachweis müssen Zielumgebung, Zeitpunkt, Command, Vorher-/Nachher-Zähler und Commit zusammenpassen.
Offen bleiben insbesondere Produktivmigrationen, tatsächliche Feature-Aktivierung, Archivschlüssel-/Backup-/Restore-Verfügbarkeit ohne Offenlegung der Schlüssel, Worker-Neustarts, wirksame Queue-Zeitgrenzen und die im Plan für den Bildcheck vorgesehenen betrieblichen Entscheidungen. Der Bericht behauptet weder fehlende Schlüssel noch eine unzulässige Aktivierung des Bildchecks.
7. Empfohlene Reihenfolge der Nacharbeit und Abnahme
Die folgenden Pakete sind Empfehlungen für die nächste Umsetzung. In diesem Auftrag wurde keines davon geändert.
| Schritt | Inhalt | Abschlusskriterium |
|---|---|---|
| 1 | N22: Testumgebung vor Datenbankoperationen absichern. | Fehlkonfiguration bricht vor jedem destruktiven Test-Hook ab; danach gezielte Tests sicher ausführbar. |
| 2 | N01, N03, N04, N05, N07: Freigabe und Akteur/Status/Hash zusammenführen. | Öffentliches Bild hat gültige Freigabe; Kundenbestätigung funktioniert für den vorgesehenen Kunden; keine veraltete KI-Entscheidung ändert den aktuellen Stand. |
| 3 | N02, N10, N11, N12, N13: Archiv- und Löschabläufe schließen. | Hold schützt auch unter Parallelität; Dateioperationen sind geprüft und wiederholbar; alle Entfernungspfade besitzen den vorgesehenen Endzustand. |
| 4 | N06, N08, N09, N20: Nachweisbindung und Fehlerpfade verbessern. | Angezeigter Text entspricht protokollierter Annahme; Cachefehler vernichtet kein Original; Snapshot passt zur veröffentlichten Kennzeichnung; ungültige Modellantwort ist Error. |
| 5 | N14, N21: Hintergrundabläufe wiederanlauffähig machen. | Benachrichtigung wird eindeutig eingeplant; Aggregation erholt sich von veralteter Marke ohne Doppelzählung. |
| 6 | N15–N19: Berichts- und Canonical-Invarianten korrigieren. | Mengen addieren sich; Feed-Identitäten bleiben getrennt; Kundenanzeige, Sitemap und Canonical sind konsistent. |
| 7 | Betriebs- und Browserabnahme auf konkretem Commit dokumentieren. | Reproduzierbarer Teststand, geprüfte Zielumgebung, vollständiger Upload-/Korrektur-/Archiv-/Restore-Durchlauf. |
7.1 Gezielte Testpakete
- Identität und alte Restfehler: vorhandene ContactAccess-, FortifyTwoFactorRoutes-, PressReleaseQuota-, PressReleaseApi-, Classification- und ContentScore-Tests ausführen; die in N03/N04 beschriebenen Zwischenfenster ergänzen.
- Bilder:
tests/Feature/Images/,tests/Feature/Api/V1/PressReleaseImageRightsV2ApiTest.phpund die öffentliche Medienausgabe gemeinsam prüfen. Nicht nur DB-Zeilen, sondern echte Auslieferung, Dateivollständigkeit und gespeicherten Nachweis vergleichen. - Archiv:
tests/Feature/Archive/EvidenceArchiveTest.php, Anonymisierungs- und Korrekturtests um konkurrierenden Hold, Storage-Fehler, Wiederholung und alle Entfernungspfade erweitern. Ein Restore-Test benötigt einen älteren Datenbestand und einen davon unabhängigen aktuellen Tombstone-Stand. - Dokumentannahmen:
tests/Feature/Legal/LegalAcceptanceGateTest.phpund Bildbestätigung mit Versionswechsel zwischen Rendern und Submit prüfen. - Reports: Kohorten-Grenzfälle, Feed-Gruppierung, Indexverteilung, Canonical-Änderungen und Sichtbarkeitsanzeige mit gemeinsamen Datenfällen prüfen. Mengeninvarianten sind aussagekräftiger als ausschließlich einzelne erwartete Zeilen.
- Hintergrundprozesse: ExpireGrandfathering und View-Aggregation zusätzlich mit zwei echten DB-Verbindungen beziehungsweise einem kontrollierten Queue-Abbruch prüfen. Ein synchroner SQLite-Test allein bestätigt diese Garantien nicht.
Erst nach diesen gezielten Korrekturen und Nachweisen ist eine Gesamtfreigabe der betroffenen neuen Abläufe belastbar. Die ausdrücklich verschobenen Newsletter-Arbeiten bleiben vor der ersten Nutzung des Kampagnenmoduls ein eigenes Abnahmepaket.
8. Änderungsumfang dieses Audits
Neu erstellt wurde ausschließlich dev/2026-09-11-audit/UMSETZUNGSAUDIT-2026-09-25.md. Vorhandene unversionierte Planungsdateien, Code, Tests, Konfiguration und Datenbestand wurden nicht verändert. Es wurden keine Mails verschickt, Migrationen oder Bereinigungen ausgeführt, Dienste gestartet oder Deployments vorgenommen.