presseportale/dev/archive/2026-09-audit/UMSETZUNGSAUDIT-2026-10-02.md
Kevin Adametz 8e3554bf91 dev: Doku aufgeräumt, neue Roadmap als einzige Liste offener Punkte
- 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>
2026-10-09 11:25:09 +00:00

29 KiB
Raw Blame History

Umsetzungsaudit vom 02.10.2026

Grundlagen: Entwicklungsplan, Umsetzungsaudit vom 29.09.2026. Schwerpunkt ist die als erledigt dokumentierte Nacharbeit in Block 9, einschließlich ihrer Wechselwirkungen mit Block 8.

Geprüfter Stand: Arbeitskopie auf technik/betriebsfixes-2026-09, HEAD 98c465b691752d15a0e07eceb6f927ddda184e90, einschließlich der bereits vorhandenen Änderungen und unversionierten Dateien. HEAD allein enthält die geprüfte Umsetzung von Block 9 nicht. Die im Plan beschriebenen neuen Services, Middleware, Migrationen und Tests liegen teilweise noch unversioniert vor.

Auftrag: Prüfung und Dokumentation. Es wurden keine Anwendungsdateien, Tests, Konfigurationen oder Bestandsdaten geändert, keine Migrationen und keine Betriebsbefehle ausgeführt. Einzige neu angelegte Datei ist dieses Audit. Empfehlungen sind nicht umgesetzt.

1. Ergebnis

Die Umsetzung ist deutlich verbessert, aber noch nicht vollständig sachgerecht abgeschlossen. Die wesentlichen Korrekturen gegen fremde Firmenzuordnung, Google-Vorabübernahme, automatische Veröffentlichung trotz manueller Prüfsperre und ungefilterte Bilder in Feed/PDF sind im Code vorhanden. Mehrere Fehlerpfade bleiben jedoch offen.

Diese Prüfung ergibt acht konkrete Restbefunde: zwei P1 und sechs P2. Die P1 betreffen unterschiedliche Bereiche: eine von außen erreichbare Mail-Sperrlücke und einen bedingten Datenverlust beim Entwicklertest. Hinzu kommen offene Betriebsnachweise; diese sind keine nachgewiesenen Produktionsvorfälle.

ID Priorität Befund Bezug zum letzten Audit/Plan
O01 P1 – Sicherheit Unsignierte weiche Rückläufer können fremde Mailadressen sperren R-04, E25, Block 9.6
O02 P1 – Testdaten-Sicherheit DB_URL kann den Schutz vor Tests gegen eine echte Datenbank umgehen N22, Qualitätsgates
O03 P2 Nachträglich freigegebene Bilder können nach Snapshotfehler als „nie veröffentlicht“ gelöscht werden N09, B-05, Block 9.5
O04 P2 Restore-Abgleich kann bei wiederverwendeten IDs einen anderen Archiveintrag löschen A-03/A-06, Block 9.4/9.5
O05 P2 Tombstone-Signatur ist durch den Altformat-Rückfall umgehbar; Wiederregistrierung ignoriert ungültige Exporte A-03/A-15, E31
O06 P2 Blockierte Meldungen können alle späteren Terminveröffentlichungen verdrängen K-02, Block 9.6
O07 P2 Archivauftrag wird von Direktweg und Nachholer nicht exklusiv beansprucht A-02, Block 9.4
O08 P2 Nachgeholte Depublikation kann ohne die zugesagten Bilddateien erfolgreich abgeschlossen werden A-04, Block 9.5

Bewertungsmaßstab: P1 vor Nutzung des betroffenen Ablaufs schließen; P2 vor fachlicher Abnahme des jeweiligen Ablaufs beheben. Alle acht Befunde sind durch den gelesenen Kontrollfluss belegt. Keiner wurde in dieser Sitzung durch einen schreibenden Test oder gegen Live ausgenutzt. Konkrete Bestandsbetroffenheit und Häufigkeiten wurden nicht erhoben.

2. Prüfumfang und Grenzen

Geprüft wurden insbesondere die Änderungen an Authentifizierung und Firmenrechten, Veröffentlichung und Scheduler, Bildbestätigung und Bildlöschung, Snapshot-Versionierung, Archivaufträgen und Restore, Bounce-Verarbeitung und Newsletter-Sperre. Ergänzend wurden die Änderungen an Canonicals, Sitemap/Änderungsdatum, Merge/Unmerge, Zustimmungsgate, Berichten und die zugehörigen Regressionstests gelesen. Bei den älteren Blöcken erfolgte eine gezielte Rückprüfung der berührten Schutzmechanismen, keine vollständige Wiederholung aller historischen Audits.

Die Laravel-Boost-Paketinformationen und die Dokumentationssuche wurden verwendet; Framework-Verhalten beim Datenbank-URL-Override wurde zusätzlich direkt am installierten Laravel-Code geprüft. Die Testkonfiguration und ihre Schutzmechanismen wurden gesondert gelesen.

Prüfung Ergebnis dieser Sitzung
Plan/Audit gegen Arbeitskopie, Änderungen und neue Dateien durchgeführt
Quelltextprüfung der genannten Aufrufketten und vorhandenen Tests durchgeführt
Sail-Verfügbarkeit: vendor/bin/sail php -v Abbruch: Docker or Podman is not running.
Pest, Larastan, PHP-Lint, Browser- und MySQL-Parallelitätstests nicht ausgeführt
Datenbankabfragen, Mailversand, Archiv-/Restore-Kommandos nicht ausgeführt
Live-Konfiguration, Deployment, Schlüssel-/Passwortrotation nicht verifiziert

Die im Entwicklungsplan angegebenen 2.265 erfolgreichen Tests, ein übersprungener Test sowie grüne Larastan-/Pint-Läufe sind historische Angaben vom 29.09.; sie wurden hier nicht erneut bestätigt. Tests würden zudem Testdaten, Logs, Storage-Fakes und gegebenenfalls kompilierte Views schreiben. Es wurden weder Container gestartet noch Caches gelöscht, um den reinen Auditauftrag einzuhalten.

Die folgenden Zeilenangaben beziehen sich auf die geprüfte Arbeitskopie; nach weiteren Änderungen können sie sich verschieben.

3. Was am Code nachvollziehbar umgesetzt ist

„Umgesetzt“ bedeutet in dieser Tabelle: Schutz beziehungsweise Korrektur ist im geprüften Code vorhanden. Es ist keine Bestätigung eines erfolgreichen Live-Deployments oder einer vollständig bestandenen Laufzeitprüfung.

Bereich / alte IDs Stand und Beleg
Fremde Firmenzuordnung, R-01 ApiNewsroomAssignment::assign() prüft vor Pivot und Portaländerung bestehende Zugehörigkeit beziehungsweise Allowlist. Neue API-Pivots tragen granted_via = api; der Bestandsprüfbefehl ist vorhanden. Die Bereinigung bereits entstandener Fremdrechte bleibt ein Betriebsnachweis.
Google-Vorabübernahme, K-04 SocialAuthService::resolveUser() verwirft bei Verifikation eines offenen Kontos Passwort, Remember-Token und Sanctum-Tokens; SessionRevocation und die Web-Middleware behandeln alte Sessions. Regressionstest vorhanden. Persistenz und Ausfallverhalten des Cache-basierten Widerrufs wurden nicht dynamisch geprüft.
Gemeinsamer Loginabschluss, K-08 / Plan 1.8 LoginCompletion ist vorhanden und in Google-, Magic-Link-, Volt-Passwort- und Fortify-Abschluss eingebunden. Die pauschale spätere Plan-Notiz „1.8 nur als Google-Zweig“ ist für diese Arbeitskopie veraltet.
Manuelle Prüfung, K-01 Scheduler filtert manual_review_required_at; publish() prüft bei automatischen Quellen zusätzlich unter der PM-Sperre. Der Adminweg bleibt die vorgesehene manuelle Entscheidung.
Öffentliche Bilder, B-01/B-02 Startseiten-Feed nutzt publiclyVisible(), Card-Helfer und PDF prüfen die Auslieferbarkeit. Auch die Medienroute verwendet den gemeinsamen Freigabezustand.
Uploadgrenze, B-03 Temporäre Livewire-Uploadregel ist auf 16 MiB angehoben. Die PHP-/Proxygrenzen sind damit noch nicht nachgewiesen.
Bestätigung, B-04/B-14 Gesperrte Bild-ID und SHA in der Kundenkomponente; Service prüft Hash und aktuellen Bildzustand unter Bildsperre.
Entfernen und Rückhaltegründe, B-05/B-07 Remover lädt PM und Bild unter Sperren neu. precheck_withheld_at und withheld_at sind getrennt. Rest: O03.
Veröffentlichungssnapshots, N09 Dateifassung enthält Bytes-/Metadatenbezug, OG-Datei und JSON; Schreibfehler werden erkannt und Fassungen beim Bildarchivieren eingesammelt. Rest: O03 und O08; ein allgemeiner Snapshot-Nachholer fehlt.
Archivaufträge, A-01/A-02/A-04 Auftragstabelle ersetzt den einzelnen Marker, Textstände werden im verschlüsselten Auftrag festgehalten; Nachholer ohne Schlüssel bricht ab. Rest: O07/O08.
Restore, A-03/A-06 Quarantäne, Sweep-Pause, Wiederregistrierung und Bereinigung von Anwendungszeilen sind vorhanden. Rest: O04/O05.
Änderungsdatum/Sprache/Canonical, R-02/R-03/N19 Aggregation und PM-Verschiebung beim Merge vermeiden updated_at; content_changed_at wird für Inhaltsänderungen verwendet; Sitemap berücksichtigt die Sprache; Archivierung stößt Canonical-Neuzuordnung an.
Merge/Unmerge, R-05 Eingefüllte Feldwerte werden protokolliert; Unmerge prüft vor Rücksetzen, ob der Wert noch dem eingefüllten Wert entspricht.
Zustimmungsgate, K-07 legalAcceptanceVersions ist #[Locked]; abweichende Fassungen und fehlende Pflichtdokumente verhindern die Einreichung.
Newsletter, E9 Versand standardmäßig aus; Start, Resume-Befehl und Versandjob berücksichtigen die Sperre. U05–U07 sind weiterhin aufgeschoben und dadurch nicht behoben.
Tests/Qualitätsgates, Q-01/Q-02 Gemeinsamer Testhelfer steht in tests/Pest.php; die beanstandete Slug-Annotation wurde entfernt. Aktueller Lauf nicht bestätigt; zusätzliche Guard-Lücke O02.

4. Restbefunde mit Abhilfe

O01 · P1 · Bounce-Signatur schützt nicht vor Sperren über weiche Rückläufer

Fundstellen: app/Services/Mail/BounceMailProcessor.php:58–98, 119–147; app/Services/Mail/EmailSuppressionService.php:130–163, 221–257; config/bounces.php:45–57.

Befund: Die Signatur wird berechnet, aber ein ungültiger Nachweis wird nur behandelt, wenn $kind === Hard ist. delayed ergibt sofort Soft; auch ein zunächst harter Rückläufer kann vorher aufgrund seines Status in Soft umklassifiziert werden. Beide Wege gelangen ungeprüft in die Sperrzählung. Drei weiche Ereignisse innerhalb von sieben Tagen genügen mit der Standardkonfiguration für eine aktive Sperre.

Nachvollziehbarer Fall: Drei unsignierte DSNs mit jeweils eigener Message-ID, derselben fremden Empfängeradresse und Action: delayed erreichen das verarbeitete Bounce-Postfach. Der Parser liefert weiche Rückläufer; die Signatur hat auf diesen Zweig keinen Einfluss. Die Sperrliste blockiert anschließend auch transaktionale Mails an diese Adresse. Voraussetzung ist, dass solche Nachrichten das konfigurierte Postfach und den Abhollauf erreichen; eine vorgelagerte Absenderauthentisierung ist in dieser Codekette nicht implementiert.

Zusätzlich: Im dokumentierten Übergang BOUNCE_UNSIGNED_HARD_KIND=soft ist derselbe Effekt auch mit drei unsignierten harten Rückläufern möglich. Die spätere Umstellung auf policy schließt den direkten Soft-Pfad nicht.

Abhilfe: Für jeden Rückläufer, der eine Empfängersperre beeinflussen kann, einen gültigen empfängergebundenen Nachweis verlangen. Ohne Nachweis ausschließlich zur Sichtung protokollieren, ohne Soft-/Hard-Sperrzähler. Eine bewusst unsichere Übergangsregel ist kein vollständiger R-04-Fix.

Regressionstest: Mehrere unsignierte beziehungsweise falsch signierte delayed- und failed/4.x-DSNs mit verschiedenen Message-IDs dürfen auch oberhalb des Soft-Schwellwerts keine Sperre erzeugen. Beide Werte von unsigned_hard_kind abdecken. Gültig signierte Soft-Bounces müssen weiterhin regulär zählen.

Vorhandene Tests: BounceMailProcessingTest prüft den einzelnen unsignierten Hard-Bounce und wiederholte falsch signierte Hard-Bounces, aber nicht diese wiederholten Soft-Wege.

O02 · P1 · Testdatenbank-Guard ignoriert den URL-Override

Fundstellen: tests/TestCase.php:31–54; phpunit.xml:30–33; config/database.php:58–61; vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:224–225; vendor/laravel/framework/src/Illuminate/Support/ConfigurationUrlParser.php:36–51; tests/Pest.php:27–28.

Befund: Der Guard kontrolliert den Verbindungsnamen und die rohen Konfigurationswerte driver=sqlite, database=:memory:. Die SQLite-Konfiguration erlaubt daneben url=env('DB_URL'). Laravel löst diese URL später auf und überschreibt damit unter anderem Treiber und Datenbank. PHPUnit erzwingt DB_CONNECTION und DB_DATABASE, neutralisiert DB_URL jedoch nicht.

Nachvollziehbarer Fall: Die Testumgebung erbt eine erreichbare reale Datenbank über DB_URL. Die rohen SQLite-/Memory-Werte passieren den Guard; die tatsächliche Verbindung wird anschließend aus der URL gebildet. RefreshDatabase kann dann migrate:fresh gegen die reale Datenbank starten und Tabellen löschen.

Einordnung: Das ist ein bedingtes Entwicklungs-/CI-Risiko, keine behauptete aktuelle Löschung und kein öffentlicher Webangriff. In den geprüften lokalen Env-Dateien wurde kein DB_URL-Eintrag festgestellt; die gesamte Prozess-/CI-Umgebung ist damit nicht ausgeschlossen.

Abhilfe: DB_URL für Tests ausdrücklich neutralisieren. Zusätzlich die vollständig aufgelöste Verbindungskonfiguration prüfen beziehungsweise jede nicht leere URL bereits vor den DB-Traits ablehnen. Der bestehende frühe Zeitpunkt in setUpTraits() ist richtig und muss erhalten bleiben.

Regressionstest: Nur den Konfigurationswert database.connections.sqlite.url auf eine Nicht-Test-URL setzen und verlangen, dass der Guard ohne Verbindungsaufbau abbricht. Keine echte Datenbank für diesen Negativtest verwenden. TestDatabaseGuardTest deckt bisher nur einen anderen Default-Verbindungsnamen ab.

O03 · P2 · Veröffentlichungsmarke hängt bei späterer Bildfreigabe am Snapshot-Erfolg

Fundstellen: app/Services/PressRelease/Images/PressReleaseImageUploader.php:324–364; app/Services/Image/Precheck/ImagePrecheckReviewService.php:31–55; app/Services/PressRelease/Images/ImageCorrectionService.php:100–145; app/Services/PressRelease/Images/PublishedImageSnapshot.php:128–145; app/Services/PressRelease/Images/PressReleaseImageRemover.php:86–119.

Befund: Beim normalen PM-Publish wird die Veröffentlichungsmarke in der Transaktion gesetzt. Bei nachträglicher Kundenbestätigung an einer bereits veröffentlichten PM wird dagegen zunächst nur rights_confirmed_at gespeichert. Das Bild ist damit öffentlich auslieferbar. published_ever_at setzt erst der nachgelagerte Snapshot nach erfolgreichen Dateischreibvorgängen. Ähnlich arbeiten die Freigabe der Vorab-Sichtung und die erstmalige Freigabe im Korrekturweg. Snapshotfehler werden lediglich gemeldet.

Nachvollziehbarer Fall: Admin lädt an einer veröffentlichten PM ein neues Bild hoch; Kunde bestätigt; Snapshot-Schreiben scheitert. Die Medienroute kann das Bild ausliefern, published_ever_at bleibt aber NULL. Beim späteren Entfernen nimmt der Remover den Zweig „nie veröffentlicht“ und löscht Original, Bestätigung und Bildzeile ohne image_removed-Archivauftrag. Ein konkurrierendes Entfernen im Zeitfenster vor dem Snapshot kann denselben Fehler auslösen.

Abhilfe: Bei jedem Übergang zur öffentlichen Auslieferbarkeit die Veröffentlichungsmarke innerhalb einer mit dem Remover koordinierten PM-/Bildtransaktion setzen. Snapshot-Erstellung als separat nachholbare Arbeit führen. Ein fehlgeschlagener Nachweis darf aus einem tatsächlich freigegebenen Bild kein „nie veröffentlichtes“ machen.

Regressionstest: Nachträgliche Kundenbestätigung, Precheck-Freigabe und Korrektur-Freigabe an veröffentlichter PM jeweils mit fehlgeschlagenem Snapshot-Schreiben prüfen. Danach entfernen: Beweisdateien müssen archiviert oder für einen offenen Archivauftrag erhalten werden. Ergänzend MySQL-Test für Freigabe gegen Entfernen mit zwei Verbindungen.

Vorhandene Tests: ImagePipelineAuditTest:299 deckt den Schreibfehler beim normalen PM-Publish ab; dort wird die Marke bereits separat gesetzt. Dieser Test beweist die nachträglichen Freigabewege nicht.

O04 · P2 · Restore-Tombstones werden nur über die numerische ID zugeordnet

Fundstellen: app/Services/Archive/EvidenceArchive.php:450–480, 817–834.

Befund: Der Restore-Abgleich löscht zunächst den im Export genannten Dateipfad, sucht die zu tombstonende Datenbankzeile aber ausschließlich mit find(exportierte ID). Es wird nicht geprüft, ob deren payload_path zum Export passt. Die neue Wiederregistrierung erzeugt ihrerseits frische Auto-Increment-IDs.

Nachvollziehbarer Fall: Nach Rückkehr zu einem älteren DB-Stand wird ID 101 für Payload B verwendet, während der aktuelle Tombstone-Export ID 101 für die früher gelöschte Payload A enthält. Der Abgleich markiert B als gelöscht, bereinigt gegebenenfalls die Anwendungskopien von B und löscht in der anschließenden Tombstone-Schleife auch dessen Payload. Die HMAC kann dabei vollständig gültig sein: Falsch ist die Identitätszuordnung. Wiederregistrierung in anderer Reihenfolge und ein erneuter Abgleich sind ein weiterer Weg zu abweichenden IDs.

Abhilfe: Den UUID-haltigen Payload-Pfad beziehungsweise eine dauerhaft gespeicherte Archiv-UUID als Wiederherstellungsidentität verwenden. ID und Pfad müssen vor jeder destruktiven Aktion konsistent sein; bei Konflikten abbrechen und melden. Eine fremde Zeile darf niemals allein aufgrund der alten ID tombstoned werden.

Regressionstest: Gültiger Export für ID X/Pfad A, aktuelle DB-Zeile mit ID X/Pfad B. B samt Anwendungskopien bleibt erhalten. Zusätzlich Wiederregistrierung mit geänderten IDs und wiederholten Restore-Abgleich prüfen.

O05 · P2 · Ungültige und unsignierte Tombstone-Exporte werden nicht sicher abgegrenzt

Fundstellen: app/Services/Archive/EvidenceArchive.php:560–564, 847–888; app/Console/Commands/ArchiveReregister.php:29–49; app/Console/Commands/ArchiveReconcileAfterRestore.php:27–42.

Befund A – Signatur-Rückfall: Fehlt hmac oder ist es kein String, behandelt readTombstoneExport() den Export als zulässiges Altformat und gibt die Zeilen ungeprüft zurück. Ein veränderter neuer Export wird somit verwendbar, sobald sein HMAC-Feld entfernt oder auf NULL gesetzt wird. Das Dateiformat selbst weist nicht vertrauenswürdig nach, dass es tatsächlich vor der Signaturumstellung entstand. Betroffen ist der zugesagte Integritätsschutz einer Löschliste; ein anonymer Zugriff auf diese Datei wurde nicht festgestellt.

Befund B – Wiederregistrierung: Bei ungültiger Signatur liefert der Leser eine leere Liste plus Status invalid. reregister() übernimmt nur die Liste und verwirft den Status. Der Befehl kann daraufhin Payloads als neue Einträge registrieren, die laut dem eigentlich maßgeblichen Export gelöscht bleiben müssten, und dennoch Erfolg melden. Das betrifft auch einen Export, dessen früherer Signaturschlüssel versehentlich nicht konfiguriert ist.

Abhilfe: Für den Normalbetrieb gültig signierte Exporte verlangen. Altformat-Import nur über einen ausdrücklich ausgewählten, kontrollierten Migrationsweg zulassen. Ungültige Exporte müssen Wiederregistrierung und destruktiven Exportabgleich stoppen. Für fehlende Exporte eine ausdrückliche Restore-Regel vorsehen, statt Unkenntnis still als „nichts gelöscht“ zu behandeln.

Regressionstest: Manipulierten Export zusätzlich ohne hmac und mit hmac=null prüfen. Bei ungültiger Signatur darf archive:reregister keine Zeile erzeugen und muss fehlschlagen. Der vorhandene Manipulationstest prüft nur geänderte Einträge bei weiterhin vorhandenem HMAC im Reconcile-Befehl.

O06 · P2 · Dauerhaft blockierte Kandidaten verstopfen den Veröffentlichungslauf

Fundstellen: app/Console/Commands/PublishScheduledPressReleases.php:68–98, 132–180.

Befund: Jeder Lauf lädt nur die ältesten --limit Kandidaten, standardmäßig 200. Meldungen ohne Kontingent oder ohne Bildbestätigung bleiben mit unverändertem Status und Termin in derselben Auswahl. Ein Fortsetzen hinter diesen Kandidaten findet nicht statt. Der Lauf endet bei ausschließlich fachlichen Blockaden erfolgreich.

Nachvollziehbarer Fall: 200 ältere fällige Meldungen sind blockiert, eine jüngere ist vollständig veröffentlichungsfähig. Jeder Lauf verarbeitet erneut dieselben 200. Die jüngere Meldung erscheint nie, solange der blockierte Bestand die vordere Auswahl füllt. Zum kleinen Regressionstest genügen --limit=2, zwei blockierte und eine zulässige Meldung.

Abhilfe: Kandidaten fair und mit stabilem Cursor durchlaufen; fachliche Blockaden dürfen nicht dauerhaft das gesamte Laufbudget belegen. Beispielsweise Seiten nach (scheduled_at, id) mit getrenntem Scan-/Zeitbudget verarbeiten oder blockierte Kandidaten mit einer kontrollierten Wiedervorlage versehen. Allein das Limit zu erhöhen verschiebt den Fehler.

Regressionstest: Die zulässige Meldung hinter einer vollen Seite blockierter Kandidaten wird im selben oder einem nachweislich fortschreitenden Folgelauf veröffentlicht. Blockaden bleiben sichtbar und werden weiterhin erneut geprüft.

O07 · P2 · Archivaufträge sind trotz Transaktionen nicht gegen Doppelverarbeitung geschützt

Fundstellen: app/Services/Archive/ArchivePendingQueue.php:88–99, 123–135; app/Services/Archive/PendingArchiveRetry.php:58–69, 108–140; app/Console/Commands/ArchiveRetryPending.php:43–53; app/Services/Archive/EvidenceArchive.php:138–165.

Befund: Sowohl completeWith() als auch retry() starten die Archivierung mit einer bereits geladenen Auftraginstanz. Keiner der Wege lädt und beansprucht die Auftragszeile exklusiv unter Sperre; auch das zwischenzeitliche Löschen des Auftrags wird vor der Archivierung nicht geprüft. Für die neuen pm_depublished-/gdpr_change-Aufträge gibt es keine eindeutige, im Archiveintrag gespeicherte Auftragsidentität.

Nachvollziehbarer Fall: Der Direktweg hat seinen fachlichen Commit abgeschlossen, aber noch nicht archiviert. Der Nachholer liest den nun sichtbaren Auftrag. Beide erzeugen ein Archiv-ZIP und eine Archiveintrag-Zeile; erst anschließend löschen sie denselben Auftrag. Die zweite Löschung mit null Treffern rollt den zweiten Archiveintrag nicht zurück. Auch zwei manuell gleichzeitig gestartete Nachholer können dies auslösen. withoutOverlapping() am Scheduler koordiniert den HTTP-Direktweg nicht.

Auswirkung: Ein Ereignis bekommt mehrere Nachweise und gegebenenfalls unterschiedliche Erfassungs-/Aufbewahrungszeitpunkte. Bei Bildereignissen konkurrieren zusätzlich Dateilesen und die Bereinigung lokaler Beweisdateien. Das Versprechen „ein Auftrag je Ereignis, einmaliger Abschluss“ ist damit nicht vollständig umgesetzt.

Abhilfe: Beide Wege über denselben atomaren Claim beziehungsweise dieselbe Auftragssperre führen und nach Sperrerwerb das weitere Bestehen prüfen. Eine dauerhafte eindeutige Ereignis-/Auftrags-ID am Archiveintrag verhindert Doppelablage zusätzlich. Externe Dateioperationen und Wiederholung nach Prozessabbruch ausdrücklich einbeziehen.

Regressionstest: Zwei Verbindungen/Prozesse bearbeiten denselben Auftrag; genau ein vollständiger Archiveintrag entsteht, der Marker wird erst nach Abschluss entfernt. Zusätzlich eine bereits gelöschte, aber noch im Speicher gehaltene Auftraginstanz erneut verarbeiten: keine weitere Ablage. Keine dynamische MySQL-Reproduktion in dieser Sitzung.

O08 · P2 · Depublikationsauftrag hält Bildpfade fest, aber nicht deren Verfügbarkeit

Fundstellen: app/Services/Archive/PressReleaseArchiver.php:123–145, 154–193; app/Services/PressRelease/Images/PressReleaseImageRemover.php:149–180; app/Services/Archive/PendingArchiveRetry.php:108–128; app/Services/Archive/ArchivePendingQueue.php:90–96.

Befund: Der festgehaltene Depublikationsstand enthält den Snapshot-Pfad und Bildmetadaten. Die Bytes werden erst beim späteren Archivieren gelesen. Existiert der Pfad dann nicht mehr, wird die Datei ohne Fehler ausgelassen. recordDepublished() erzeugt trotzdem einen Eintrag, wodurch der Auftrag als erfolgreich erledigt gilt.

Nachvollziehbarer Fall: Depublikation bei Archivstörung erzeugt einen offenen Auftrag. Nach Behebung der Störung wird das Bild vor dem Nachholer entfernt und regulär als image_removed archiviert; der Remover bereinigt seine lokalen Snapshots. Der spätere pm_depublished-Eintrag enthält nun den Text und Bildmetadaten, aber keine Bilddatei. Der Auftrag verschwindet ohne Hinweis auf einen unvollständigen Nachweis. Die Datei kann noch im getrennten Bildarchiv vorhanden sein, wird hier aber weder übernommen noch verbindlich referenziert. Bei unabhängigem Dateiverlust fehlt auch dieser Rückweg.

Abhilfe: Unveränderliche Snapshot-Dateien bis zum Abschluss aller referenzierenden Aufträge erhalten oder den Auftrag auf eine dauerhaft verfügbare archivierte Fassung verweisen lassen. Erwartete, aber fehlende Dateien dürfen nicht still als vollständige Archivierung gelten. Für v2-Bilder Vollständigkeit anhand der im Auftrag festgehaltenen Fassung prüfen; Altbestand ohne Snapshot gesondert kennzeichnen.

Regressionstest: Depublikation mit fehlgeschlagener Archivierung, danach erfolgreiche Bildentfernung, danach Retry. Der Depublikationsnachweis enthält die richtige damalige Bildfassung oder eine belastbare Archivverknüpfung. Bei fehlenden Bytes bleibt ein sichtbarer Fehler/offener Auftrag bestehen. Der vorhandene A-04-Test prüft spätere Textänderungen, nicht diese Dateilebensdauer.

5. Offene Punkte und Hinweise ohne neuen bestätigten Laufzeitbefund

  1. Deployment und Rechtebereinigung: Block 9 ist in dieser Arbeitskopie uncommittet. Ob die acht Migrationen, die Allowlist, Worker-Neustarts und die Prüfung alter API-Zuordnungen auf Live durchgeführt wurden, ist nicht belegt. Die vorhandenen neuen Dateien müssen beim späteren Commit/Deploy vollständig erfasst werden. Eine neue Sperre am API-Einstieg entzieht bereits vorhandene Fremdrechte nicht rückwirkend.
  2. Bekanntes Zugangsdatenleck: Der Entwicklungsplan dokumentiert ein aus der Arbeitskopie entferntes KAS-Passwort, das in der Git-Historie verblieben ist. Der Wert wurde hier weder ausgegeben noch in das Audit übernommen. Die erfolgreiche Rotation ist aus dem geprüften Stand nicht nachgewiesen und bleibt vorrangig zu bestätigen. Das ist ein offener Betriebsnachweis, keine Feststellung einer erfolgten Kompromittierung.
  3. Newsletter: NEWSLETTER_SENDING_ENABLED ausgeschaltet lassen, bis U05–U07 umgesetzt und geprüft sind. Der neue Schalter ersetzt deren fachliche Korrektur nicht.
  4. Restore-Abnahme: DB-alt/Disk-neu, Disk-alt/DB-neu und beide-alt inklusive Export, wiederverwendeten IDs, Signaturfehlern, Quarantäne und erneuten Abgleichen in einer isolierten Umgebung testen. Bei Wiederregistrierung sind Legal Holds laut bestehendem Code/Runbook zusätzlich zu prüfen. Ein externes Tombstone-Ziel bleibt nach E16 offen.
  5. Bildbetrieb: Proxy-/PHP-Uploadgrenzen, Speicherbedarf großer Bilder, Zeitbudget des synchronen Vorabchecks und Browser-/CDN-Verhalten nach OG-Korrekturen sind weiterhin nicht durch diese Prüfung belegt. Das sind die bereits dokumentierten Mess-/Abnahmepunkte, keine hier gemessenen neuen Ausfälle.
  6. Qualität: MySQL-/Worker-Integration und Browser-/Tastaturabnahme bleiben offen. Die gelesenen Tests belegen gute Abdeckung der normalen und vieler einzelner Fehlerwege, aber nicht die oben aufgeführten Kombinationen und Parallelabläufe. Volt-Code ist nicht vollständig durch den bisherigen PHPStan-Scope abgesichert.
  7. Dokumentationsstand: Die lokalen Löschungen unter docs/ sind weiterhin vorhanden und wurden nicht verändert. Der Plan enthält neben dem aktuellen Block-9-Stand noch historische „offen“-Aussagen zu bereits zusammengeführten Loginabschlüssen und Snapshot-Versionierung. Bei einer späteren Dokumentationspflege historische Protokolle deutlich von der aktuellen Restliste trennen.
  8. Weitere Fachfreigaben: Die im letzten Audit benannten Freigaben für Rechtstexte, Vorabcheck und spätere Bildfunktionen werden durch dieses technische Audit nicht ersetzt. E19–E34 sind im Plan ausdrücklich als noch zu bestätigende Entscheidungen gekennzeichnet; eine spätere Bestätigung wurde hier nicht festgestellt.

6. Empfohlene nächste Umsetzung

Reihenfolge Arbeit Abnahmebedingung
1 O01: Signaturnachweis für jede sperrwirksame Bounce-Art Auch wiederholte unsignierte Soft-DSNs sperren keine Adresse.
2 O02: Testumgebung und tatsächliche DB-Konfiguration absichern URL-Override wird vor Verbindung und RefreshDatabase abgefangen.
3 O03: Bildfreigabe, Veröffentlichungsmarke und Nachweisauftrag koordinieren Ein öffentlich freigegebenes Bild gerät trotz Snapshotfehler nicht in die Sofortlöschung.
4 O04/O05: Restore-Identität und Export-Vertrauen korrigieren Kein fremder Archiveintrag wird gelöscht, kein gelöschter Nachweis bei ungültigem Export wieder registriert.
5 O07/O08: Archivaufträge exklusiv und vollständig abschließen Ein Ereignis ergibt einen vollständigen, reproduzierbaren Nachweis; fehlende Dateien bleiben sichtbar.
6 O06: Fairer Fortschritt im Scheduler Blockierte Kandidaten halten andere fällige Veröffentlichungen nicht dauerhaft auf.
7 Betroffene Regressionstests, dann isolierte Gesamtsuite und Betriebsabnahme Aktuelle Ergebnisse dokumentiert; Live-Schritte und Zugangsdatenrotation nachgewiesen.

Abnahmeurteil: Die Kernrichtung der Nacharbeit ist sachgerecht. Die pauschale Kennzeichnung von Block 9 als vollständig erledigt ist für Sicherheit, Bildnachweis und Restore jedoch zu weitgehend. Die oben beschriebenen Restfehler sollten vor der jeweiligen Freigabe geschlossen werden. Dieses Audit selbst hat ausschließlich dokumentiert.