tests/legal: Testdatenbank-Guard vor den DB-Traits, Flags in phpunit.xml gepinnt, Zustimmung an gezeigte Fassung gebunden (Umsetzungsaudit 25.09.: N22, N06, 5.3)
- Tests\TestCase prüft die Standardverbindung in setUpTraits(), also vor RefreshDatabase — bisher erst nach dem Refresh (N22). Eigener Test. - phpunit.xml pinnt IMAGE_RIGHTS_V2, IMAGE_PRECHECK(_STAGE) und LEGAL_ACCEPTANCE_GATE auf die Vorgaben aus .env.example; die Suite hing sonst an der lokalen .env (Audit Abschnitt 6). - Zustimmungsgate: die Livewire-Komponenten tragen die beim Rendern gezeigten Fassungen (Trait-Hook rendering); weicht die aktuelle Fassung beim Absenden ab, wird die Annahme abgelehnt und der neue Wortlaut gezeigt. Ein fehlendes Pflichtdokument ist bei aktivem Gate ein Konfigurationsfehler (N06). - Audit vom 25.09., Decision-Update vom 22.09. und Plan Fassung 2 versioniert; die Antwortkopie „…-211.md" entfernt; U10-Formulierung im Plan präzisiert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
aaeee24412
commit
34fa342c7f
10 changed files with 1436 additions and 10 deletions
|
|
@ -7,6 +7,7 @@ use App\Models\LegalDocumentAcceptance;
|
||||||
use App\Services\Legal\LegalAcceptanceGate;
|
use App\Services\Legal\LegalAcceptanceGate;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KI-Kennzeichnung im Submit-Flow (Art. 50 KI-VO, Decision-Update 08.07.2026).
|
* KI-Kennzeichnung im Submit-Flow (Art. 50 KI-VO, Decision-Update 08.07.2026).
|
||||||
|
|
@ -30,6 +31,26 @@ trait InteractsWithAiDeclaration
|
||||||
*/
|
*/
|
||||||
public bool $legalAcceptanceConfirmed = false;
|
public bool $legalAcceptanceConfirmed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fassungen (type => version), die beim letzten Rendern im Submit-Modal
|
||||||
|
* standen. Der Schalter bestätigt genau diese; wechselt zwischen Anzeige
|
||||||
|
* und Absenden die Fassung, wird die Annahme abgelehnt und der neue
|
||||||
|
* Wortlaut gezeigt (Umsetzungsaudit 25.09.2026, N06). Livewire signiert
|
||||||
|
* den Zustand, der Browser kann die Werte nicht verändern.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
public array $legalAcceptanceVersions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Livewire-Trait-Hook: läuft nach der Aktion, unmittelbar vor dem
|
||||||
|
* Rendern — die Property trägt danach exakt die gezeigten Fassungen.
|
||||||
|
*/
|
||||||
|
public function renderingInteractsWithAiDeclaration(): void
|
||||||
|
{
|
||||||
|
$this->legalAcceptanceVersions = LegalAcceptanceGate::versionsOf(app(LegalAcceptanceGate::class)->requiredFor(auth()->user()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pflichtprüfung vor dem Einreichen — Fehler erscheinen direkt im
|
* Pflichtprüfung vor dem Einreichen — Fehler erscheinen direkt im
|
||||||
* Submit-Modal. Entwurf-Speichern bleibt frei von dieser Pflicht.
|
* Submit-Modal. Entwurf-Speichern bleibt frei von dieser Pflicht.
|
||||||
|
|
@ -45,12 +66,22 @@ trait InteractsWithAiDeclaration
|
||||||
'aiDeclarationConfirmed.accepted' => __('Bitte bestätigen, dass die Angaben zum KI-Einsatz vollständig und wahrheitsgemäß sind.'),
|
'aiDeclarationConfirmed.accepted' => __('Bitte bestätigen, dass die Angaben zum KI-Einsatz vollständig und wahrheitsgemäß sind.'),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (app(LegalAcceptanceGate::class)->requiredFor(auth()->user())->isNotEmpty()) {
|
$required = app(LegalAcceptanceGate::class)->requiredFor(auth()->user());
|
||||||
|
|
||||||
|
if ($required->isNotEmpty()) {
|
||||||
$rules['legalAcceptanceConfirmed'] = ['accepted'];
|
$rules['legalAcceptanceConfirmed'] = ['accepted'];
|
||||||
$messages['legalAcceptanceConfirmed.accepted'] = __('Bitte nehmen Sie die aktuelle Fassung der AGB und der Veröffentlichungsrichtlinien an.');
|
$messages['legalAcceptanceConfirmed.accepted'] = __('Bitte nehmen Sie die aktuelle Fassung der AGB und der Veröffentlichungsrichtlinien an.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->validate($rules, $messages);
|
$this->validate($rules, $messages);
|
||||||
|
|
||||||
|
if ($required->isNotEmpty() && LegalAcceptanceGate::versionsOf($required) !== $this->legalAcceptanceVersions) {
|
||||||
|
$this->legalAcceptanceConfirmed = false;
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'legalAcceptanceConfirmed' => __('Die Bedingungen wurden zwischenzeitlich aktualisiert. Bitte lesen Sie die neue Fassung und nehmen Sie sie erneut an.'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,7 +93,7 @@ trait InteractsWithAiDeclaration
|
||||||
$gate = app(LegalAcceptanceGate::class);
|
$gate = app(LegalAcceptanceGate::class);
|
||||||
$required = $gate->requiredFor(auth()->user());
|
$required = $gate->requiredFor(auth()->user());
|
||||||
|
|
||||||
if ($required->isEmpty() || ! $this->legalAcceptanceConfirmed) {
|
if ($required->isEmpty() || ! $this->legalAcceptanceConfirmed || LegalAcceptanceGate::versionsOf($required) !== $this->legalAcceptanceVersions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use App\Models\LegalDocumentAcceptance;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zustimmungsgate vor jeder Einreichung (Bildkennzeichnung Block G,
|
* Zustimmungsgate vor jeder Einreichung (Bildkennzeichnung Block G,
|
||||||
|
|
@ -41,12 +42,37 @@ class LegalAcceptanceGate
|
||||||
}
|
}
|
||||||
|
|
||||||
return collect(self::TYPES)
|
return collect(self::TYPES)
|
||||||
->map(fn (string $type): ?LegalDocument => LegalDocument::current($type))
|
->map(function (string $type): LegalDocument {
|
||||||
->filter()
|
// Ein fehlendes Pflichtdokument ist ein Konfigurationsfehler und
|
||||||
|
// darf das Gate nicht still verkleinern (Umsetzungsaudit
|
||||||
|
// 25.09.2026, N06).
|
||||||
|
return LegalDocument::current($type)
|
||||||
|
?? throw new RuntimeException(sprintf('Zustimmungsgate: kein gültiges Dokument vom Typ „%s“ hinterlegt (legal_documents).', $type));
|
||||||
|
})
|
||||||
->reject(fn (LegalDocument $document): bool => $this->hasAccepted($user, $document))
|
->reject(fn (LegalDocument $document): bool => $this->hasAccepted($user, $document))
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fassungen je Typ, wie sie dem Nutzer gerade gezeigt werden — die
|
||||||
|
* Bestätigung im Browser wird an genau diese Kennungen gebunden.
|
||||||
|
*
|
||||||
|
* @param iterable<int, LegalDocument> $documents
|
||||||
|
* @return array<string, string> type => version
|
||||||
|
*/
|
||||||
|
public static function versionsOf(iterable $documents): array
|
||||||
|
{
|
||||||
|
$versions = [];
|
||||||
|
|
||||||
|
foreach ($documents as $document) {
|
||||||
|
$versions[$document->type] = $document->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($versions);
|
||||||
|
|
||||||
|
return $versions;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasAccepted(User $user, LegalDocument $document): bool
|
public function hasAccepted(User $user, LegalDocument $document): bool
|
||||||
{
|
{
|
||||||
return LegalDocumentAcceptance::query()
|
return LegalDocumentAcceptance::query()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,431 @@
|
||||||
|
# Decision-Update: Bildrechte, KI-Kennzeichnung, Beweisarchiv
|
||||||
|
|
||||||
|
*Stand: 22.09.2026 · Gilt für presseecho.de, businessportal24.com, pressekonto.com · Status: Defaults festgelegt, Umsetzung freigegeben, anwaltliche Prüfung ausstehend*
|
||||||
|
|
||||||
|
Dieses Dokument fasst die Ergebnisse aus dem Anwaltspaket „Bildkennzeichnung", zwei Prüfrunden und den daraus getroffenen Entscheidungen zusammen. Es ersetzt die Anlagen 1 bis 4 des Pakets vom 15.09.2026 und ergänzt `Umsetzung_Bildnachweis_KI-Kennzeichnung.md` sowie `handover_loesch-anfragen.md`. Rechtliche Aussagen sind Einordnungen, keine Rechtsberatung; offene Rechtsfragen stehen in Abschnitt 8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Was sich ändert
|
||||||
|
|
||||||
|
| Bereich | Vorher | Jetzt |
|
||||||
|
|---|---|---|
|
||||||
|
| Freistellung | Eigener, verschuldensunabhängiger Absatz in § 8a | Ein Satz, Verweis auf § 8 Abs. 2 (verschuldensabhängig) |
|
||||||
|
| Rechteeinräumung | Vermischt in § 8a | Nur in § 9; neuer Abs. 1a für Feeds und Open Graph |
|
||||||
|
| Partnerweitergabe | Pauschal in § 8a | Ausschließlich nach § 9 Abs. 2; letzter Satz dort präzisiert |
|
||||||
|
| Erstattung bei Bildentfernung | Ausgeschlossen | Kein Ausschluss |
|
||||||
|
| KI-Kennzeichnung | Sperre ohne Korrekturweg | Sperre nur bei geprüfter Signatur, Korrekturweg, Trennung Kundenangabe/Portalkennzeichnung |
|
||||||
|
| Bestätigungstext | Verweis auf nicht existierenden § 8a | Verweis auf gültige Fassung, Anerkennung der AGB mit Version |
|
||||||
|
| Aufbewahrung | Platzhalter | Beweisarchiv: 3 Jahre zum Jahresende; Entwürfe sofort löschen |
|
||||||
|
| Prüfversprechen | „geprüfte Inhalte", „priorisierte redaktionelle Prüfung" | „automatisierte Prüfung", Pro-Versprechen gestrichen |
|
||||||
|
| Altbestand | Unklar | Kein Verantwortlich-Hinweis ohne Protokoll; Legacy-Default „Originalfoto" entfällt |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Bestätigte Entscheidungen (Geschäftsentscheidungen, selbst getroffen)
|
||||||
|
|
||||||
|
| Nr. | Entscheidung | Default | Verworfene Alternative |
|
||||||
|
|---|---|---|---|
|
||||||
|
| E1 | Vertragspartner bei Agenturen | Immer der Kontoinhaber; Firmenprofil des Auftraggebers ist nur Zuordnung | Auftraggeber als Vertragspartner (Vollmachtsnachweis, Betreuungsaufwand) |
|
||||||
|
| E2 | Originaldatei | Immer speichern, zusätzlich veröffentlichte Fassung | Reduziert bei Bildern ohne C2PA (zwei Regeln) |
|
||||||
|
| E3 | Aufbewahrung veröffentlicht | 3 Jahre nach dem 31.12. des Entfernungsjahres, `legal_hold` bei Streit und Regress | Zehn Jahre (§ 852 BGB) |
|
||||||
|
| E4 | Entwürfe, nie veröffentlicht | Sofort löschen, kein Archiv | 30 Tage |
|
||||||
|
| E5 | „Veröffentlicht" | Hatte irgendwann eine öffentlich abrufbare URL (auch kurze Auto-Freigabe bei Gelb) | |
|
||||||
|
| E6 | RSS | Weiterhin ohne Bilder; Rechteeinräumung deckt nur Anzeige im Feedreader | Bild-Feed mit Übernahmerecht |
|
||||||
|
| E7 | Open Graph / Teaser | OG-Variante mit eingebranntem Nachweisbalken; Teaser mit Nachweis in `alt`/`title` und Listen-Bildunterschrift | Ersatzbild ohne Kundenbild |
|
||||||
|
| E8 | Erstattung bei Bildentfernung | Kein Ausschluss (Anti-Zombie) | Eingeschränkter Ausschluss |
|
||||||
|
| E9 | KI-Sperre | Nur bei geprüfter Signatur, Dateibindung und vertrauenswürdigem Aussteller; sonst nur Vorbelegung | Sperre bei jedem Metadatenfund |
|
||||||
|
| E10 | Admin-Upload | Veröffentlichung erst nach Kundenbestätigung per Magic-Link („Bild bestätigen"), gleiches Protokoll | Kundenmail ohne Widerspruch |
|
||||||
|
| E11 | Prüfversprechen | Öffentlich „automatisierte Prüfung"; Pro-Tarif ohne Prüfvorteil | „Qualitätsprüfung" als Leistungsbegriff |
|
||||||
|
| E12 | AGB-Einbeziehung | Bild-Schalter erkennt AGB-Fassung an; zusätzlich Zustimmungsgate je Dokumentversion vor jeder Einreichung | Weiternutzung gilt als Zustimmung (Anti-Zombie-Verstoß) |
|
||||||
|
| E13 | UrhDaG | Nicht anwendbar (§ 2 Abs. 1 Nr. 4), interner Vermerk, keine Klausel | |
|
||||||
|
| E14 | Deploy-Gating | Deploy 1b (Formular, Archiv, Protokoll) geht vor der Anwaltsantwort live; § 8a und DSE v0.4 danach | Alles auf den Anwalt warten |
|
||||||
|
| E15 | KI-Vorab-Check für Bilder | Ja, eng zugeschnitten: Sichtung auf offensichtliche Probleme mit definiertem Ausgang je Befund; setzt nie die KI-Kennzeichnung; öffentlich weiterhin nur „automatisierte Prüfung" (Abschnitt 12) | Kein Check; oder Check als „Bildprüfung" beworben |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Finale Texte
|
||||||
|
|
||||||
|
Durchgehend „Anbieter" (wie in den bestehenden AGB), nicht „Betreiber".
|
||||||
|
|
||||||
|
### 3.1 AGB § 8a Bilder und Medieninhalte (neu, zwischen § 8 und § 9)
|
||||||
|
|
||||||
|
> **§ 8a Bilder und Medieninhalte**
|
||||||
|
>
|
||||||
|
> **(1) Rechte.** Der Kunde reicht nur Bilder ein, für die er über die zur Veröffentlichung erforderlichen Rechte verfügt, einschließlich des Rechts, dem Anbieter die Nutzungen nach § 9 einzuräumen. Reicht der Kunde Bilder für Dritte ein, etwa als Agentur für einen Auftraggeber, bleibt er Vertragspartner des Anbieters und übernimmt die Pflichten nach dieser Ziffer auch für dieses Material.
|
||||||
|
>
|
||||||
|
> **(2) Rechte Dritter.** Der Kunde stellt sicher, dass abgebildete Personen in die Veröffentlichung eingewilligt haben oder eine gesetzliche Ausnahme greift und dass keine Marken-, Eigentums-, Hausrechts- oder sonstigen Rechte Dritter verletzt werden.
|
||||||
|
>
|
||||||
|
> **(3) Bildnachweis.** Der Kunde gibt zu jedem Bild einen Bildnachweis an. Der Anbieter übernimmt den angegebenen Nachweis und ordnet ihn dem Bild zu. Eine routinemäßige rechtliche Prüfung des Nachweises und der zugrunde liegenden Lizenz erfolgt nicht; die Bearbeitung konkreter Hinweise auf Rechtsverletzungen bleibt unberührt. Der Kunde ist dafür verantwortlich, dass der Nachweis den Anforderungen des Urhebers und der Lizenz entspricht.
|
||||||
|
>
|
||||||
|
> **(4) KI-Kennzeichnung.** Der Kunde kennzeichnet Bilder, die mit KI erzeugt oder bearbeitet wurden, beim Einreichen zutreffend. Enthält die Datei signierte Herkunftsdaten (etwa Content Credentials), deren Signatur, Zuordnung zur Datei und Vertrauenswürdigkeit der Herkunft der Anbieter geprüft hat, kennzeichnet der Anbieter das Bild entsprechend; der Kunde kann diese Kennzeichnung nicht abschwächen. Der Anbieter prüft begründete Einwände unverzüglich unter Einbeziehung der verfügbaren Nachweise und korrigiert unzutreffende Kennzeichnungen. Der Kunde ist für seine eigenen Angaben verantwortlich; eine hiervon abweichende, vom Anbieter gesetzte Kennzeichnung gilt nicht als Erklärung des Kunden. Eigene Pflichten des Kunden aus der Verordnung (EU) 2024/1689 bleiben unberührt.
|
||||||
|
>
|
||||||
|
> **(5) Freistellung.** Verletzt der Kunde schuldhaft eine Pflicht nach den Absätzen 1 bis 4, gilt § 8 Abs. 2 entsprechend.
|
||||||
|
>
|
||||||
|
> **(6) Entfernung.** Bei einem begründeten Hinweis auf eine Rechtsverletzung kann der Anbieter das betroffene Bild vorläufig entfernen; die Pressemitteilung bleibt im Übrigen veröffentlicht, soweit dies ohne das Bild möglich ist. § 8 Abs. 3 Satz 2 gilt entsprechend.
|
||||||
|
>
|
||||||
|
> **(7) Beweissicherung.** Der Anbieter speichert zu jedem veröffentlichten Bild die Originaldatei, die veröffentlichte Bildfassung mit Nachweis und Kennzeichnung sowie die Angaben und die Bestätigung des Kunden. Die Speicherung endet drei Jahre nach dem Ende des Jahres, in dem die Veröffentlichung des Bildes endete. Bei einer laufenden Auseinandersetzung über das Bild dauert die Speicherung bis zu deren Abschluss einschließlich eines Rückgriffs an. Bilder, die nie veröffentlicht wurden, werden bei Löschung oder Ersetzung sofort gelöscht. Näheres regelt die Datenschutzerklärung.
|
||||||
|
|
||||||
|
### 3.2 AGB § 9 Rechteeinräumung (Änderungen)
|
||||||
|
|
||||||
|
> **(1)** Der Kunde räumt dem Anbieter an den eingereichten Inhalten das einfache, räumlich unbeschränkte Recht ein, die Inhalte für die Dauer der Veröffentlichung auf den angeschlossenen Portalen öffentlich zugänglich zu machen, zu vervielfältigen und technisch aufzubereiten. Zur technischen Aufbereitung gehören insbesondere Formatierung, Teaser, responsive Darstellungen sowie bei Bildern Skalierung, Zuschnitt auf Vorschauformate, Formatkonvertierung, das Anbringen des Bildnachweises und einer etwaigen KI-Kennzeichnung am Bild und das Eintragen dieser Angaben in die Metadaten der ausgelieferten Bildfassungen. Bildzuschnitte und Kennzeichnungen erfolgen unter Wahrung der wesentlichen Bildaussage; sinnentstellende Bearbeitungen sind nicht gestattet.
|
||||||
|
>
|
||||||
|
> **(1a)** Das Recht nach Absatz 1 umfasst die Bereitstellung von Teasern und Vorschaubildern in den Feeds der Portale zur Anzeige in Feedreadern sowie als Vorschau beim Teilen von Links auf die Portale in sozialen Netzwerken und Messengern. Der Kunde gestattet dem Anbieter, den jeweiligen Diensten das Abrufen, die für die Vorschaufunktion erforderliche Speicherung und die Anzeige dieser Teaser und Vorschaubilder im Zusammenhang mit dem geteilten Link zu ermöglichen. Eine darüber hinausgehende Verwertung durch diese Dienste ist nicht Gegenstand dieser Einräumung. Eine Weitergabe an Partnerportale richtet sich ausschließlich nach Absatz 2.
|
||||||
|
>
|
||||||
|
> **(2)** *[Satz 1 und 2 unverändert.]* Ohne eine solche Auswahl oder Tarifzugehörigkeit erfolgt keine Weitergabe an Partnerportale zur dortigen eigenständigen Veröffentlichung. Die Bereitstellung von Teasern und Vorschaubildern nach Absatz 1a bleibt unberührt.
|
||||||
|
>
|
||||||
|
> **(3)** Eine inhaltliche Veränderung, Kürzung oder Bearbeitung über die Absätze 1 und 1a hinaus erfolgt nicht.
|
||||||
|
>
|
||||||
|
> **(4)** Die Rechteeinräumung nach Absatz 1 bleibt für die Dauer der Archivierung bestehen, auch wenn das Kundenkonto endet (§ 11 Abs. 5). Das Recht zur nichtöffentlichen Vervielfältigung und Speicherung zum Zweck der Beweissicherung besteht im erforderlichen Umfang für die Aufbewahrungsdauer nach § 8a Abs. 7 fort.
|
||||||
|
|
||||||
|
### 3.3 Bestätigungstext im Upload-Formular
|
||||||
|
|
||||||
|
Die Textversion ist an die AGB-Version gebunden. Solange AGB v0.6 gilt, lautet der Verweis „§ 8 und § 9" und „§ 8 Abs. 2"; mit v0.7 „§ 8, § 8a und § 9" und „§ 8 Abs. 2 und § 8a Abs. 5". Vor Vergabe einer neuen Kennung prüfen, welche Kennung der heute produktive Wortlaut trägt; Kennungen werden nie wiederverwendet. AGB-Fassung und Link werden beim Rendern aufgelöst und mit der Bestätigung gespeichert.
|
||||||
|
|
||||||
|
**Langtext (aufklappbar hinter „Details"):**
|
||||||
|
|
||||||
|
> Ich bestätige, dass ich zur Veröffentlichung dieses Bildes auf presseecho.de und businessportal24.com sowie im Rahmen der von mir gebuchten Verbreitung berechtigt bin. Das umfasst die Rechte des Urhebers, die Rechte abgebildeter Personen sowie Marken- und sonstige Rechte Dritter. Mein Bildnachweis und meine Angabe zur KI-Kennzeichnung sind zutreffend. Für diese Einreichung gelten die Allgemeinen Geschäftsbedingungen in der Fassung vom [Datum, Version] ([Link]), die ich hiermit anerkenne. Die Regeln zu Bildern stehen dort in [§ 8 und § 9 / § 8, § 8a und § 9]; bei schuldhafter Verletzung meiner vertraglichen Pflichten gelten die Freistellungsregelungen in [§ 8 Abs. 2 / § 8 Abs. 2 und § 8a Abs. 5].
|
||||||
|
|
||||||
|
**Kurzform am Schalter:**
|
||||||
|
|
||||||
|
> Ich bestätige, dass ich zur Veröffentlichung dieses Bildes berechtigt bin, meine Angaben zutreffen und die AGB (Fassung vom [Datum]) gelten.
|
||||||
|
|
||||||
|
**API:** Partner geben denselben Langtext per `rights_confirmed: true` mit `confirm_text_version` ab. Die Bindung an die AGB-Fassung entsteht über die API-Nutzungsbedingungen, die dieselbe Fassung referenzieren.
|
||||||
|
|
||||||
|
### 3.4 Verantwortlich-Zeile am Bild
|
||||||
|
|
||||||
|
Nur wo ein Bestätigungsprotokoll existiert:
|
||||||
|
|
||||||
|
> Bild und Angaben: [Firma], eingereicht am [Datum]
|
||||||
|
|
||||||
|
Altbestand ohne Protokoll: nur „Foto: [Nachweis]" bzw. „Quelle: [Firma]". Das Detailfenster zeigt zusätzlich die Quelle der KI-Kennzeichnung (Angabe des Kunden / aus geprüften Herkunftsdaten / aus Metadaten vorbelegt).
|
||||||
|
|
||||||
|
### 3.5 Veröffentlichungsrichtlinien
|
||||||
|
|
||||||
|
**Ersatz für den Absatz zu Prüfung und Qualitätssiegel (derzeit `richtlinien.blade.php`, Zeile 36):**
|
||||||
|
|
||||||
|
> Jede Mitteilung durchläuft vor der Veröffentlichung eine automatisierte Prüfung. Sie sortiert Spam, reine Werbung und Beiträge aus, die keine Pressemitteilung sind, ordnet die Kategorie zu und bewertet die formale Qualität des Textes. Eine Prüfung, ob Inhalte richtig sind oder ob Sie die Rechte an Texten und Bildern besitzen, findet nicht statt. Dafür sind Sie verantwortlich.
|
||||||
|
|
||||||
|
**Neuer Abschnitt „Bilder" (nach „Was eine Pressemitteilung bei uns ausmacht", vor den Ablehnungskategorien):**
|
||||||
|
|
||||||
|
> **Bilder**
|
||||||
|
>
|
||||||
|
> Zu jedem Bild gehört ein Bildnachweis; er erscheint öffentlich unter dem Bild. Laden Sie nur Bilder hoch, für die Sie die Rechte haben, und achten Sie darauf, dass abgebildete Personen mit der Veröffentlichung einverstanden sind. Bilder, die mit KI erzeugt oder bearbeitet wurden, kennzeichnen Sie beim Hochladen; enthält die Datei signierte Herkunftsdaten dazu, prüfen wir sie und zeigen die Kennzeichnung am Bild. Halten Sie eine von uns gesetzte Kennzeichnung für falsch, schreiben Sie uns; wir prüfen das und korrigieren Fehler. Bilder ohne geklärte Rechte entfernen wir; das Meldeverfahren gilt auch für Bilder.
|
||||||
|
|
||||||
|
Version `richtlinien` in `legal_documents` hochzählen.
|
||||||
|
|
||||||
|
### 3.6 Preisseite und Mails
|
||||||
|
|
||||||
|
| Stelle | Text |
|
||||||
|
|---|---|
|
||||||
|
| „Inklusive in jedem Tarif" | Automatisierte Prüfung jeder Mitteilung vor der Veröffentlichung (Spam, Werbung, Kategorie, formale Qualität) |
|
||||||
|
| Pro-Tarif | „Priorisierte redaktionelle Prüfung" ersatzlos streichen; kein Ersatzversprechen |
|
||||||
|
| Ablehnungs- und Feedbackmails | „Unsere automatisierte Prüfung hat …" statt „Wir haben geprüft …" |
|
||||||
|
| Sonstige Stellen mit „geprüft", „Siegel", „redaktionell" | Suchen und auf dieselbe Formel bringen |
|
||||||
|
|
||||||
|
### 3.7 Datenschutzerklärung
|
||||||
|
|
||||||
|
**Neuer Abschnitt 9a (nach „9. Veröffentlichte Pressemitteilungen und Medienprivileg"):**
|
||||||
|
|
||||||
|
> **9a. Bilder in Pressemitteilungen: Originaldateien, Herkunftsprüfung und Rechtebestätigung**
|
||||||
|
>
|
||||||
|
> Wer ein Bild zu einer Pressemitteilung hochlädt, bestätigt dabei, zur Veröffentlichung berechtigt zu sein, und macht Angaben zum Bildnachweis und zum Einsatz von KI. Zu jedem veröffentlichten Bild speichern wir:
|
||||||
|
>
|
||||||
|
> - die hochgeladene Originaldatei einschließlich der darin enthaltenen Metadaten (zum Beispiel Kameradaten, Urheberangaben, Herkunftsdaten wie Content Credentials),
|
||||||
|
> - die veröffentlichte Fassung des Bildes mit dem angezeigten Bildnachweis und der angezeigten KI-Kennzeichnung,
|
||||||
|
> - das Ergebnis unserer automatischen Auswertung der Herkunftsdaten und die daraus von uns gesetzte Kennzeichnung, getrennt von der Angabe des Hochladenden,
|
||||||
|
> - die Bestätigung des Hochladenden mit Zeitpunkt, der Fassung des bestätigten Textes und der zugrunde liegenden AGB, dem angemeldeten Konto beziehungsweise dem verwendeten API-Zugang, der Firma, für die hochgeladen wurde, einer pseudonymisierten Ableitung der IP-Adresse und einer gekürzten Kennung des verwendeten Browsers.
|
||||||
|
>
|
||||||
|
> Die IP-Ableitung wird mit einem geheimen Schlüssel gebildet. Sie ist pseudonymisiert, nicht anonym: Mit dem Schlüssel könnten wir prüfen, ob eine bestimmte Adresse zu ihr passt; ohne den Schlüssel ist das nicht möglich.
|
||||||
|
>
|
||||||
|
> Zweck dieser Speicherung ist der Nachweis, welche Datei mit welchen Angaben eingereicht wurde, falls Dritte Rechte an dem Bild geltend machen, sowie die Nachvollziehbarkeit unserer KI-Kennzeichnung. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse liegt in der Nachweisführung und Rechtsverteidigung. Eine dokumentierte Abwägung liegt vor.
|
||||||
|
>
|
||||||
|
> Die Originaldatei ist nicht öffentlich abrufbar. Öffentlich ausgeliefert werden ausschließlich verkleinerte Fassungen, aus denen Standortdaten, Geräteseriennummern und sonstige technische Metadaten des Originals entfernt sind. Bildnachweis und KI-Kennzeichnung werden am Bild angezeigt.
|
||||||
|
>
|
||||||
|
> Bilder, die hochgeladen, aber nie veröffentlicht werden, löschen wir mit ihrer Löschung oder Ersetzung sofort und vollständig. Wird ein veröffentlichtes Bild entfernt oder ersetzt oder endet die Veröffentlichung der Mitteilung, werden die genannten Daten in unser Beweisarchiv (Abschnitt 9b) überführt und dort nach den dort genannten Fristen gelöscht.
|
||||||
|
|
||||||
|
**Neuer Abschnitt 9b:**
|
||||||
|
|
||||||
|
> **9b. Beweisarchiv nach Entfernung von Inhalten**
|
||||||
|
>
|
||||||
|
> Wenn wir eine Pressemitteilung oder Teile davon aus dem öffentlichen Angebot entfernen, etwa auf Antrag, im Meldeverfahren, nach einer Datenschutzanfrage oder auf eigene Entscheidung, bleibt sie in allen öffentlich sichtbaren Bereichen unserer Portale entfernt, einschließlich Suche, Feeds und Sitemap. Wir beantragen zusätzlich die Löschung aus den gängigen Suchmaschinen; auf deren Aktualisierung haben wir keinen Einfluss.
|
||||||
|
>
|
||||||
|
> Einen auf das Notwendige beschränkten Datensatz zu der entfernten Veröffentlichung bewahren wir in einem getrennten, verschlüsselten Archiv auf: den zuletzt veröffentlichten Stand der Inhalte, die Angaben zum Einreicher (Konto und Firma), die Zeitpunkte von Veröffentlichung und Entfernung, den Grund der Entfernung als Kategorie sowie bei Änderungen den vorherigen und den neuen Stand. Zweck ist ausschließlich die Wahrung unserer Nachweis- und Verteidigungsinteressen gegenüber Rechteinhabern, Betroffenen und Behörden. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; bei Daten, deren Löschung beantragt wurde, stützen wir die weitere Aufbewahrung auf Art. 17 Abs. 3 lit. e DSGVO.
|
||||||
|
>
|
||||||
|
> Zugriff auf das Archiv haben nur Administratoren des Anbieters; jeder Zugriff wird protokolliert. Das Archiv wird für keinen anderen Zweck verwendet und nicht an Dritte weitergegeben, außer zur Rechtsverteidigung an unsere rechtlichen Berater oder auf gesetzlicher Grundlage an Behörden und Gerichte.
|
||||||
|
>
|
||||||
|
> Archiveinträge werden drei Jahre nach dem Ende des Jahres, in dem der Inhalt entfernt wurde, automatisch gelöscht. Läuft zu einem Inhalt eine rechtliche Auseinandersetzung, bleibt der Eintrag bis zu deren Abschluss einschließlich eines Rückgriffs bestehen. Die Löschung wird ohne Inhalt protokolliert. Sicherungskopien werden innerhalb von vier Wochen überschrieben; nach einer Wiederherstellung aus einer Sicherung werden bereits gelöschte Einträge erneut entfernt.
|
||||||
|
|
||||||
|
**Zeile für Abschnitt 13 (Speicherdauer):**
|
||||||
|
|
||||||
|
> Beweisarchiv zu entfernten Inhalten und Bildern (Abschnitte 9a, 9b): drei Jahre nach Jahresende der Entfernung, bei laufender Auseinandersetzung bis zu deren Abschluss. Nie veröffentlichte Entwürfe: sofort bei Löschung.
|
||||||
|
|
||||||
|
Drei Zusagen darin setzen laufende Mechanik voraus und dürfen erst mit ihr veröffentlicht werden: Zugriffsprotokoll, Tombstone-Abgleich nach Restore, dokumentierte Interessenabwägung (Abschnitt 4.4).
|
||||||
|
|
||||||
|
### 3.8 Ergänzungen für `handover_loesch-anfragen.md`
|
||||||
|
|
||||||
|
**Abschnitt 5, technische Checkliste, neue Punkte:**
|
||||||
|
|
||||||
|
- [ ] Vor der Änderung: Archiveintrag anlegen (letzter veröffentlichter Stand, Diff, Antrag, Aktenvermerk). Der Altwert steht nur dort, nie im Audit-Log.
|
||||||
|
- [ ] Bei Auskunftsersuchen nach Art. 15: Archivinhalte zu der Person gehören in die Auskunft.
|
||||||
|
|
||||||
|
**Abschnitt 7, Kommunikationsregeln, neuer Punkt:**
|
||||||
|
|
||||||
|
- „Gelöscht" steht in keinem Schreiben mehr ohne den Archivhinweis. Standardabsatz:
|
||||||
|
|
||||||
|
> Die genannten Angaben haben wir aus allen öffentlich sichtbaren Bereichen unserer Portale entfernt, einschließlich Suche, Feeds und Sitemap, und die Löschung aus dem Google-Index beantragt; die Aktualisierung dort dauert erfahrungsgemäß einige Tage. Einen auf das Notwendige beschränkten Datensatz zu dieser Veröffentlichung bewahren wir zur Wahrung unserer Nachweis- und Verteidigungsinteressen (Art. 17 Abs. 3 lit. e DSGVO) getrennt vom laufenden Betrieb, verschlüsselt und zugriffsbeschränkt auf. Er ist nicht öffentlich, wird für keinen anderen Zweck verwendet und drei Jahre nach Jahresende automatisch gelöscht.
|
||||||
|
|
||||||
|
### 3.9 Interner Vermerk UrhDaG (ins Decision Log)
|
||||||
|
|
||||||
|
> Einordnung UrhDaG, Stand 22.09.2026. Die Portale erbringen im Auftrag gewerblicher Einreicher kostenpflichtige Veröffentlichungs- und Verbreitungsleistungen für Pressemitteilungen. § 2 Abs. 1 Nr. 1 bis 3 UrhDaG können erfüllt sein. Nr. 4 ist nicht erfüllt: Pressemitteilungen werden von ihren Urhebern zur unentgeltlichen Verbreitung freigegeben; ein Lizenzmarkt, mit dem die Portale um Publikum konkurrieren könnten, existiert nicht. Die öffentliche Leserseite (Nachrichtenbereich, Firmen-Newsrooms, Suchmaschinenindexierung) verbreitet dieselben Meldungen, die gleichzeitig über Verteiler und andere Portale frei verfügbar sind. Konkurrierende Angebote sind andere Presseportale und Verteiler, nicht Online-Inhaltedienste im Sinne der Richtlinie 2019/790. Eine Ausnahme nach § 3 wird nicht in Anspruch genommen. Die Hoster-Einordnung nach DSA und UrhG bleibt davon unberührt. Wiedervorlage: bei redaktionell kuratierten Inhalten über Partner, bei Aufnahme in Google News, bei einem Umsatz über 1 Mio. Euro.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Beweisarchiv: Spezifikation
|
||||||
|
|
||||||
|
### 4.1 Auslöser und Inhalt
|
||||||
|
|
||||||
|
| Ereignis | Archivierter Datensatz | Frist |
|
||||||
|
|---|---|---|
|
||||||
|
| Veröffentlichtes Bild entfernt oder ersetzt | Originaldatei, veröffentlichte Fassung mit Nachweis und Kennzeichnung, Bestätigungsprotokoll | 3 Jahre zum Jahresende |
|
||||||
|
| PM depubliziert (kostenpflichtig, Meldeverfahren, eigene Entscheidung, Ende der Laufzeit) | Letzter veröffentlichter Stand (Text, Bilder, Kontaktfelder), Einreicher (Konto, Firma), Zeitpunkte, Grund als Kategorie | 3 Jahre zum Jahresende |
|
||||||
|
| DSGVO-Änderung (Schwärzung, Pseudonymisierung) | Diff alt/neu, Referenz auf Antrag und Antwortschreiben, Aktenvermerk | 3 Jahre zum Jahresende |
|
||||||
|
| Bildkennzeichnung korrigiert | Alter und neuer Wert, Quelle, Begründung, Bearbeiter | 3 Jahre zum Jahresende |
|
||||||
|
| Entwurf gelöscht oder Bild ersetzt, nie veröffentlicht | Kein Archiveintrag; Datei und Bestätigung sofort löschen | sofort |
|
||||||
|
| Einreichung im Freigabelauf abgelehnt, nie veröffentlicht | Mediendateien sofort löschen; Ablehnungsdatensatz (Status, Zeitpunkt, Prüfergebnis, Einreicher) bleibt als Vertragsdokumentation in der normalen Datenbank | sofort (Medien) |
|
||||||
|
|
||||||
|
Nicht im Archiv: Zugriffsstatistiken, Zahlungsdaten, sonstige Kontodaten.
|
||||||
|
|
||||||
|
### 4.2 Datenmodell
|
||||||
|
|
||||||
|
`archive_entries`:
|
||||||
|
|
||||||
|
| Feld | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `id` | |
|
||||||
|
| `subject_type`, `subject_id` | PM, Bild, Änderung |
|
||||||
|
| `event` | `image_removed`, `image_replaced`, `pm_depublished`, `gdpr_change`, `label_corrected` |
|
||||||
|
| `actor_type`, `actor_id` | Admin, Kunde (Magic-Link), System |
|
||||||
|
| `reason_category` | Enum: Antrag Kunde, Meldeverfahren, DSGVO, Laufzeitende, eigene Entscheidung |
|
||||||
|
| `payload_path` | Pfad im verschlüsselten Archiv-Storage |
|
||||||
|
| `payload_hash` | SHA-256 des Payloads |
|
||||||
|
| `retain_until` | 31.12. des Ereignisjahres + 3 Jahre |
|
||||||
|
| `legal_hold` | bool, mit `legal_hold_note`, `legal_hold_set_by`, `legal_hold_set_at` |
|
||||||
|
| `deleted_at` | Tombstone; Payload ist dann weg, Zeile bleibt ohne Inhalt |
|
||||||
|
| `created_at` | |
|
||||||
|
|
||||||
|
`archive_access_log`: `entry_id`, `admin_id`, `accessed_at`, `purpose` (Freitext, Pflicht).
|
||||||
|
|
||||||
|
### 4.3 Verhalten
|
||||||
|
|
||||||
|
- Storage getrennt vom Web-Root, Inhalte verschlüsselt, Schlüssel im Ploi-Environment, nicht in der Datenbank.
|
||||||
|
- Einträge sind append-only. Es gibt keine Bearbeitung, nur Anlegen, Tombstone, Zugriff.
|
||||||
|
- Täglicher Job: alle Einträge mit `retain_until < heute` und `legal_hold = false` löschen, Tombstone setzen.
|
||||||
|
- `legal_hold` setzt nur ein Admin mit Pflichtvermerk. Wird bei Streit und laufendem Regress gesetzt und beim Abschluss wieder entfernt (Frist läuft dann ab Aufhebungsdatum neu: 31.12. + 3 Jahre).
|
||||||
|
- Jeder lesende Zugriff geht durch einen Service, der `archive_access_log` schreibt. Direkter Storage-Zugriff ist keine vorgesehene Nutzung.
|
||||||
|
- Post-Restore-Job: Nach Einspielen eines Backups werden alle Einträge, die im Tombstone-Stand als gelöscht markiert sind, erneut aus dem Storage entfernt. Der Tombstone-Stand selbst wird täglich exportiert, damit er den Restore überlebt.
|
||||||
|
- Das Audit-Log der Anwendung referenziert nur `archive_entries.id`, nie Altwerte im Klartext.
|
||||||
|
- Export eines Eintrags für den Anwalt: signiertes ZIP mit Payload, Metadaten und Hash.
|
||||||
|
|
||||||
|
### 4.4 Interessenabwägung (Kurzfassung für die Dokumentation)
|
||||||
|
|
||||||
|
Berechtigtes Interesse: Nachweis und Verteidigung gegenüber Ansprüchen von Urhebern, abgebildeten Personen, Betroffenen (einschließlich Art. 82 DSGVO) und Behörden; Rückgriff gegen Kunden nach § 8 Abs. 2 AGB. Erforderlichkeit: Ohne den Datensatz kann weder belegt werden, was wann öffentlich war, noch was der Kunde bestätigt hat; das gilt gerade nach einer Löschung auf Antrag. Verhältnismäßigkeit: Nur der letzte veröffentlichte Stand und die Bestätigung, keine Nutzungsdaten; verschlüsselt, zugriffsprotokolliert, nicht öffentlich, kein anderer Zweck; Frist entspricht der Regelverjährung; Entwürfe ohne Veröffentlichung werden nicht aufbewahrt. Betroffeneninteressen: Die Daten waren zuvor öffentlich; die Aufbewahrung ist nicht öffentlich und erweitert die Kenntnis niemandes. Ergebnis: Interessen des Anbieters überwiegen; Art. 17 Abs. 3 lit. e greift für Löschanträge.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Systemänderungen (Backlog)
|
||||||
|
|
||||||
|
### 5.1 Vor Deploy 1b (Formular, Archiv, Protokoll)
|
||||||
|
|
||||||
|
| Nr. | Änderung | Grund | Fundstelle |
|
||||||
|
|---|---|---|---|
|
||||||
|
| S1 | Originalarchiv (privater Storage) und Snapshot der veröffentlichten Bildfassung mit Nachweis und Kennzeichnung | Original wird heute nach Verarbeitung gelöscht | `ImageService.php:156` |
|
||||||
|
| S2 | Bestätigungsprotokoll: Bild-ID, SHA-256 der Datei, Kontoinhaber, Firmenprofil, `confirm_text_version`, `agb_version`, Sprache, Zeitpunkt, IP-HMAC, gekürzter UA, Uploadweg (Formular/API/Admin) | Zuordnung Vertragspartner, Datei, Wortlaut | |
|
||||||
|
| S3 | Tabelle unveränderlicher Bestätigungstexte; Kennung des heutigen Wortlauts ermitteln, neue Kennung nur einmal vergeben | Jede Bestätigung verweist auf genau eine Fassung | |
|
||||||
|
| S4 | API v1: `rights_confirmed` und `confirm_text_version` Pflicht, sonst 422; API-Nutzungsbedingungen mit AGB-Fassung | Gleicher Beweisstand auf allen Wegen | `StorePressReleaseImageRequest.php:35` |
|
||||||
|
| S5 | Legacy-Default „Originalfoto" entfernen, `not_declared`; Altbestand mit `legacy`-Flag, keine Verantwortlich-Zeile | Keine erfundene Erklärung | `LegacyPressReleaseImageController.php:69` |
|
||||||
|
| S6 | Zwei Felder: `customer_ai_declaration` und `portal_ai_label` mit `label_source` (`c2pa_verified` / `iptc_prefill` / `customer`); Quelle im Detailfenster | Trennung Kundenangabe/Portalkennzeichnung (§ 8a Abs. 4) | |
|
||||||
|
| S7 | Admin-Upload: Status „wartet auf Kundenbestätigung", Magic-Link „Bild bestätigen", Bestätigung ins selbe Protokoll | E10 | |
|
||||||
|
| S8 | `published_ever`-Flag an PM und Bild; steuert Archiv oder Sofortlöschung | E4, E5 | |
|
||||||
|
| S9 | Sofortlöschung bei Entwurf gelöscht, Bild ersetzt, Ablehnung (nur Medien) | E4 | |
|
||||||
|
| S10 | Richtlinien-Text, Bilder-Abschnitt, Preisseite, Mail-Templates auf „automatisierte Prüfung" | E11, Hoster-Status | `richtlinien.blade.php:36`, Preisseite |
|
||||||
|
| S11 | Bestätigungstext mit aufgelöster AGB-Fassung und Link rendern | E12 | |
|
||||||
|
|
||||||
|
### 5.2 Vor AGB v0.7 / DSE v0.4
|
||||||
|
|
||||||
|
| Nr. | Änderung | Grund |
|
||||||
|
|---|---|---|
|
||||||
|
| S12 | `legal_document_acceptances` (Nutzer, Dokumenttyp, Version, Zeitpunkt, Kontext); Gate vor jeder Einreichung, aktiver Klick | Einbeziehung für alle Kunden, auch ohne Bild-Upload |
|
||||||
|
| S13 | `archive_entries`, `archive_access_log`, Löschjob, `legal_hold`, Post-Restore-Job, Tombstone-Export | Abschnitt 4 |
|
||||||
|
| S14 | Archivierung bei Depublikation, DSGVO-Änderung und Kennzeichnungskorrektur anbinden; Audit-Log ohne Altwerte | Abschnitt 4.1 |
|
||||||
|
| S15 | OG-Bildvariante mit Nachweisbalken; Teaser mit Nachweis in `alt`/`title` und Listen-Bildunterschrift | E7 |
|
||||||
|
| S16 | Beschnitt-Steuerung: Fokuspunkt je Bild oder Flag „nicht beschneiden" (Letterbox) | § 9 Abs. 1 letzter Satz |
|
||||||
|
| S17 | Korrekturweg: Admin-Override der Kennzeichnung mit Begründung; Zwischenzustand „Bild vorläufig zurückgehalten"; Archiveintrag `label_corrected` | § 8a Abs. 4 |
|
||||||
|
| S18 | Zugriffsprotokoll auf Originalarchiv, sonst DSE-Satz streichen | DSE 9a/9b |
|
||||||
|
| S19 | Löschanfragen-Handover und Antwortvorlagen um Archivabsatz und Art.-15-Schritt ergänzen | Abschnitt 3.8 |
|
||||||
|
|
||||||
|
### 5.3 Phase 2/3
|
||||||
|
|
||||||
|
| Nr. | Änderung | Grund |
|
||||||
|
|---|---|---|
|
||||||
|
| S20 | C2PA-Verifikation mit Signaturprüfung und Trust-List (c2patool); ohne gültige Prüfung nur Vorbelegung, keine Sperre | E9 |
|
||||||
|
| S21 | IPTC/XMP Urheber, Copyright, Kennzeichnung in ausgelieferte Varianten schreiben (exiftool) | § 95c-Restrisiko, geringer Aufwand |
|
||||||
|
| S22 | Re-Sync-Sperre auch für Bilder, sobald API-Partner welche liefern | Write-once-Problem |
|
||||||
|
| S23 | Bildklausel in den Syndikationsvertrag, bevor connektar Bilder liefert | Frage 8 des Pakets |
|
||||||
|
| S24 | KI-Vorab-Check für Bilder nach Abschnitt 12; erste Ausbaustufe nur Stock-Wasserzeichen, dann die übrigen Kategorien | E15; direkt nach Deploy 1b, Aufwand ein bis zwei Tage auf Basis der Text-Prüfstrecke |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Reihenfolge
|
||||||
|
|
||||||
|
| Schritt | Inhalt | Bedingung |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | S1 bis S11 | keine |
|
||||||
|
| 2 | Deploy 1b: neues Formular, Bestätigungstext (v0.6-Verweis), Richtlinien-Version hochzählen, DSE-Minimalabsatz ohne die drei mechanikabhängigen Zusagen | Schritt 1 |
|
||||||
|
| 3 | Gekürztes Anwaltspaket mit diesem Dokument als Anlage versenden | parallel |
|
||||||
|
| 4 | S12 bis S19 | parallel zur Anwaltsfrist |
|
||||||
|
| 5 | AGB v0.7 (§ 8a, § 9), DSE v0.4 (9a, 9b, 13), Bestätigungstext-Fassung mit § 8a-Verweis, Zustimmungsgate aktivieren | Anwaltsantwort und S12 bis S19 |
|
||||||
|
| 6 | S20 bis S23 | nach Bedarf |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Anwaltsagenda (gekürzt, mit Prüfannahmen)
|
||||||
|
|
||||||
|
Eine Sitzung, zusammen mit den vier offenen Fragen aus `handover_loesch-anfragen.md`, Abschnitt 9. Jede Frage mit unserem Default, damit der Anwalt bestätigt oder widerspricht. Anlagen: dieses Dokument (Abschnitte 3 und 4), bestehende AGB v0.6, Screenshots Formular und Nachweiszeile.
|
||||||
|
|
||||||
|
| Nr. | Frage | Unser Default |
|
||||||
|
|---|---|---|
|
||||||
|
| A1 | AGB-Konsistenz: § 8a ohne eigene Freistellung, § 9 Abs. 1, 1a, 2, 4 wie in 3.2. Hält das im B2B nach § 307? Ist der Konflikt mit § 9 Abs. 2 gelöst? Deckt Abs. 1a die Vervielfältigung durch soziale Netzwerke? Reicht „Wahrung der wesentlichen Bildaussage" gegen § 14/§ 39 UrhG? | Texte wie in 3.1/3.2 |
|
||||||
|
| A2 | Hoster-Status: Reichen Richtlinien-Formulierung, Verantwortlich-Zeile und Preisseiten-Wording? UrhDaG-Vermerk (3.9) tragfähig? | E11, E13 |
|
||||||
|
| A3 | Vertretung: Kontoinhaber als Vertragspartner (§ 8a Abs. 1 Satz 2), API-Bedingungen mit Bestätigung je Upload, Admin-Upload mit Magic-Link. Reicht das für den Regress? | E1, E10 |
|
||||||
|
| A4 | Beweisarchiv: Pauschale Drei-Jahres-Frist für alle Ereignistypen vertretbar (Art. 17 Abs. 3 lit. e, Erforderlichkeit)? Interessenabwägung 4.4 ausreichend? Originaldatei immer speichern verhältnismäßig? | E2, E3, E4 |
|
||||||
|
| A5 | AGB-Einbeziehung: Anerkennung über den Bild-Schalter mit Fassung und Link wirksam? Bei Abos und Kontingenten: ist eine neue Einreichung ein neuer Vertrag (§ 311)? Reicht das Zustimmungsgate für Bestandskunden? | E12 |
|
||||||
|
| A6 | Art. 50 KI-VO: Macht die vom Portal gesetzte, nicht abschwächbare Kennzeichnung das Portal zum Mitbetreiber nach Abs. 4? Korrekturweg ausreichend? | E9, § 8a Abs. 4 |
|
||||||
|
| A7 | Schlussfrage: Trägt das Gesamtpaket (Original, veröffentlichte Fassung, Protokoll, AGB, Archiv) im Regressfall? Was fehlt? | |
|
||||||
|
|
||||||
|
Als Prüfannahme, nicht als Frage: Nachweisposition unter dem Bild genügt § 13 UrhG bei sichtbarer Zuordnung; Metadatenentfernung in Varianten verstößt bei sichtbarem Nachweis nicht gegen § 95c; Rückschreiben erfolgt in Phase 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Was der Anwalt kippen könnte
|
||||||
|
|
||||||
|
| Default | Risiko | Reaktion ohne Umbau |
|
||||||
|
|---|---|---|
|
||||||
|
| Pauschale Drei-Jahres-Frist | Erforderlichkeit je Fall | `retain_until` je `event` anpassen |
|
||||||
|
| AGB-Einbeziehung über Bild-Schalter bei Abos | § 311: kein neuer Vertrag | Zustimmungsgate S12; Bestandskunden einmalig durchschleusen |
|
||||||
|
| Originaldatei immer speichern | Datenminimierung bei Bildern ohne C2PA | Job: Originale ohne Herkunftsdaten auf Hash plus Metadatenauszug reduzieren |
|
||||||
|
| UrhDaG-Vermerk | Nr. 4 anders bewertet | Kleiner-Diensteanbieter-Status (Umsatz unter 1 Mio. Euro) prüfen lassen, Lizenzbemühungen dokumentieren |
|
||||||
|
| Kontoinhaber als Vertragspartner | Agentur nicht zur Bindung des Auftraggebers befugt | Ist bereits eingepreist: Regress richtet sich gegen den Kontoinhaber |
|
||||||
|
| Erstattung bei Bildentfernung | Wirtschaftlich zu weit | Gezielte Klausel bei vom Kunden zu vertretender Verletzung nachziehen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Wiedervorlagebedingungen
|
||||||
|
|
||||||
|
- Redaktionelle Prüfung über einen Partner wird eingeführt: Hoster-Status und UrhDaG neu bewerten, Richtlinien und Preisseite anpassen.
|
||||||
|
- Aufnahme in Google News oder ein Leserangebot wird eigenes Produkt: UrhDaG-Vermerk aktualisieren.
|
||||||
|
- connektar oder ein anderer API-Partner liefert Bilder: S22, S23, Bildklausel im Syndikationsvertrag vor Aktivierung.
|
||||||
|
- Bild-Feed mit Übernahmerecht gewünscht: § 9 Abs. 1a erweitern, neue Rechtefrage.
|
||||||
|
- Jahresumsatz über 1 Mio. Euro: UrhDaG-Sicherheitsnetz entfällt.
|
||||||
|
- Anwaltsantwort liegt vor: Abschnitt 8 abarbeiten, AGB v0.7 und DSE v0.4 freigeben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Anti-Zombie-Check
|
||||||
|
|
||||||
|
| Regel | Prüfung |
|
||||||
|
|---|---|
|
||||||
|
| Keine versteckten Kosten | Bildentfernung ohne Erstattungsausschluss; kein Credit-Abzug im DSGVO-Pfad; Korrekturweg kostenfrei |
|
||||||
|
| Keine automatische Zustimmung | Zustimmungsgate mit aktivem Klick; keine „Weiternutzung gilt als Zustimmung" |
|
||||||
|
| Transparenz | Prüfung als „automatisiert" beschrieben; Pro-Versprechen ohne Substanz gestrichen; Archiv in DSE offen benannt; „gelöscht" nie ohne Archivhinweis |
|
||||||
|
| Kein Betreuungsaufwand ohne Nutzen | Entwürfe sofort weg statt Frist; Archiv löscht automatisch; Korrekturweg ist Mailbox plus Vermerk |
|
||||||
|
| Direkter menschlicher Kontakt | Korrekturweg und Löschanfragen laufen über eine Person, nicht über ein Formularlabyrinth |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Offene Punkte nach Dringlichkeit
|
||||||
|
|
||||||
|
1. Kennung des heute produktiven Bestätigungstextes ermitteln (S3), bevor irgendetwas versioniert wird.
|
||||||
|
2. Alle Stellen mit „geprüft", „Siegel", „redaktionell" auf Preisseite, in Richtlinien und Mails finden (S10).
|
||||||
|
3. Anwaltspaket nach Abschnitt 7 versenden, mit den vier Löschanfragen-Fragen.
|
||||||
|
4. Interessenabwägung 4.4 als eigenes Dokument im Rechtsordner ablegen.
|
||||||
|
5. Entscheidung, ob der Pro-Tarif nach Streichung des Prüfversprechens einen anderen echten Vorteil bekommt oder so bleibt.
|
||||||
|
6. DSE-Templatevariablen aus dem Bestand (Log-Fristen, Magic-Link-Tokendauer, Stripe-Entität, KI-Anbieter) mit v0.4 gemeinsam füllen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Update 22.09.2026: KI-Vorab-Check für Bilder (E15, S24)
|
||||||
|
|
||||||
|
### 12.1 Einordnung
|
||||||
|
|
||||||
|
Ein Vision-Modell sichtet jedes hochgeladene Bild auf offensichtliche Probleme. Es ist keine Rechteprüfung und wird öffentlich nicht als solche beschrieben. Der Nutzen liegt in wenigen Kategorien, allen voran Stock-Wasserzeichen, dem häufigsten Abmahnfall. Der Check senkt die Wahrscheinlichkeit eines Falls; Archiv, Protokoll, Bestätigung und AGB bleiben unverändert nötig.
|
||||||
|
|
||||||
|
| Kann zuverlässig | Kann nicht |
|
||||||
|
|---|---|
|
||||||
|
| Stock-Wasserzeichen (Getty, Shutterstock, Adobe Stock, iStock) | Beurteilen, ob der Kunde die Rechte hat |
|
||||||
|
| Nacktheit, Gewalt, offensichtlich rechtswidrige Inhalte | Wissen, ob abgebildete Personen eingewilligt haben |
|
||||||
|
| Screenshots, Dokumentenscans, Ausweise, Briefe | Verlässlich erkennen, ob ein Bild KI-generiert ist |
|
||||||
|
| Personen erkennbar ja/nein, Kfz-Kennzeichen lesbar | Urheber identifizieren |
|
||||||
|
| Fremde Logos prominent, Werbetafeln im Bild | Lizenzbedingungen kennen |
|
||||||
|
| Technische Mängel: zu klein, verzerrt, Platzhalter, Kollage | |
|
||||||
|
|
||||||
|
### 12.2 Drei Regeln (Hoster-Status)
|
||||||
|
|
||||||
|
1. **Wissen verpflichtet.** Jeder Befund hat einen definierten Ausgang: automatische Ablehnung, Admin-Queue mit Entscheidung und Vermerk, oder Freigabe. Kein Befund liegt nur im Log. Ein gesehener und ignorierter Treffer ist schlechter als kein Check (Art. 6 DSA).
|
||||||
|
2. **Öffentlich bleibt es „automatisierte Prüfung".** Keine Formulierung, die eine Bild- oder Rechteprüfung verspricht. Intern: Sichtung auf offensichtliche Probleme. Art. 7 DSA deckt freiwillige Prüfungen ab, solange sie nicht zur Übernahme fremder Inhalte als eigene werden.
|
||||||
|
3. **Die KI setzt nie die Kennzeichnung.** Eine Einschätzung „wirkt KI-generiert" löst höchstens einen Hinweis an den Kunden aus. Die Sperre bleibt an geprüfte C2PA-Signaturen gebunden (E9).
|
||||||
|
|
||||||
|
### 12.3 Ablauf
|
||||||
|
|
||||||
|
Bild beim Upload auf 1024 px lange Kante verkleinern, Metadaten strippen, an das Modell senden, strukturierte Antwort mit festen Kategorien und Konfidenz verlangen. Der Prompt fragt keine Identifizierung von Personen ab, nur „Person(en) erkennbar".
|
||||||
|
|
||||||
|
| Befund | Ausgang | Kunde sieht |
|
||||||
|
|---|---|---|
|
||||||
|
| Stock-Wasserzeichen, Nacktheit, Gewalt, Ausweis oder Dokument | Automatische Ablehnung des Bildes | Grund in einem Satz, Bild neu hochladen |
|
||||||
|
| Screenshot, Kollage, fremdes Logo prominent, Kennzeichen lesbar | Admin-Queue | „Wird kurz gesichtet"; Meldung bleibt im Freigabelauf |
|
||||||
|
| Personen erkennbar | Kein Eingriff, Hinweis im Formular | „Auf dem Bild sind Personen zu sehen. Mit Ihrer Bestätigung erklären Sie, dass sie mit der Veröffentlichung einverstanden sind." |
|
||||||
|
| Wirkt KI-generiert, Kunde hat „nein" angegeben | Hinweis, keine Änderung | „Bitte prüfen Sie Ihre Angabe zur KI-Kennzeichnung." |
|
||||||
|
| Unklar oder Modellfehler | Durchlassen, Vermerk im Protokoll | nichts |
|
||||||
|
|
||||||
|
Automatisch abgelehnt wird nur bei den vier eindeutigen Kategorien; alles andere geht in die Queue oder wird zum Hinweis, damit Fehlurteile keine Kunden kosten.
|
||||||
|
|
||||||
|
### 12.4 Antwortschema (Modell)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stock_watermark": {"hit": false, "confidence": 0.0},
|
||||||
|
"nudity_or_violence": {"hit": false, "confidence": 0.0},
|
||||||
|
"document_or_id": {"hit": false, "confidence": 0.0},
|
||||||
|
"screenshot_or_collage": {"hit": false, "confidence": 0.0},
|
||||||
|
"third_party_logo_prominent": {"hit": false, "confidence": 0.0},
|
||||||
|
"license_plate_readable": {"hit": false, "confidence": 0.0},
|
||||||
|
"persons_visible": {"hit": false, "confidence": 0.0},
|
||||||
|
"looks_ai_generated": {"hit": false, "confidence": 0.0},
|
||||||
|
"technical_defect": {"hit": false, "note": ""}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Schwellen je Kategorie konfigurierbar; Start: automatische Ablehnung ab 0,85, Queue ab 0,6.
|
||||||
|
|
||||||
|
### 12.5 Protokoll und Archiv
|
||||||
|
|
||||||
|
Das Ergebnis wird als eigenes Feld im Bestätigungsprotokoll gespeichert: Kategorien mit Konfidenz, Modell und Version, Zeitpunkt, getroffener Ausgang, bei Queue die Admin-Entscheidung mit Vermerk. Es ist der Beleg eigener Sorgfalt und wandert mit dem Bild ins Beweisarchiv.
|
||||||
|
|
||||||
|
### 12.6 Datenschutz
|
||||||
|
|
||||||
|
- Bilder mit Gesichtern an einen KI-Anbieter zu senden ist eine Verarbeitung personenbezogener Daten: AVV mit dem Anbieter, keine Speicherung und kein Training beim Anbieter, Verkleinern und Metadaten strippen als Minimierung.
|
||||||
|
- DSE: Abschnitt zur KI-gestützten Prüfung um Bilder ergänzen. Vorschlag: „Hochgeladene Bilder werden in verkleinerter Form und ohne Metadaten an [KI-Anbieter] übermittelt und dort automatisiert auf offensichtliche Probleme gesichtet (zum Beispiel Wasserzeichen von Bildagenturen, unzulässige Inhalte). Eine Speicherung beim Anbieter erfolgt nicht. Das Ergebnis speichern wir mit der Rechtebestätigung (Abschnitt 9a)."
|
||||||
|
- Art. 22 DSGVO: Automatische Ablehnung betrifft im B2B-Kontext kaum eine natürliche Person; der Weg zum Menschen (Queue, Kontakt) ist offen und muss es bleiben.
|
||||||
|
|
||||||
|
### 12.7 Ausbaustufen
|
||||||
|
|
||||||
|
| Stufe | Umfang | Aufwand |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Nur Stock-Wasserzeichen, automatische Ablehnung | etwa eine Stunde auf der Text-Prüfstrecke |
|
||||||
|
| 2 | Alle Kategorien nach 12.3 mit Queue und Hinweisen | ein bis zwei Tage |
|
||||||
|
| 3 | Schwellen aus echten Fällen nachjustieren; Auswertung der Queue-Entscheidungen | laufend, gering |
|
||||||
|
|
||||||
|
### 12.8 Wiedervorlage
|
||||||
|
|
||||||
|
- Häufen sich Fehlurteile in einer Kategorie: Kategorie von Ablehnung auf Queue herabstufen, nicht abschalten.
|
||||||
|
- Wird der Check nach außen beworben oder vom Partner als Prüfleistung eingebracht: Hoster-Status neu bewerten (Abschnitt 9).
|
||||||
|
|
@ -218,7 +218,7 @@ Nicht aufgenommen aus Block 7: U05–U07, weil das Kampagnenmodul nicht in Betri
|
||||||
| 7.3 | U02 | M | Jobs speichern Ergebnis und Audit nur per bedingtem UPDATE `where content_hash = analysierter Hash`; trifft es nicht, Audit als verworfen und neuer Lauf. `publish()` prüft bei automatischen Quellen (`ki`, `scheduler`) unter der Zeilensperre, dass die jüngste gültige Klassifikation den aktuellen Hash trägt; sonst kein Publish, neuer Prüfjob mit Routing, `StaleClassificationException`. Admin-Publish bleibt manuelle Übersteuerung. |
|
| 7.3 | U02 | M | Jobs speichern Ergebnis und Audit nur per bedingtem UPDATE `where content_hash = analysierter Hash`; trifft es nicht, Audit als verworfen und neuer Lauf. `publish()` prüft bei automatischen Quellen (`ki`, `scheduler`) unter der Zeilensperre, dass die jüngste gültige Klassifikation den aktuellen Hash trägt; sonst kein Publish, neuer Prüfjob mit Routing, `StaleClassificationException`. Admin-Publish bleibt manuelle Übersteuerung. |
|
||||||
| 7.4 | U03 | S | Rotes Ergebnis eines Re-Checks ohne Routing bei einer PM in Review führt zur Ablehnung mit Autorenmail wie bei der Erstprüfung. Veröffentlichte PMs bleiben unverändert (Admin-Signal). |
|
| 7.4 | U03 | S | Rotes Ergebnis eines Re-Checks ohne Routing bei einer PM in Review führt zur Ablehnung mit Autorenmail wie bei der Erstprüfung. Veröffentlichte PMs bleiben unverändert (Admin-Signal). |
|
||||||
| 7.5 | U09 | S | Middleware sperrt die sieben ungenutzten Fortify-2FA-Verwaltungsrouten (404); Challenge-Routen bleiben. Test je Route mit passwortbestätigter Session. |
|
| 7.5 | U09 | S | Middleware sperrt die sieben ungenutzten Fortify-2FA-Verwaltungsrouten (404); Challenge-Routen bleiben. Test je Route mit passwortbestätigter Session. |
|
||||||
| 7.6 | U10 | S | API-Update prüft die resultierende Kategorie gegen das resultierende Portal, sobald `portal` oder `category_id` im Request steht. |
|
| 7.6 | U10 | S | API-Update prüft die resultierende Kategorie gegen das resultierende Portal beim tatsächlichen Portalwechsel oder bei mitgesendeter `category_id`; ein erneut gesendetes, unverändertes Portal löst allein keine Prüfung aus (Präzisierung 25.09.2026, Umsetzungsaudit 5.3). |
|
||||||
| 7.7 | U11, U12 | M | Bedingtes UPDATE mit Status, Enddatum und `cancelled_at`; nur getroffene Zeilen erzeugen Ereignis und Mail. Marker `grandfathering_expired_at` und `grandfathering_notified_at`; Mail-Einplanung in zweitem Schritt, nachholbar beim nächsten Lauf. |
|
| 7.7 | U11, U12 | M | Bedingtes UPDATE mit Status, Enddatum und `cancelled_at`; nur getroffene Zeilen erzeugen Ereignis und Mail. Marker `grandfathering_expired_at` und `grandfathering_notified_at`; Mail-Einplanung in zweitem Schritt, nachholbar beim nächsten Lauf. |
|
||||||
| 7.8 | U13 | S | Zeitbudget 480 s pro Lauf (Lock 600 s), Rest beim nächsten Lauf. Marke nur bedingt fortschreiben (`where value = alte Marke`), sonst Transaktion abbrechen. |
|
| 7.8 | U13 | S | Zeitbudget 480 s pro Lauf (Lock 600 s), Rest beim nächsten Lauf. Marke nur bedingt fortschreiben (`where value = alte Marke`), sonst Transaktion abbrechen. |
|
||||||
| 7.9 | 7.2 | S | Policy-`before()` bejaht nur aktive Super-Admins. |
|
| 7.9 | 7.2 | S | Policy-`before()` bejaht nur aktive Super-Admins. |
|
||||||
|
|
|
||||||
499
dev/2026-09-11-audit/UMSETZUNGSAUDIT-2026-09-25.md
Normal file
499
dev/2026-09-11-audit/UMSETZUNGSAUDIT-2026-09-25.md
Normal file
|
|
@ -0,0 +1,499 @@
|
||||||
|
# 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](UMSETZUNGSAUDIT-2026-09-18.md), [Entwicklungsplan mit Block 7 und Entscheidungen E7–E9](ENTWICKLUNGSPLAN.md), [Bildkennzeichnungsplan, Fassung 2 einschließlich Fortschreibung vom 25.09.](bildkennzeichnung-entwicklungsplan-2026-09-21.md), [Decision-Update vom 22.09.](Decision-Update_Bildrechte_Beweisarchiv_2026-09-22.md), [ursprünglicher Audit](AUDIT.md).
|
||||||
|
|
||||||
|
**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 `RefreshDatabase` zu 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
|
||||||
|
|
||||||
|
1. **Vergleichsbasis und Entscheidungen:** letzten Audit, Block 7, Commitfolge, aktuelle Bildplanung und Decision-Update abgeglichen; ältere Entscheidungen von späteren Änderungen getrennt.
|
||||||
|
2. **Restbefunde U01–U13:** betroffene Services, Jobs, Policies, Middleware, Migrationen und Regressionstests erneut gelesen.
|
||||||
|
3. **Bildaufnahme:** Formular, API, Admin-Upload, Dateiprüfung, Originalspeicherung, Metadatenauswertung, Vision-Vorprüfung und Rechtebestätigung verfolgt.
|
||||||
|
4. **Bildausgabe und Lebenszyklus:** Einreichung, Veröffentlichung, direkte Medienroute, Credits, OpenGraph, Snapshot, Korrektur, Entfernung und Entwurfspurge als zusammenhängende Abläufe geprüft.
|
||||||
|
5. **Archiv und Bestätigungen:** Verschlüsselung, Zugriffswege, Legal Hold, Fristlöschung, Tombstones, Restore-Abgleich und Anbindung an Anonymisierung/Korrektur geprüft.
|
||||||
|
6. **Feeds, Canonical und Berichte:** Feed-Auswahl, Sichtbarkeitsangaben, Reader-Gruppierung, Canonical-Lebenszyklus, Alterskohorten und Indexverteilung geprüft.
|
||||||
|
7. **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.
|
||||||
|
8. **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. `member` muss 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. `algorithmicallyEnhanced` soll 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
|
||||||
|
|
||||||
|
```text
|
||||||
|
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](../2026-09-01/basiswert-0.1-kohorten.json).
|
||||||
|
|
||||||
|
**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()` erzeugt `label_corrected` auch 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:** `PurgePressReleaseEvents` zä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.php` und 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.php` und 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.
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
# Entwicklungsplan: Bildnachweis, KI-Kennzeichnung, Rechtebestätigung, Beweisarchiv
|
||||||
|
|
||||||
|
**Stand:** 22.09.2026, Fassung 2 (Entscheidungen getroffen; Blöcke A bis H und K umgesetzt und committet, Deploy offen; Blöcke I und J stehen aus)
|
||||||
|
**Grundlage:** [Umsetzungsvorschlag 15.09.](bildkennzeichnung-2026-09-15.md), [Feedback-Audit 15.09.](bildkennzeichnung-feedback-audit-2026-09-15.md), [Decision-Update 22.09.](Decision-Update_Bildrechte_Beweisarchiv_2026-09-22.md) und die Antworten des Betreibers vom 21.09. auf die Entscheidungsfragen der Fassung 1. Bei Widerspruch gilt das Decision-Update (im Folgenden „DU"). Code-Abgleich an HEAD `e620ef3` auf `technik/betriebsfixes-2026-09`.
|
||||||
|
**Zweck:** Alle Punkte aus den vier Quellen, die am heutigen Code umsetzbar sind, in einer abarbeitbaren Reihenfolge mit Fix, Test und Aufwand. Gilt zuerst für das Titelbild einer Pressemitteilung; Newsroom-Bilder (Roadmap 1b.3) nutzen denselben Unterbau.
|
||||||
|
|
||||||
|
Die Fassung 1 vom 21.09. enthielt eine Fragenliste und vier Textanlagen für den Anwalt. Beides ersetzt das Decision-Update (Abschnitte 3 und 7) und ist hier gestrichen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Fazit
|
||||||
|
|
||||||
|
Die Richtung des Vorschlags trägt, die Korrekturen des Feedback-Audits sind übernommen, und das Decision-Update hat die offenen Geschäfts- und Rechtsfragen mit Defaults belegt. Der Umfang ist damit klar und größer als in Fassung 1:
|
||||||
|
|
||||||
|
- **Formular und Beweisstand für Bilder** wie geplant: drei Blöcke, zwei Pflichtangaben plus Bestätigung, Original privat, Metadaten gelesen, Zustimmung mit rekonstruierbarem Wortlaut. Sperre nur bei geprüfter Signatur (Phase 2).
|
||||||
|
- **Beweisarchiv** als eigener Baustein, der über Bilder hinausgeht: verschlüsselter, zugriffsprotokollierter Datensatz zu jeder entfernten Veröffentlichung (Bild, Depublikation, DSGVO-Änderung, Kennzeichnungskorrektur), drei Jahre zum Jahresende, `legal_hold`, Löschung nach Restore. Nie veröffentlichte Entwürfe werden sofort gelöscht.
|
||||||
|
- **Vertragliche Einbeziehung**: Bestätigungstext an die AGB-Fassung gebunden; zusätzlich ein Zustimmungsgate je Dokumentversion vor jeder Einreichung.
|
||||||
|
- **Öffentliche Sprache**: „automatisierte Prüfung" statt „geprüft" und „qualitätsgeprüft"; kein Pro-Prüfversprechen.
|
||||||
|
- **Reihenfolge**: Deploy 1b (Formular, Archiv, Protokoll) geht **vor** der Anwaltsantwort live. AGB v0.7 und DSE v0.4 folgen danach.
|
||||||
|
|
||||||
|
### Was der Code-Abgleich ergeben hat
|
||||||
|
|
||||||
|
| Befund | Konsequenz |
|
||||||
|
|---|---|
|
||||||
|
| Dev-Kopie: 14.601 Bilder. 14.554 Legacy-Importe, davon 3.786 an veröffentlichten PMs und **10.768 an inaktiven** (10.463 `rejected` = im Altsystem „offline genommen", 301 archiviert, 4 Entwürfe). 47 Bilder aus dem neuen System (32 an Entwürfen, 15 veröffentlicht), 45 davon mit Rechtebestätigung, 17 als KI gekennzeichnet | E-B11: die 10.768 werden gelöscht (E.5). Der Bestand betrifft danach nur noch 3.786 Bilder an veröffentlichten PMs. |
|
||||||
|
| Weder `exiftool` noch `c2patool` im Container. Imagick 6.9 liest IPTC-, XMP- und EXIF-Profile und kann WebP; PHP-Erweiterung `exif` vorhanden | Phase 1 ohne neue Binärdatei. `c2patool` erst Phase 2, dann in Sail-Image und auf dem Ploi-Server. |
|
||||||
|
| `ImageService::storePressReleaseImage()` löscht das Original nach der Variantenerzeugung und fällt bei fehlender Cover-Variante auf die Quelldatei zurück | Uploader sichert das Original vorher; der Rückfall wird zum Fehler. GD schreibt keine Metadaten in Varianten, die Bereinigung ist heute schon gegeben (Test friert das ein). |
|
||||||
|
| Der heutige Bestätigungstext („Rechte bestätigt" plus Beschreibungssatz im Bildmanager, Zeile 710) hat keine Kennung. 45 Bestätigungen tragen nur `rights_confirmed_at` | DU S3: dieser Wortlaut wird rückwirkend als `img-confirm-v1` archiviert, die 45 Zeilen bekommen einen Verweis darauf. Neue Kennungen ab `img-confirm-v2`. |
|
||||||
|
| Bildmanager kann bestehende Bilder nur löschen, nicht bearbeiten. `assertAiDeclarationComplete()` blockt das Einreichen jeder PM mit Bild ohne `ai_declaration` | Nach E.5 gibt es keine Legacy-Bilder an wiedereinreichbaren PMs mehr; kein Bestandsschutz nötig. |
|
||||||
|
| Legacy-API: `POST /service/pressreleases/{id}/images` setzt `original` fest, keine Statusprüfung. connektar liefert keine Bilder | E-B4: Endpunkt wird abgeschaltet (C.2). |
|
||||||
|
| Öffentliche Wortwahl: „qualitätsgeprüft" in `lang/de/web.php` (Zeilen 189, 231, 233, 249, 255), Footer-Tagline, `presseecho.blade.php:43`; „Geprüfte Mitteilungen" in `lang/de/static.php:533`; „geprüft" in `richtlinien.blade.php:36`; „redaktionelle Qualität" in `agb.blade.php:139, 206`. Die „priorisierte redaktionelle Prüfung" ist im `PlanSeeder` bereits gestrichen | DU S10 (D.6). Die AGB-Stellen gehen mit v0.7 an den Anwalt. |
|
||||||
|
| Depublikation, Archivierung, Statuswechsel und Löschung laufen über `PressReleaseService` (`depublish`, `archive`, `changeStatusFromAdmin`, `deleteFromAdmin`); DSGVO-Änderungen über `AnonymizationService` und `CorrectionService` | Das sind die Anbindungspunkte des Beweisarchivs (F.4). |
|
||||||
|
| OpenGraph-Bild ist heute die Detail-Variante (`release-detail.blade.php:168`); Teaser nutzen `card`/`thumb` ohne Nachweis; RSS-Feeds ohne Bilder; JSON-LD `image` als URL-Liste | DU E6: Feeds bleiben ohne Bilder. DU E7: OG-Variante mit Nachweisbalken und Teaser mit Nachweis (H). |
|
||||||
|
| Kundenoberfläche durchgehend „Sie"; AGB sagen „Anbieter" | Alle neuen Texte in Sie-Form mit „Anbieter". |
|
||||||
|
| Im Decision-Update referenziert, aber nicht im Repository: `Umsetzung_Bildnachweis_KI-Kennzeichnung.md`, `handover_loesch-anfragen.md` | DU S19 (Löschanfragen-Handover) kann erst bearbeitet werden, wenn das Dokument im Ordner liegt. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Entscheidungen
|
||||||
|
|
||||||
|
Alle Fragen der Fassung 1 sind beantwortet (Betreiber, 21.09.2026). Wo das Decision-Update vom 22.09. abweicht, gilt es.
|
||||||
|
|
||||||
|
| Nr. | Frage | Entscheidung | Quelle |
|
||||||
|
|---|---|---|---|
|
||||||
|
| E-B1 | Phase 1 ohne automatische Sperre, nur Vorbelegung mit protokollierter Abweichung; Sperre in Phase 2 nach C2PA-Test | ja | Betreiber; DU E9 |
|
||||||
|
| E-B2 | Aufbewahrungsfrist | drei Jahre nach dem 31.12. des Entfernungsjahres, `legal_hold` bei Streit und Regress; nie veröffentlichte Uploads sofort löschen. Anwalt bestätigt (DU A4) | Betreiber; DU E3, E4 |
|
||||||
|
| E-B3 | Bestand ohne Bildnachweis | **revidiert durch DU 3.4:** ohne Bestätigungsprotokoll keine Verantwortlich-Zeile. Bestand zeigt „Foto: [Nachweis]", ohne Nachweis „Quelle: [Firma]" | DU 3.4 |
|
||||||
|
| E-B4 | Legacy-API ohne die neuen Pflichtfelder | **Legacy-Bildupload abschalten.** Wird nicht genutzt; wer Bilder liefern will, nimmt v1 | Betreiber |
|
||||||
|
| E-B5 | `algorithmicallyEnhanced` | kein KI-Hinweis, nur interner Vermerk | Betreiber |
|
||||||
|
| E-B6 | Upload-Grenze | Formular und API 16 MiB, Pixelgrenze 8.000 × 8.000, vor der Prüfung | Betreiber |
|
||||||
|
| E-B7 | Widerspruch gegen Vorbelegung oder Sperre | Kontaktweg für den Kunden, protokollierter Admin-Korrekturweg mit Zwischenzustand „vorläufig zurückgehalten" | Betreiber; DU S17 |
|
||||||
|
| E-B8 | Anrede | Sie | Betreiber |
|
||||||
|
| E-B9 | Admin-Upload | **revidiert durch DU E10:** Veröffentlichung erst nach Kundenbestätigung per Magic-Link „Bild bestätigen", gleiches Protokoll | DU E10 |
|
||||||
|
| E-B10 | Hartes Löschen | Original und Bestätigung überleben das Löschen bis zum Fristende (im Beweisarchiv) | Betreiber; DU 4 |
|
||||||
|
| E-B11 | Wiedereinreichen alter PMs mit Legacy-Bild | **kein Bestandsschutz.** Legacy-Bilder an nicht veröffentlichten PMs (`rejected`, `archived`, `draft`) werden gelöscht; eine erneute Einreichung läuft ohne Bild | Betreiber |
|
||||||
|
| E-B12 | Bestätigungstabelle und Uploader polymorph für Newsroom-Bilder | ja | Betreiber |
|
||||||
|
| E-B13 | Overlay-Icon | eigenes „i" im Kreis, kein „cr"-Logo | Betreiber |
|
||||||
|
|
||||||
|
Aus dem Decision-Update zusätzlich übernommen (dort E1 bis E14): Kontoinhaber ist Vertragspartner, Firmenprofil nur Zuordnung (E1); Original immer speichern plus veröffentlichte Fassung (E2); „veröffentlicht" = hatte irgendwann eine öffentliche URL, auch kurz (E5); RSS ohne Bilder (E6); OG-Variante mit Nachweisbalken, Teaser mit Nachweis (E7); kein Erstattungsausschluss bei Bildentfernung (E8); „automatisierte Prüfung", kein Pro-Prüfversprechen (E11); Zustimmungsgate je Dokumentversion (E12); UrhDaG nur interner Vermerk (E13); Deploy 1b vor der Anwaltsantwort (E14).
|
||||||
|
|
||||||
|
### Offene Punkte (entschieden 22.09.2026)
|
||||||
|
|
||||||
|
| Nr. | Punkt | Entscheidung |
|
||||||
|
|---|---|---|
|
||||||
|
| O1 | DU 4.1: „Einreichung im Freigabelauf abgelehnt, nie veröffentlicht: Mediendateien sofort löschen." Eine abgelehnte PM kann der Kunde korrigieren und erneut einreichen; das Bild wäre dann weg. | **Entschieden:** Medien erst löschen, wenn die abgelehnte PM durch den bestehenden Entwurfs-Purge fällt oder gelöscht wird, nicht im Moment der Ablehnung. |
|
||||||
|
| O2 | Zustimmungsgate für API-Nutzer (DU S12): je Request, je Token oder je Kontoinhaber? | **Entschieden:** einmal je Kontoinhaber und Dokumentversion über `POST /api/v1/legal/acceptances`; ohne gültige Annahme antwortet `submit` mit 428 und den geforderten Versionen. |
|
||||||
|
| O3 | Die 301 Legacy-Bilder an archivierten PMs: darunter können depublizierte Meldungen sein, für die das Beweisarchiv künftig drei Jahre gälte. | **Entschieden:** mit E-B11 löschen; der Altbestand hat kein Bestätigungsprotokoll, das ein Archiv tragen würde. |
|
||||||
|
| O4 | Schrift für den eingebrannten Nachweisbalken (H.1) muss als TTF im Repository liegen. | **Entschieden:** Inter liegt bereits unter `resources/fonts/` (TTF, 18pt-Schnitte) mit `OFL.txt` (SIL Open Font License 1.1). H.1 nutzt `Inter_18pt-SemiBold.ttf`, kein neuer Font. **Achtung:** der Ordner ist heute untracked (nicht ignoriert, nur nie eingecheckt); mit H.1 werden `Inter_18pt-SemiBold.ttf` und `OFL.txt` committet, sonst fehlt die Schrift auf Live. |
|
||||||
|
| O5 | Pro-Tarif nach Streichung des Prüfversprechens (DU 11.5) | Offen, wird geklärt, wenn D.6 die Preisseite anfasst. Betreiber. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Abgleich mit dem Decision-Update (Backlog S1 bis S24)
|
||||||
|
|
||||||
|
| DU | Inhalt | Im Plan | Bemerkung |
|
||||||
|
|---|---|---|---|
|
||||||
|
| S1 | Originalarchiv und Snapshot der veröffentlichten Fassung | A.6, E.2 | Snapshot beim Veröffentlichen, nicht erst beim Entfernen |
|
||||||
|
| S2 | Bestätigungsprotokoll | A.2 | polymorph (E-B12) |
|
||||||
|
| S3 | Tabelle unveränderlicher Bestätigungstexte, heutige Kennung | A.3 | heutiger Wortlaut = `img-confirm-v1` |
|
||||||
|
| S4 | API v1 Pflichtfelder, 422 | C.1 | |
|
||||||
|
| S5 | Legacy-Default entfernen, Altbestand `legacy` | C.2, A.1 | Endpunkt wird abgeschaltet (E-B4); `legacy` = `rules_version IS NULL` |
|
||||||
|
| S6 | Kundenangabe und Portalkennzeichnung getrennt | A.1 | `ai_declaration` bleibt die Portalkennzeichnung (alle Leser), dazu `ai_declaration_user` und `ai_declaration_source` |
|
||||||
|
| S7 | Admin-Upload mit Kundenbestätigung per Magic-Link | B.4 | |
|
||||||
|
| S8 | `published_ever` an PM und Bild | E.1 | |
|
||||||
|
| S9 | Sofortlöschung nie veröffentlichter Uploads | E.3 | mit O1 |
|
||||||
|
| S10 | Wortwahl „automatisierte Prüfung" | D.6 | Fundstellen in Abschnitt 1 |
|
||||||
|
| S11 | Bestätigungstext mit aufgelöster AGB-Fassung rendern | A.3, B.1 | |
|
||||||
|
| S12 | `legal_document_acceptances`, Zustimmungsgate | G | mit O2 |
|
||||||
|
| S13 | `archive_entries`, `archive_access_log`, Löschjob, `legal_hold`, Post-Restore, Tombstone-Export | F.1 bis F.3 | |
|
||||||
|
| S14 | Archivierung bei Depublikation, DSGVO-Änderung, Korrektur | F.4 | |
|
||||||
|
| S15 | OG-Variante, Teaser mit Nachweis | H.1, H.2 | |
|
||||||
|
| S16 | Beschnitt-Steuerung | H.3 | |
|
||||||
|
| S17 | Korrekturweg mit Zwischenzustand und Archiveintrag | E.6 | |
|
||||||
|
| S18 | Zugriffsprotokoll Originalarchiv | E.4 | |
|
||||||
|
| S19 | Löschanfragen-Handover ergänzen | – | Dokument nicht im Repository |
|
||||||
|
| S20 | C2PA-Verifikation | I.1 bis I.4 | |
|
||||||
|
| S21 | Metadaten in Varianten schreiben | I.5 | Imagick statt exiftool, gleiche Wirkung |
|
||||||
|
| S22 | Re-Sync-Sperre für Bilder | I.6 | |
|
||||||
|
| S23 | Bildklausel im Syndikationsvertrag | – | Betreiber |
|
||||||
|
| S24 | KI-Vorab-Check für Bilder (DU Abschnitt 12, E15) | K.1 bis K.6 | Sichtung auf offensichtliche Probleme, keine Rechteprüfung; Stufe 1 nur Stock-Wasserzeichen, Stufe 2 alle Kategorien; direkt nach Deploy 1b |
|
||||||
|
|
||||||
|
### Nicht aufgenommen
|
||||||
|
|
||||||
|
- Externe Bild-URLs und Sammelupload `images[]` in der API (Feedback F04).
|
||||||
|
- Umbenennung `copyright` in `credit`; vierter Enum-Wert `not_declared` (`NULL` leistet dasselbe, alle Leser prüfen darauf).
|
||||||
|
- Inhaltliche Rechteprüfung von Bildern; Nachprüfung des veröffentlichten Bestands. (Die eng zugeschnittene Sichtung auf offensichtliche Probleme aus DU Abschnitt 12 ist seit dem Update vom 22.09.2026 als Block K aufgenommen; sie prüft keine Rechte und wird öffentlich nicht als Bildprüfung beschrieben.)
|
||||||
|
- Bilder in RSS-Feeds (DU E6).
|
||||||
|
- Reduziertes Original bei Bildern ohne C2PA (DU 8: nur als Reaktion, falls der Anwalt die Datenminimierung anders sieht).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Aufwandsskala und Arbeitsregeln
|
||||||
|
|
||||||
|
| Kürzel | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| S | eine Stunde bis ein halber Tag |
|
||||||
|
| M | ein bis drei Tage |
|
||||||
|
| L | länger oder abhängig von einer Entscheidung |
|
||||||
|
|
||||||
|
Wie im Audit-Entwicklungsplan: erst der Test, der am heutigen Code rot ist, dann der Fix, dann die betroffene Testdatei, am Blockende die gesamte Suite, PHPStan und Pint. Vor Testläufen `php artisan config:clear` (Config-Cache-Falle).
|
||||||
|
|
||||||
|
Der Flag `IMAGE_RIGHTS_V2` bleibt als Deploy-Sicherung: Deploy 1 geht mit `false` live, wird auf Live geprüft und dann eingeschaltet. Er wartet nicht auf den Anwalt (DU E14).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block A — Datenmodell, Texte, Prüfung, Uploader
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A.1 | Migration `press_release_images`, alles nullable: `ai_declaration_user` (Angabe des Kunden), `ai_declaration_source` (Enum: `customer`, `iptc_prefill`, `c2pa_verified`, `admin_correction`), `ai_declaration_locked` (bool, default false), `credit_source` (`customer`, `metadata`, `api`), `provenance_status` (Enum A.4), `provenance` (json), `provenance_checked_at`, `provenance_tool_version`, `original_disk`, `original_path`, `original_sha256` (64), `published_snapshot_path`, `published_snapshot_sha256`, `published_ever_at`, `customer_confirmation_requested_at` (B.4), `rules_version` (string; `NULL` = Bestand, `2026-10` = Formular v2), `focus_x`/`focus_y` (tinyint, H.3), `no_crop` (bool, H.3). `ai_declaration` bleibt die **Portalkennzeichnung**, weil alle Leser (Artikelseite, Kunden- und Adminansicht, PDF, Snapshot in `press_release_ai_declarations`) daran hängen. `license_url` auf 2048 Zeichen. Bestand unangetastet. Test: vorhandene Werte unverändert. | S | keine |
|
||||||
|
| A.2 | Tabelle `image_rights_confirmations`, insert-only (Observer wirft bei Update oder Delete, Ausnahme E.3): `subject_type`/`subject_id` (polymorph, Phase 1 `PressReleaseImage`), `press_release_id`, `company_id` (Zuordnung), `user_id` (Kontoinhaber = Vertragspartner, DU E1), `personal_access_token_id`, `channel` (`form`, `api`, `magic_link`), `legal_text_id` (A.3), `text_snapshot` (gerenderter Wortlaut mit aufgelöster AGB-Fassung und Link), `agb_version`, `locale`, `file_sha256`, `declaration_snapshot` (json: `copyright`, `license_url`, `source_url`, `ai_declaration_user`, `ai_declaration`, `ai_declaration_source`, `locked`, `provenance_status`, `provenance`), `ip_hmac` (HMAC-SHA256 mit eigenem Schlüssel `CONFIRMATION_IP_KEY` und Versionskennung), `user_agent` (255), `confirmed_at`. Fremdschlüssel auf Bild und PM mit `nullOnDelete`, Snapshot-Spalten tragen die Werte weiter (E-B10). Test: hartes Löschen des Bildes lässt die Bestätigung stehen. | S | A.1 |
|
||||||
|
| A.3 | Textarchiv `legal_texts` (insert-only): `key` (z. B. `img-confirm-v2`), `type` (`image_rights_confirmation`), `locale`, `title`, `body` (mit Platzhaltern `{agb_version}`, `{agb_date}`, `{agb_url}`, `{portals}`, `{sections}`, `{indemnity_sections}`), `bound_agb_version`, `body_sha256`, `effective_from`, `note`. Seeder: `img-confirm-v1` = heutiger Wortlaut aus dem Bildmanager (Zeile 710), gebunden an AGB 0.6; `img-confirm-v2` = Langtext und Kurzform aus DU 3.3 mit Verweis „§ 8 und § 9" / „§ 8 Abs. 2", gebunden an AGB 0.6; `img-confirm-v3` = dieselben Texte mit „§ 8, § 8a und § 9" / „§ 8 Abs. 2 und § 8a Abs. 5", gebunden an AGB 0.7, `effective_from` leer bis Deploy 3. Kennungen werden nie wiederverwendet. `LegalText::currentFor($type, $agbVersion, $locale)`. Backfill: die 45 bestehenden Bestätigungen bekommen je eine Zeile in A.2 mit `legal_text_id` = v1, `agb_version` = 0.6, `confirmed_at` = `rights_confirmed_at`, `channel` = `form`, ohne IP und UA. Test: Wortlaut byte-genau rekonstruierbar, auch nachdem v3 aktiv ist. | S | A.2 |
|
||||||
|
| A.4 | Enums: `ImageProvenanceStatus` (`absent`, `unsigned_metadata`, `validated_trusted`, `validated_untrusted`, `invalid`, `unsupported`, `error`), `AiDeclarationSource` (wie A.1), `CreditSource`. `AiImageDeclaration::rank()` (0, 1, 2) für die Portalregel „Kennzeichnung = max(geprüfter Befund, Kundenangabe)", mit Kommentar, dass das eine Portalregel ist (Feedback F11). `label()` von `Original` wird „Ohne generative KI erstellt oder bearbeitet"; `publicLabel()` unverändert. | S | keine |
|
||||||
|
| A.5 | `App\Services\Image\ImageProvenanceInspector`: Imagick (`getImageProfile('iptc')`, `getImageProfile('xmp')`) und `exif_read_data()`. Felder: IPTC 2:110 Credit, 2:80 By-line, 2:116 Copyright; XMP `photoshop:Credit`, `dc:creator`, `dc:rights`, `Iptc4xmpExt:DigitalSourceType`; EXIF `Artist`, `Copyright`. IPTC-URIs normalisiert (Lang- und Kurzform). Zuordnung: `trainedAlgorithmicMedia` → Vorschlag `ai_generated`; `compositeWithTrainedAlgorithmicMedia` → `ai_edited`; `algorithmicallyEnhanced` → nur `enhancement_hint`, kein Vorschlag (E-B5); `digitalCapture`, `negativeFilm`, `positiveFilm`, `print` → Vorschlag `original`; sonst keiner. Nachweis-Vorschlag: Credit → dc:creator → By-line → EXIF Artist → CopyrightNotice, auf 255 gekürzt mit Vermerk. Status in Phase 1 nur `absent`, `unsigned_metadata` oder `error`. Dauer gemessen und im json abgelegt; Ausnahme oder über 10 s → `error` mit Fehlerklasse. Nicht ausgewertet: `CreatorTool`, Dateiname, Bildinhalt. Fixtures unter `tests/Fixtures/images/` (mit Imagick erzeugt): Kamera-JPEG mit IPTC, JPEG mit XMP `trainedAlgorithmicMedia`, JPEG mit `algorithmicallyEnhanced`, PNG ohne Metadaten, WebP mit XMP, beschädigte Datei. | M | A.4 |
|
||||||
|
| A.6 | `App\Services\PressRelease\PressReleaseImageUploader::store(UploadedFile, HasImages $subject, ImageSubmission, Actor)`: (1) Größe und Pixel prüfen (E-B6), Bytes einmal lesen, SHA-256; (2) Original auf Disk `local` nach `press-release-originals/{pm}/{uuid}.{ext}`, Schreibvorgang prüfen; (3) Prüfung auf dieselben Bytes; (4) Derivate über `ImageService` (A.7), bei Fehler alles zurückrollen; (5) `ai_declaration_user` = Angabe, `ai_declaration` = max(Vorschlag aus geprüftem Befund, Angabe) (Phase 1: Vorschlag aus `unsigned_metadata` fließt **nicht** ein, nur Vorbelegung; Abweichung nach unten wird im json als `deviated_from_prefill` vermerkt), `ai_declaration_source` = `customer`; (6) Transaktion mit `lockForUpdate()` auf der PM: ein-Titelbild-Regel, Bildzeile, Bestätigung (außer Kanal Admin, B.4), `rules_version`; (7) DB-Fehler → Dateien löschen. `ImageSubmission`: `copyright`, `licenseUrl`, `sourceUrl`, `title`, `description`, `aiDeclaration`, `rightsConfirmed`, `legalTextKey`, `focus`, `noCrop`. `Actor`: User, Token, Kanal. Test: DB-Fehler hinterlässt keine Datei; zwei gleichzeitige Uploads ergeben ein Titelbild; Hash von Original, Bestätigung und Prüfung identisch. | M | A.1 bis A.5, A.7 |
|
||||||
|
| A.7 | `ImageService::storePressReleaseImage()`: kein Rückfall auf die Quelldatei, `RuntimeException` stattdessen; neue Variante `og` (1200 × 630, H.1); `resizeCover` mit Fokuspunkt und `no_crop` (H.3). Quelldatei im Derivat-Ordner wird nach der Erzeugung gelöscht (Original liegt separat). Test: fehlende Cover-Variante landet nie öffentlich; Varianten ohne EXIF-GPS und Seriennummer. Erwartung „Originaldatei gelöscht" in `PressReleaseImageLicenseTest` umschreiben. | S | keine |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block B — Formular v2 und Admin-Upload
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| B.1 | `press-release-images-manager.blade.php` umbauen (Flag an): Block 1 Bild mit Vorschau, Prüfhinweis, Fokuspunkt-Wahl (H.3); Block 2 Bildnachweis (Pflicht, vorbelegt aus Metadaten, sonst Firmenname, sichtbar als „Vorschlag, bitte prüfen"), KI-Kennzeichnung (Pflicht, drei Optionen mit neuem Label, Erklärtext), aufklappbar „Optionale Angaben" (Titel, Bildunterschrift, Lizenz-URL, Quellen-URL getrennt); Block 3 Bestätigung als `flux:switch` mit Kurzform aus DU 3.3 (inkl. AGB-Datum), „Details" klappt den gerenderten Langtext aus A.3 auf, Kontaktweg für Widerspruch daneben. Vier Zustände: A `validated_trusted` (Phase 2), B `absent`, C `unsigned_metadata` („Vorbelegt aus den Metadaten der Datei, Sie können die Auswahl ändern"), D `error` („Herkunftsdaten konnten nicht geprüft werden", Speichern erlaubt, Vermerk). Entfallen: `author` als Pflicht, `license_type`, `license_detail`, Personen-, Marken-, Notizfelder, „Anbieter-Bedingungen geprüft". | M | A.6 |
|
||||||
|
| B.2 | Bindung an die Datei (Feedback F09): Prüfung nach `updatedNewImage` auf der Temp-Datei, Ergebnis und Hash in `#[Locked]`-Properties; beim Speichern rechnet der Uploader neu, Abweichung → Abbruch. Dateiwechsel setzt Vorbelegung und Schalter zurück. Manuell geänderter Nachweis wird nicht überschrieben. Effektive Werte serverseitig. | S | B.1 |
|
||||||
|
| B.3 | Tests: `PressReleaseImageRightsV2Test` ersetzt die Lizenz-Tests (alte bleiben für Flag aus, bis der Flag fällt); `PressReleaseAiDeclarationTest` um Snapshot-Erwartungen (Kundenangabe und Portalkennzeichnung getrennt). | M | B.1, B.2 |
|
||||||
|
| B.4 | Admin-Upload (DU E10, S7): Admin lädt hoch ohne Bestätigung; Bild bekommt `customer_confirmation_requested_at`, Status in der Karte „wartet auf Kundenbestätigung"; Mail „Bild bestätigen" mit Magic-Link an den Kontoinhaber (bestehende Magic-Link-Infrastruktur, Herkunft `image_confirmation`, erneut anforderbar); Landeseite zeigt Bild, Angaben und den Schalter mit Langtext; Bestätigung mit `channel = magic_link` in A.2. Veröffentlichungsgate (E.1) lässt ein Bild ohne Bestätigung nicht öffentlich werden; der Admin sieht das beim Freigeben. Admin-Bildtausch an einer veröffentlichten PM läuft genauso (neue Bestätigung zur neuen Datei). Test: ohne Klick keine Bestätigung, PM nicht veröffentlichbar mit Bild; nach Klick Protokoll mit Kontoinhaber. | M | A.2, A.6 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block C — API
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| C.1 | `StorePressReleaseImageRequest` (Flag an): `copyright` required, `ai_declaration` required, `rights_confirmed` accepted, `confirm_text_version` required und aktive Kennung (sonst 422 mit `current_version`), `license_url`, `source_url`, `focus_x`, `focus_y`, `no_crop` optional; Grenze 16 MiB (E-B6). Controller ruft den Uploader mit `channel = api` und Token-ID. `PressReleaseImageResource` kompatibel ergänzt: `ai_declaration` (Portal), `ai_declaration_user`, `ai_declaration_source`, `provenance_status`, `rights_confirmation {version, confirmed_at}`, `warnings[]` (`provenance_check_failed`, Phase 2 `ai_declaration_raised_by_metadata`). `urls.original` bleibt das Cover, dokumentiert. Tests in `PressReleaseImageApiTest`. | M | A.6 |
|
||||||
|
| C.2 | Legacy-Bildupload abschalten (E-B4): `POST /service/pressreleases/{id}/images` antwortet im Legacy-Fehlerformat mit 410 und Hinweis auf v1; `GET …/images` bleibt. Controller und Legacy-Tests entfernen bzw. auf 410 umstellen; `dev/betrieb/api-legacy-paritaet.md` ergänzen. `StorePressReleaseImageRequest` verliert die Legacy-Ausnahme. | S | keine |
|
||||||
|
| C.3 | API-Doku (`dev/api/`, öffentliche API-Seite): Pflichtfelder, 422, Bestätigungstext mit Kennung, AGB-Fassung über die API-Nutzungsbedingungen (DU 3.3), Abschaltung des Legacy-Uploads. | S | C.1, C.2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block D — Ausgabe und öffentliche Sprache
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| D.1 | `App\Support\ImageCredit::for(PressReleaseImage, ?Company)`: eine Auflösung für alle Ausgaben. Nachweiszeile: „Foto: [copyright]", sonst „Foto: [author]", sonst (Bestand) „Quelle: [Firma]" (DU 3.4). KI-Zusatz aus `publicLabel()`, Bestand-Fallback `is_ai_generated`. Verantwortlich-Zeile „Bild und Angaben: [Firma], eingereicht am [Datum]" **nur** mit Bestätigungsprotokoll. Lizenz- und Quellenlink nur `http`/`https`. Ersetzt die vier Einzelimplementierungen (Artikelseite, Kunden- und Adminansicht, `ProofPdfRenderer::imageCreditLines()`). | S | A.2 |
|
||||||
|
| D.2 | Overlay auf der Artikelseite (beide Portale, anonyme Vorschau): rundes Icon unten links, 28 px, eigenes „i" (E-B13). Alpine.js, `aria-haspopup`, `aria-expanded`, Fokus hinein und zurück, Escape, unter `sm` Bottom-Sheet. Inhalt: Nachweis, Lizenz/Quelle, KI-Hinweis mit **Quelle der Kennzeichnung** („Angabe des Einreichers" / „aus geprüften Herkunftsdaten" / „aus Metadaten vorbelegt", DU 3.4), Verantwortlich-Zeile (nur mit Protokoll), „Bild melden". Keine Content-Credentials-Zeile in Phase 1. Manuelle Abnahme mit Tastatur und Screenreader. | M | D.1 |
|
||||||
|
| D.3 | Nachweiszeile unter dem Bild bleibt ohne Klick sichtbar. Kunden- und Adminansicht zeigen zusätzlich Bestätigungskennung, AGB-Fassung, Zeitpunkt und Kanal; bei Admin-Uploads den Zustand „wartet auf Kundenbestätigung". | S | D.1 |
|
||||||
|
| D.4 | `ProofPdfRenderer`: Zeilen aus D.1 plus Bestätigungskennung und Zeitpunkt; alte Felder bleiben für Bestandsbelege lesbar. | S | D.1 |
|
||||||
|
| D.5 | Web-Cache: `WebCaches::forgetPublicPages()` (bzw. die gezielte Variante für die Detailseite) bei Bild-Upload, -Löschung, Kundenbestätigung und Korrektur. | S | A.6 |
|
||||||
|
| D.6 | Wortwahl (DU E11, S10): `richtlinien.blade.php:36` durch den Absatz aus DU 3.5 ersetzen und den Abschnitt „Bilder" aus DU 3.5 einfügen, Version `richtlinien` in `legal_documents` hochzählen; „qualitätsgeprüft" und „geprüfte" in `lang/de/web.php` (189, 231, 233, 249, 255), `static.php:533`, `presseecho.blade.php:43` und der Footer-Tagline auf „automatisierte Prüfung" bzw. neutrale Formulierung; Ablehnungs- und Feedbackmails auf „Unsere automatisierte Prüfung hat …" (Fundstellen in den Mail-Templates und `lang/de` beim Umbau suchen). Die beiden AGB-Stellen (139, 206) bleiben bis v0.7. Test: `LegalDocumentTest` für die neue Richtlinien-Version; Snapshot-Test der geänderten Lang-Keys. | S | keine |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block E — Lebenszyklus, Original, Korrektur, Bereinigung
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| E.1 | `published_ever_at` an PM und Bild (DU S8, E5): gesetzt in `publish()`, `changeStatusFromAdmin()` nach `Published`, `restoreFromGone()` und für Bilder beim Upload an eine bereits veröffentlichte PM. Backfill: PMs mit `published_at` oder Status-Log nach `published`; deren Bilder ebenso. Veröffentlichungsgate: `publish()` verlangt für jedes Bild mit `rules_version` eine Bestätigung zur aktuellen `original_sha256` (A.2); fehlt sie (Admin-Upload ohne Kundenbestätigung), Abbruch mit klarer Meldung. Test je Pfad. | S | A.1, A.2 |
|
||||||
|
| E.2 | Snapshot der veröffentlichten Fassung (DU E2, S1): beim ersten Veröffentlichen der PM wird je Bild die Cover-Variante plus ein JSON mit gerendertem Nachweis, Portalkennzeichnung und Quelle nach `press-release-published/{pm}/{image}-{sha}.{ext|json}` auf Disk `local` kopiert, `published_snapshot_*` gesetzt. Bei Bildtausch an veröffentlichter PM neuer Snapshot. Test: Snapshot entspricht der ausgelieferten Variante. | S | E.1 |
|
||||||
|
| E.3 | Sofortlöschung nie veröffentlichter Uploads (DU E4, S9, mit O1): `remove()`, v1 `destroy()`, Ersatz durch Upload, `PurgeExpiredPressReleaseDrafts` und Löschung der PM löschen bei `published_ever_at IS NULL` Original, Derivate, Snapshot **und** Bestätigung (harte Löschung der Bestätigungszeile ist hier gewollt; der Observer aus A.2 lässt genau diesen Fall zu). Bei gesetztem `published_ever_at`: Übergabe an das Beweisarchiv (F.4), danach Derivate löschen, Soft Delete. Test je Fall. | M | E.1, F.4 |
|
||||||
|
| E.4 | Zugriff auf Original und Snapshot nur über `GET /admin/press-releases/{pm}/images/{image}/original` und `…/published` mit Admin-Recht, jeder Abruf in `archive_access_log` (F.1) mit Pflichtangabe „Zweck". Medienroute, Vorschau-Signaturen und API-Resource kennen die Pfade nicht. Architekturtest: `original_path` und `published_snapshot_path` nur in Uploader, Snapshot-Service, Admin-Route, Archiv. | S | A.1, F.1 |
|
||||||
|
| E.5 | Legacy-Bereinigung (E-B11): `press-release-images:purge-inactive-legacy` (Standard Dry-Run, `--execute`): löscht Bildzeilen und Dateien für Legacy-Bilder (`legacy_id` gesetzt, `rules_version NULL`) an PMs mit Status `rejected`, `archived` oder `draft`; auf der Dev-Kopie 10.768 Bilder. Bericht mit Zahl je Status und Speicherplatz. Live-Schritt nach Deploy 1. Test mit Fixture-Zeilen. | S | keine |
|
||||||
|
| E.6 | Korrekturweg (E-B7, DU S17): Admin ändert `copyright` und `ai_declaration` (Portal) mit Pflichtgrund; `ai_declaration_source = admin_correction`; Zwischenzustand „Bild vorläufig zurückgehalten" (Bild aus der öffentlichen Seite, Platzhalter aktiv) bis zur Entscheidung; Archiveintrag `label_corrected` mit alt/neu/Quelle/Grund/Bearbeiter (F.4). Kundenangabe und ursprüngliche Bestätigung bleiben unverändert. Kontaktweg im Formular und im Overlay. | S | A.1, F.4 |
|
||||||
|
| E.7 | Betrieb: `config/images.php` (Größen, Pixel, Timeout, Vertrauensliste für Phase 2); Zähler `provenance_check_failed` im Admin-Systemstatus; Laufzeit der Prüfung auf Live mit 16-MiB-Bild messen und im Protokoll notieren. | S | A.5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block F — Beweisarchiv (DU Abschnitt 4)
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| F.1 | Tabellen `archive_entries` (`subject_type`, `subject_id`, `event` Enum `image_removed`, `image_replaced`, `pm_depublished`, `gdpr_change`, `label_corrected`; `actor_type`, `actor_id`, `reason_category` Enum, `payload_path`, `payload_hash`, `retain_until`, `legal_hold`, `legal_hold_note`, `legal_hold_set_by`, `legal_hold_set_at`, `deleted_at` als Tombstone, `created_at`) und `archive_access_log` (`entry_id`, `admin_id`, `accessed_at`, `purpose` Pflicht). Append-only per Observer. Disk `archive` (`storage/app/archive`, außerhalb des Web-Roots), Payload als ZIP, verschlüsselt mit eigenem Schlüssel `ARCHIVE_KEY` aus der Umgebung über `Illuminate\Encryption\Encrypter` (nicht `APP_KEY`, damit ein Schlüsselwechsel der App das Archiv nicht berührt). `retain_until` = 31.12. des Ereignisjahres + 3 Jahre. | M | keine |
|
||||||
|
| F.2 | `App\Services\Archive\EvidenceArchive`: `record(event, subject, payload[], actor, reason)` (packt, verschlüsselt, hasht, schreibt Eintrag), `open(entry, admin, purpose)` (entschlüsselt, protokolliert), `export(entry)` (signiertes ZIP mit Metadaten und Hash für den Anwalt), `setLegalHold()`/`releaseLegalHold()` (nur Admin, Pflichtvermerk; nach Aufhebung `retain_until` neu ab Aufhebungsdatum). Admin-Seite: Liste, Detail, Zugriff mit Zweck, `legal_hold`-Schalter, Export. Das Audit-Log der Anwendung referenziert nur `archive_entries.id`, nie Altwerte. | M | F.1 |
|
||||||
|
| F.3 | Jobs: `archive:purge` täglich (Einträge mit `retain_until < heute` und ohne `legal_hold`: Payload löschen, Tombstone setzen, Log ohne Inhalt); `archive:export-tombstones` täglich (JSON der Tombstone-IDs auf die Archiv-Disk, Teil der Sicherung); `archive:reconcile-after-restore` (liest den Export, entfernt Payloads zu Tombstones erneut; Bestandteil des Restore-Runbooks in `dev/betrieb/deploy-runbook.md`). Test: Purge im Dry-Run ändert nichts; `legal_hold` blockt; Reconcile löscht wiederhergestellte Payloads. | S | F.2 |
|
||||||
|
| F.4 | Anbindung (DU 4.1, S14): `image_removed`/`image_replaced` aus E.3 (Original, Snapshot, Bestätigung); `pm_depublished` in `depublish()`, `archive()`, `changeStatusFromAdmin()` weg von `Published`, `deleteFromAdmin()` einer veröffentlichten PM und Ende der Laufzeit (letzter veröffentlichter Stand: Text, Bilder-Snapshots, Kontaktfelder, Einreicher, Zeitpunkte, Grund als Kategorie); `gdpr_change` in `AnonymizationService` und `CorrectionService` (Diff alt/neu, Referenz auf Antrag); `label_corrected` aus E.6. Ablehnung im Freigabelauf: kein Eintrag (nur Vertragsdaten in der normalen DB). Test je Auslöser: Eintrag vorhanden, Payload entschlüsselbar, Hash stimmt. | M | F.2, E.2, E.3, E.6 |
|
||||||
|
| F.5 | DSE-Zusagen prüfen: Zugriffsprotokoll (F.1), Tombstone nach Restore (F.3) und die dokumentierte Interessenabwägung (DU 4.4 als Datei im Rechtsordner, Betreiber) müssen stehen, bevor DSE 9b in Deploy 3 veröffentlicht wird. Checkliste im Protokoll. | S | F.1 bis F.4 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block G — Zustimmungsgate (DU E12, S12)
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G.1 | Tabelle `legal_document_acceptances` (`user_id`, `legal_document_id`, `type`, `version`, `accepted_at`, `context` Enum `submit`, `booking`, `api`, `gate`, `ip_hmac`, `user_agent`). Insert-only. Bestehende `booking_confirmations` bleiben, werden nicht migriert. | S | keine |
|
||||||
|
| G.2 | Gate vor jeder Einreichung: die vier Kunden-Submit-Pfade (`create`, `edit`, `show`, `index`) prüfen über einen gemeinsamen Trait, ob für AGB und Richtlinien in der aktuellen Version eine Annahme vorliegt; sonst Modal mit aktivem Klick je Dokument, Link auf den Text, kein „Weiternutzung gilt als Zustimmung". Bestandskunden werden beim nächsten Einreichen einmalig durchgeschleust. API (O2): `POST /api/v1/legal/acceptances`, `submit` antwortet ohne gültige Annahme mit 428 und `required_versions`. Test: Submit ohne Annahme blockiert, mit Annahme durch; neue Version fordert erneut. | M | G.1 |
|
||||||
|
| G.3 | Aktivierung erst in Deploy 3 mit AGB v0.7 (bis dahin hinter `LEGAL_ACCEPTANCE_GATE=false`), damit Bestandskunden gleich die neue Fassung annehmen und nicht zweimal klicken. | S | G.2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block H — OpenGraph, Teaser, Beschnitt (DU E7, S15, S16)
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| H.1 | Variante `og` (1200 × 630) mit eingebranntem Nachweisbalken unten: halbtransparenter Balken, Text „Foto: [Nachweis]" plus KI-Zusatz, per GD `imagettftext` mit `resources/fonts/Inter_18pt-SemiBold.ttf` (vorhanden, OFL 1.1, O4). Wird beim Upload und beim Korrekturweg neu erzeugt (Nachweis kann sich ändern). `release-detail.blade.php:168` liefert `og` statt `detail`. Bestand ohne `og`-Variante: `press-release-images:regenerate-variants --only=og` für die 3.786 veröffentlichten Legacy-Bilder (Nachweis oder „Quelle: [Firma]"). Test: Variante existiert, Balken vorhanden (Farbprüfung der unteren Zeilen). | M | D.1, A.7 |
|
||||||
|
| H.2 | Teaser und Listen: `alt` und `title` der Vorschaubilder tragen den Nachweis („[Titel] · Foto: [Nachweis]"); Listen-Karten bekommen eine Bildunterschrift-Zeile mit dem Nachweis, wo Platz ist (Startseite, Kategorie, Newsroom, Suche, Archiv). Beide Portale. | S | D.1 |
|
||||||
|
| H.3 | Beschnitt-Steuerung (§ 9 Abs. 1 letzter Satz): Fokuspunkt je Bild (`focus_x`/`focus_y` in Prozent, Neun-Punkte-Wahl im Formular) oder `no_crop` (Letterbox mit neutralem Rand). `resizeCover` berücksichtigt beides für alle Varianten. Test: Fokus rechts oben schneidet links unten ab; `no_crop` zeigt das volle Bild. | M | A.7, B.1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block I — Phase 2 und 3: C2PA, Metadaten, Partner
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| I.1 | `c2patool` (Release-Binary, Version und Prüfsumme im Repo) in Sail-Image und per Ploi-Skript auf den Server; Aufruf über `Symfony\Process` mit Argumentliste, `--detailed`, 10 s, 1 MiB Ausgabe, keine externen Manifestverweise. Vertrauensliste `config/c2pa.php` (Aussteller; Geschäftsentscheidung). | M | E.7 |
|
||||||
|
| I.2 | Inspector-Erweiterung: `validated_trusted`, `validated_untrusted`, `invalid`, `unsupported`; nur das aktive Manifest zählt, Bestandteile werden nicht übernommen; Konflikt C2PA gegen XMP → C2PA gewinnt mit Vermerk; Aussteller nur aus dem Zertifikat. Testkorpus: OpenAI, Adobe Firefly, Google, Kamera mit Content Credentials, gebrochene Bindung, zwei Manifeste, Fotomontage mit KI-Bestandteil. | M | I.1 |
|
||||||
|
| I.3 | Sperre (DU E9): `ai_declaration_locked` nur bei `validated_trusted` mit `trainedAlgorithmicMedia` oder `compositeWithTrainedAlgorithmicMedia`; `ai_declaration` = max(Befund, Kundenangabe), `ai_declaration_source = c2pa_verified`; Höherstufung möglich; Unterschreitung verworfen und als `lock_bypass_attempt` geloggt (12 Monate, ohne Nutzdaten); API-Warnung `ai_declaration_raised_by_metadata`. Zustand A im Formular und Content-Credentials-Zeile im Overlay nur bei `validated_trusted`. | S | I.2 |
|
||||||
|
| I.4 | Messung auf Live (Laufzeit je Dateigröße, Fehlerquote nach vier Wochen). Revisit: mehr als eine Handvoll Widersprüche pro Monat → Korrekturweg neu bewerten. | S | I.3 |
|
||||||
|
| I.5 | Metadaten in die Varianten (DU S21): Imagick `setImageProfile('xmp', …)` mit `dc:rights`, `photoshop:Credit`, `Iptc4xmpExt:DigitalSourceType` für JPEG, PNG, WebP; nach dem Schreiben wieder auslesen; Variantenname mit Versionsanteil wegen `max-age=86400`. | M | D.1 |
|
||||||
|
| I.6 | Re-Sync-Sperre für Bilder (DU S22): sobald ein API-Partner Bilder liefert, dürfen Importer-Läufe entfernte oder korrigierte Bilder nicht wiederherstellen (analog `lockSync()` der PM). Erst bauen, wenn der Fall eintritt. | S | Partner |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block J — Ausblick: Bilder im Newsroom (Roadmap 1b.3)
|
||||||
|
|
||||||
|
Unverändert aus Fassung 1: Tabelle `company_images` mit denselben Spalten (gemeinsames Migrations-Makro), Logos ohne Kennzeichnung; Uploader und Bildmanager über `HasImages` verallgemeinert, Bestätigungen mit `subject_type = Company`; Newsroom-Bilder sind sofort öffentlich, deshalb Bestätigung vor dem Speichern; öffentliche Galerie mit Nachweiszeile und Overlay; Kontingent je Newsroom (Geschäftsentscheidung). Aufwand M. Voraussetzung: Deploy 1 und 2 live.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block K — KI-Vorab-Check für Bilder (DU Abschnitt 12, E15, S24)
|
||||||
|
|
||||||
|
Grundlage ist die Text-Prüfstrecke: OpenAI über `Http` (Konfiguration `services.openai`), Manager/Treiber-Muster (`ClassificationManager`), deterministischer Fallback, unveränderliches `ki_audits`-Protokoll. Der Bild-Check bekommt denselben Aufbau mit eigenem Manager, eigenem Config-Block `images.precheck` und Bild-Eingabe (`image_url` mit Data-URI). Drei Regeln aus DU 12.2 gelten wörtlich: jeder Befund hat einen definierten Ausgang; öffentlich bleibt es „automatisierte Prüfung"; die KI setzt nie die Kennzeichnung.
|
||||||
|
|
||||||
|
| Nr. | Arbeitspaket | Aufwand | Abhängigkeit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| K.1 | `ImagePrecheckManager` mit Treibern `openai` und `deterministic` (liefert nie einen Befund); Vertrag `check(string $absolutePath): ImagePrecheckResult`. Vorbereitung als Datenminimierung: Imagick, 1024 px lange Kante, `stripImage()`, JPEG 82, Base64. Antwortschema DU 12.4 über `response_format json_object`; der Prompt fragt nur „Person(en) erkennbar", nie eine Identifizierung. Fehler, Timeout, unbrauchbare Antwort → Ergebnis `error`: durchlassen mit Vermerk, nie blockieren. Config: `enabled` (`IMAGE_PRECHECK`, Standard aus), `stage` (`IMAGE_PRECHECK_STAGE`, 1 = nur `stock_watermark`, 2 = alle Kategorien), `provider`, `model` (leer = `services.openai.model`), `timeout` 20 s, `reject_threshold` 0,85, `queue_threshold` 0,6. | S | A |
|
||||||
|
| K.2 | Auswertung nach DU 12.3 in `ImagePrecheckOutcome` (`reject`, `queue`, `hint`, `pass`, `error`): `reject` bei `stock_watermark`, `nudity_or_violence`, `document_or_id` ab 0,85; `queue` bei `screenshot_or_collage`, `third_party_logo_prominent`, `license_plate_readable` ab 0,6 und bei den drei Ablehnungskategorien zwischen 0,6 und 0,85; `hint` bei `persons_visible`, bei `looks_ai_generated` nur wenn die Kundenangabe „ohne KI" lautet, und bei `technical_defect`; sonst `pass`. Stufe 1 wertet ausschließlich `stock_watermark`. `ai_declaration` bleibt unberührt (Regel 3). | S | K.1 |
|
||||||
|
| K.3 | Einbau in den Uploader: Check auf den Originalbytes **vor** dem Schreiben von Original und Derivaten. Formular: bereits in `preflight()` nach der Dateiauswahl, Ergebnis mit Hash-Bindung im Komponentenzustand; `store()` rechnet nur neu, wenn kein Ergebnis oder der Hash nicht passt. API: in `store()`. `reject` → `ImageUploadException` mit dem Grund in einem Satz (Formular unter dem Dateifeld, API 422 Feld `image`), nichts gespeichert, `ki_audits`-Eintrag `image_precheck` an der PM (Kategorie, Konfidenz, Modell, ohne Datei). Gespeicherte Bilder: Spalten `precheck` (JSON: Kategorien mit Konfidenz, Modell, Version, Zeitpunkt, Ausgang, Stufe), `precheck_outcome`, `precheck_review_required_at`, `precheck_reviewed_at`, `precheck_reviewed_by`, `precheck_review_note`; `ki_audits`-Eintrag je Check; `precheck_snapshot` als eigenes Feld im Bestätigungsprotokoll (DU 12.5); Archiv-Payload `imageRemoved` um die Spalten erweitert. | M | K.2 |
|
||||||
|
| K.4 | Admin-Queue: `queue` setzt `precheck_review_required_at` und `withheld_at`. Die Meldung bleibt im Freigabelauf (DU 12.3), nur das Bild ist bis zur Sichtung zurückgehalten (bestehender Zustand E.6, Platzhalter öffentlich). Kunde sieht „Wird kurz gesichtet" an der Titelbild-Karte. Admin-Entscheidung mit Vermerk: Freigabe (Bild wird sichtbar, OG-Variante bleibt) oder Ablehnung (Entfernen über `PressReleaseImageRemover`, Grund `own_decision`); beide als `ki_audits`-Eintrag `image_precheck_review` und in den `precheck_reviewed_*`-Spalten. | M | K.3 |
|
||||||
|
| K.5 | Oberfläche: Formular v2 zeigt Hinweise aus `hint` (Personen: Einverständnis-Satz aus DU 12.3; „wirkt KI-generiert" nur, wenn „ohne KI" gewählt ist; technischer Mangel als Notiz); Admin-PM-Seite bekommt den Sichtungsblock mit Befund, Freigabe/Ablehnung und Vermerk; Admin-PM-Liste Ansicht „Bild-Sichtung" mit Zähler; Systemstatus-Zähler offener Sichtungen. | S | K.4 |
|
||||||
|
| K.6 | Datenschutz und Betrieb (DU 12.6): AVV mit dem Anbieter und DSE-Absatz zur Bildsichtung gehören zu Deploy 3 (Betreiber, Anwalt); bis dahin bleibt der Check aus. Einschalten: `IMAGE_PRECHECK=true`, `IMAGE_PRECHECK_STAGE=1` direkt nach Deploy 1b, Stufe 2 nach einer Woche ohne Fehlurteile. Wiedervorlage DU 12.8: Kategorie mit gehäuften Fehlurteilen per Config von `reject` auf `queue` herabstufen (`images.precheck.downgrade_to_queue`), nicht abschalten. | S | Betreiber |
|
||||||
|
|
||||||
|
Nicht aufgenommen: Nachsichtung des Bestands; Bewerbung des Checks nach außen (würde den Hoster-Status berühren, DU 12.8); Sperre der Kennzeichnung aus dem Modellurteil (E9).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reihenfolge und Deploys
|
||||||
|
|
||||||
|
| Deploy | Inhalt | Live-Schritte | Bedingung |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **1** | Blöcke A bis E, D.6; Flag `IMAGE_RIGHTS_V2=false` | `migrate` (Sekunden), Seeder `LegalTextSeeder` (v1, v2, v3 ohne Datum) und Richtlinien-Version, Backfill A.3 und E.1, `press-release-images:purge-inactive-legacy` erst Dry-Run dann `--execute`, `.env`: `CONFIRMATION_IP_KEY`, `IMAGE_RIGHTS_V2=false` | keine |
|
||||||
|
| **1b** | Flag einschalten | `IMAGE_RIGHTS_V2=true`, `config:cache`; Legacy-Upload antwortet ab hier 410 | Prüfung auf Live mit Testkonto. **Wartet nicht auf den Anwalt** (DU E14). Bestätigungstext `img-confirm-v2` mit Verweis auf AGB 0.6. |
|
||||||
|
| **2** | Blöcke F, G (Gate aus), H | `migrate`, `.env`: `ARCHIVE_KEY`, Disk `archive` anlegen, Scheduler (`archive:purge`, `archive:export-tombstones`), Restore-Runbook ergänzen, `press-release-images:regenerate-variants --only=og` | parallel zur Anwaltsfrist |
|
||||||
|
| **2b** | Block K (Check aus) | `migrate`, `.env`: `IMAGE_PRECHECK=false`; nach AVV und DSE-Absatz: `IMAGE_PRECHECK=true`, `IMAGE_PRECHECK_STAGE=1`, später `2` | Deploy 1b live; AVV mit dem Anbieter; DSE-Absatz DU 12.6 (Deploy 3) |
|
||||||
|
| **3** | AGB v0.7 (§ 8a, § 9), DSE v0.4 (9a, 9b, 13), `img-confirm-v3` aktiv, Gate an | Seeder `legal_documents` (AGB 0.7, DSE 0.4), `legal_texts` v3 `effective_from`, `LEGAL_ACCEPTANCE_GATE=true`, Interessenabwägung als Dokument abgelegt | Anwaltsantwort, F.5 |
|
||||||
|
| **4** | Block I | `c2patool` auf dem Server, `config/c2pa.php` | vier Wochen Deploy 1b ohne Auffälligkeiten, Testkorpus |
|
||||||
|
| **5** | Block J | `migrate` | Kontingententscheidung |
|
||||||
|
|
||||||
|
Aufwand: Deploy 1 etwa zehn bis vierzehn Arbeitstage, Deploy 2 etwa acht bis zehn, Deploy 3 zwei, Deploy 4 fünf bis sieben.
|
||||||
|
|
||||||
|
**Live-Schritte für Deploy 1 und 2 zusammen (Stand 22.09.2026, beide Blöcke sind gebaut):**
|
||||||
|
|
||||||
|
1. Dateien hochladen, `php artisan view:clear && php artisan view:cache`, `php artisan config:cache`.
|
||||||
|
2. `.env`: `IMAGE_RIGHTS_V2=false`, `CONFIRMATION_IP_KEY=<eigener Wert>`, `ARCHIVE_KEY=<php artisan archive:key>`, `LEGAL_ACCEPTANCE_GATE=false`, `IMAGE_PRECHECK=false`; `ARCHIVE_KEY` getrennt sichern.
|
||||||
|
3. `php artisan migrate --force` (neun Migrationen, alle additiv; press_release_images und press_releases ohne FULLTEXT, Sekunden).
|
||||||
|
4. `php artisan db:seed --class=LegalTextSeeder --force` und `php artisan db:seed --class=LegalDocumentSeeder --force` (Richtlinien 0.2).
|
||||||
|
5. `php artisan image-rights:backfill-confirmations` und `php artisan press-releases:backfill-published-ever` (je erst `--dry-run`).
|
||||||
|
6. `php artisan press-release-images:purge-inactive-legacy` (Trockenlauf), dann mit `--execute`.
|
||||||
|
7. `php artisan queue:restart`; Scheduler läuft `archive:purge` und `archive:export-tombstones` automatisch.
|
||||||
|
8. Prüfung auf Live mit Testkonto (Flag noch aus: altes Formular sichtbar), dann `IMAGE_RIGHTS_V2=true` + `config:cache` (Deploy 1b). Ab dann antwortet der Legacy-Bildupload 410.
|
||||||
|
9. Bestand: `php artisan press-release-images:regenerate-variants --only=og --published` (Trockenlauf), dann mit `--execute` — erzeugt die OG-Variante mit eingebranntem Nachweis für die 3.786 veröffentlichten Bestandsbilder; bis dahin fällt og:image auf die Detail-Variante zurück.
|
||||||
|
10. Vorab-Check (Block K, Deploy 2b): erst nach AVV mit OpenAI und DSE-Absatz (DU 12.6) `IMAGE_PRECHECK=true`, `IMAGE_PRECHECK_STAGE=1` + `config:cache`; nach einer Woche ohne Fehlurteile `IMAGE_PRECHECK_STAGE=2`. Nutzt `OPENAI_API_KEY` der Text-Prüfstrecke; das Modell muss Bilder verarbeiten können (`IMAGE_PRECHECK_MODEL` bei Bedarf). Offene Sichtungen: Pressemitteilungen ▸ Bild-Sichtung.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Abnahmetests
|
||||||
|
|
||||||
|
| Bereich | Fälle | Block |
|
||||||
|
|---|---|---|
|
||||||
|
| Migration | vorhandene Werte unverändert; 45 Bestätigungen mit v1-Verweis; `published_ever_at`-Backfill stimmt mit Status-Log überein | A.1, A.3, E.1 |
|
||||||
|
| Bereinigung | Dry-Run ändert nichts; `--execute` entfernt nur Legacy-Bilder an `rejected`/`archived`/`draft`; veröffentlichte Legacy-Bilder bleiben | E.5 |
|
||||||
|
| Metadaten | IPTC Credit, XMP `trainedAlgorithmicMedia`, `algorithmicallyEnhanced` ohne Vorschlag, PNG ohne Metadaten, WebP mit XMP, beschädigt → `error` | A.5 |
|
||||||
|
| Formular | Pflichtfelder; Vorbelegung überschreibt keine Eingabe; Dateiwechsel setzt Schalter zurück; Zustand D speichert mit Vermerk; Kundenangabe und Portalkennzeichnung getrennt gespeichert | B |
|
||||||
|
| Admin-Upload | ohne Kundenklick keine Bestätigung und keine Veröffentlichung mit Bild; Magic-Link bestätigt mit Kontoinhaber; Bildtausch an veröffentlichter PM verlangt neue Bestätigung | B.4, E.1 |
|
||||||
|
| Atomarität | DB-Fehler nach Dateispeicherung; parallele Titelbild-Uploads; Hash identisch in Original, Bestätigung, Prüfung | A.6 |
|
||||||
|
| Zugriff | Original und Snapshot weder über Medienroute noch Vorschau noch API; Admin-Route mit Zweck im Zugriffslog | E.4 |
|
||||||
|
| API | v1 Pflichtfelder 422, falsche Kennung 422 mit Hinweis, Statusgrenze, kompatible Response; Legacy 410 | C |
|
||||||
|
| Bestätigung | Wortlaut mit AGB-Fassung byte-genau rekonstruierbar; User, Token, Kanal korrekt; Firmenwechsel ändert alte Bestätigung nicht | A.2, A.3 |
|
||||||
|
| Lebenszyklus | nie veröffentlicht → sofort alles weg; veröffentlicht → Archiveintrag, Derivate weg; Depublikation, Statuswechsel, Löschung, DSGVO-Änderung, Korrektur erzeugen je einen Eintrag; Purge, `legal_hold`, Reconcile | E.3, F |
|
||||||
|
| Gate | Submit ohne Annahme blockiert; mit Annahme durch; neue Version fordert erneut; API 428 | G |
|
||||||
|
| Ausgabe | beide Portale, Kunden-/Adminansicht, Vorschau, PDF; Nachweiszeile ohne Klick; Verantwortlich-Zeile nur mit Protokoll; Bestand „Quelle: [Firma]"; Overlay per Tastatur; OG-Variante mit Balken; Teaser-`alt` mit Nachweis; Fokus und `no_crop` | D, H |
|
||||||
|
| Sprache | keine „qualitätsgeprüft"/„Geprüfte Mitteilungen" mehr außerhalb der AGB; Richtlinien-Version hochgezählt | D.6 |
|
||||||
|
| Vorab-Check | Stock-Wasserzeichen ab 0,85 lehnt ab, nichts gespeichert, Audit ohne Datei; 0,7 geht in die Queue mit zurückgehaltenem Bild; Personen → Hinweis, kein Eingriff; „wirkt KI" ändert die Kennzeichnung nie; Modellfehler lässt durch mit Vermerk; Stufe 1 ignoriert alle anderen Kategorien; Hash-Bindung zwischen Vorprüfung und Speichern; Freigabe macht sichtbar, Ablehnung entfernt; Snapshot im Protokoll und im Archiv-Payload; Check aus = keine Anfrage | K |
|
||||||
|
| Phase 2 | vertrauenswürdig signiert sperrt; untrusted nicht; gebrochene Bindung `invalid`; Bestandteil-Typ nicht übernommen; Höherstufung möglich, Unterschreitung geloggt | I |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anwalt
|
||||||
|
|
||||||
|
Agenda, Prüfannahmen, finale Texte und Risiken stehen im Decision-Update (Abschnitte 3, 7, 8). Dieser Plan liefert dazu den Screenshot von Formular und Nachweiszeile nach Deploy 1b. Was der Anwalt kippen könnte, ist dort mit Reaktionen ohne Umbau belegt; der Plan hat dafür keine gesonderten Pakete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Umsetzungsprotokoll
|
||||||
|
|
||||||
|
| Datum | Punkte | Commit | Bemerkung |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 22.09.2026 | O4 / Schriften | `b624f80` | resources/fonts eingecheckt (20 Dateien, OFL 1.1) |
|
||||||
|
| 22.09.2026 | A.1–A.8 | `e9bdff3` | Migrationen, legal_texts (v1/v2/v3), image_rights_confirmations, ImageProvenanceInspector mit echten Fixtures, PressReleaseImageUploader, ImageService ohne Rückfall; 30 Tests |
|
||||||
|
| 22.09.2026 | B.1–B.5 | `40953d6` | press-release-images-manager-v2 hinter IMAGE_RIGHTS_V2, Admin-Upload mit Magic-Link-Bestätigung, Remover; 11 Tests |
|
||||||
|
| 22.09.2026 | C.1–C.3 | `65ac72c` | v1-Pflichtfelder, Resource, Legacy 410, API-Doku de/en, Paritätsdokument; 7 Tests |
|
||||||
|
| 22.09.2026 | D.1–D.6 | `2124ffd` | ImageCredit, Overlay, Kunden-/Adminansicht, PDF, Richtlinien 0.2, Wortwahl; 5 Tests; Baseline bereinigt |
|
||||||
|
| 22.09.2026 | E.1–E.7 | `46b89e1` | Gate, published_ever_at, Snapshot, Sofortlöschung (O1), Backfill, Legacy-Bereinigung (E-B11, 10.768 auf Dev), Beweiszugriff, Korrekturweg, Systemstatus; 14 Tests |
|
||||||
|
| 22.09.2026 | F.1–F.5 | `0240c14` | archive_entries/archive_access_log, EvidenceArchive, PressReleaseArchiver, Hooks, Scheduler, Admin-Seite, Runbook; 9 Tests. F.5-Checkliste: Zugriffsprotokoll ✓, Restore-Abgleich ✓, Interessenabwägung als Dokument (Betreiber) offen |
|
||||||
|
| 22.09.2026 | G.1–G.3, H.1–H.3 | `62723b5` | Zustimmungsgate (aus bis AGB 0.7, API 428/POST acceptances), OG-Balken, OG-Neuaufbau, Teaser-Nachweis; 8 Tests. Gesamtsuite nach Block E: 2.037 grün |
|
||||||
|
| 22.09.2026 | H.1 Bestand | `3b613fa` | `press-release-images:regenerate-variants --only=og`, Trockenlauf Standard; Testhelfer umbenannt (Kollision mit ManualReviewGateTest) |
|
||||||
|
| 22.09.2026 | K.1–K.6 | `0e5cd20` | ImagePrecheckManager (openai/deterministic), Auswertung 12.3, Uploader-Einbau mit Hash-Bindung, ki_audits `image_precheck`/`image_precheck_review`, Queue mit `withheld_at`, Admin-Sichtung (Liste, PM-Seite, Systemstatus), Formular-Hinweise, API 422/Warnung; 17 Tests. K.6 (AVV, DSE-Absatz, Einschalten) Betreiber |
|
||||||
|
| 25.09.2026 | Test auf Dev, Nacharbeiten | `6d558cd`, `d99f55e` | KI-Badge am Artikelbild rechts unten (Überlappung mit Nachweis-Zeichen); Admin ▸ Management ▸ Bilder: Liste aller Titelbilder mit PM-Link, Nachweis, Bestätigung, KI-Check, Filter; 2 Tests |
|
||||||
|
|
@ -38,5 +38,10 @@
|
||||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||||
<env name="BASIC_AUTH_ENABLED" value="false"/>
|
<env name="BASIC_AUTH_ENABLED" value="false"/>
|
||||||
<env name="BILLING_ENFORCE_BOOKING" value="false" force="true"/>
|
<env name="BILLING_ENFORCE_BOOKING" value="false" force="true"/>
|
||||||
|
<!-- Bildkennzeichnung/Rechtstexte: die Suite läuft mit den Vorgaben aus .env.example; Tests, die ein Flag brauchen, setzen es per config()->set (Umsetzungsaudit 25.09.2026, Abschnitt 6). -->
|
||||||
|
<env name="IMAGE_RIGHTS_V2" value="false" force="true"/>
|
||||||
|
<env name="IMAGE_PRECHECK" value="false" force="true"/>
|
||||||
|
<env name="IMAGE_PRECHECK_STAGE" value="1" force="true"/>
|
||||||
|
<env name="LEGAL_ACCEPTANCE_GATE" value="false" force="true"/>
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,51 @@ test('with the gate on the submit requires an active acceptance of agb and guide
|
||||||
expect(app(LegalAcceptanceGate::class)->requiredFor($owner)->pluck('version')->all())->toBe(['0.7']);
|
expect(app(LegalAcceptanceGate::class)->requiredFor($owner)->pluck('version')->all())->toBe(['0.7']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a confirmation is bound to the versions that were shown, a newer version must be read and accepted again', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
config()->set('legal.acceptance_gate', true);
|
||||||
|
['owner' => $owner, 'pr' => $pr] = legalGateDraft();
|
||||||
|
$this->actingAs($owner);
|
||||||
|
|
||||||
|
$component = LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||||
|
->assertSee('Version 0.6')
|
||||||
|
->set('aiTextDeclaration', 'none')
|
||||||
|
->set('aiDeclarationConfirmed', true)
|
||||||
|
->set('legalAcceptanceConfirmed', true);
|
||||||
|
|
||||||
|
expect($component->get('legalAcceptanceVersions')['agb'])->toBe('0.6');
|
||||||
|
|
||||||
|
// Zwischen Anzeige und Absenden wird eine neue AGB-Fassung wirksam.
|
||||||
|
LegalDocument::factory()->agb()->create(['version' => '0.7', 'effective_from' => now()->subDay()->toDateString()]);
|
||||||
|
|
||||||
|
$component->call('submitForReview')->assertHasErrors(['legalAcceptanceConfirmed']);
|
||||||
|
|
||||||
|
expect(LegalDocumentAcceptance::query()->count())->toBe(0)
|
||||||
|
->and($pr->fresh()->status)->toBe(PressReleaseStatus::Draft)
|
||||||
|
->and($component->get('legalAcceptanceConfirmed'))->toBeFalse()
|
||||||
|
->and($component->get('legalAcceptanceVersions')['agb'])->toBe('0.7');
|
||||||
|
|
||||||
|
// Neu gelesen und bestätigt: jetzt wird genau 0.7 protokolliert.
|
||||||
|
$component->assertSee('Version 0.7')
|
||||||
|
->set('aiDeclarationConfirmed', true)
|
||||||
|
->set('legalAcceptanceConfirmed', true)
|
||||||
|
->call('submitForReview')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
expect(LegalDocumentAcceptance::query()->where('type', 'agb')->pluck('version')->all())->toBe(['0.7'])
|
||||||
|
->and($pr->fresh()->status)->not->toBe(PressReleaseStatus::Draft);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a missing required document is a configuration error while the gate is on, not a smaller gate', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
config()->set('legal.acceptance_gate', true);
|
||||||
|
['owner' => $owner] = legalGateDraft();
|
||||||
|
|
||||||
|
LegalDocument::query()->where('type', 'richtlinien')->delete();
|
||||||
|
|
||||||
|
expect(fn () => app(LegalAcceptanceGate::class)->requiredFor($owner))->toThrow(RuntimeException::class, 'richtlinien');
|
||||||
|
});
|
||||||
|
|
||||||
test('acceptances are insert-only', function () {
|
test('acceptances are insert-only', function () {
|
||||||
config()->set('legal.acceptance_gate', true);
|
config()->set('legal.acceptance_gate', true);
|
||||||
['owner' => $owner] = legalGateDraft();
|
['owner' => $owner] = legalGateDraft();
|
||||||
|
|
|
||||||
35
tests/Feature/TestDatabaseGuardTest.php
Normal file
35
tests/Feature/TestDatabaseGuardTest.php
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Guard gegen eine falsch aufgelöste Testdatenbank (Config-Cache →
|
||||||
|
* Dev-DB) muss greifen, BEVOR RefreshDatabase etwas anfasst. Laravel ruft
|
||||||
|
* setUpTraits() vor allen Datenbank-Traits auf; dort hängt der Guard
|
||||||
|
* (Umsetzungsaudit 25.09.2026, N22).
|
||||||
|
*/
|
||||||
|
test('the database guard runs in setUpTraits, ahead of RefreshDatabase', function () {
|
||||||
|
$method = new ReflectionMethod(TestCase::class, 'setUpTraits');
|
||||||
|
$source = implode('', array_slice(file($method->getFileName()), $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1));
|
||||||
|
|
||||||
|
expect($method->getDeclaringClass()->getName())->toBe(TestCase::class)
|
||||||
|
->and(strpos($source, 'assertTestDatabaseIsInMemorySqlite'))->toBeLessThan(strpos($source, 'parent::setUpTraits'))
|
||||||
|
->and(in_array(RefreshDatabase::class, class_uses_recursive(static::class), true))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the database guard rejects anything but sqlite in memory', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
$guard = fn () => $this->assertTestDatabaseIsInMemorySqlite();
|
||||||
|
|
||||||
|
$guard();
|
||||||
|
|
||||||
|
try {
|
||||||
|
config()->set('database.default', 'mysql');
|
||||||
|
|
||||||
|
expect($guard)->toThrow(RuntimeException::class, 'statt SQLite :memory:');
|
||||||
|
} finally {
|
||||||
|
// Sonst räumt RefreshDatabase gegen die falsche Verbindung auf.
|
||||||
|
config()->set('database.default', 'sqlite');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
abstract class TestCase extends BaseTestCase
|
abstract class TestCase extends BaseTestCase
|
||||||
|
|
@ -17,14 +18,39 @@ abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
if (config('database.default') !== 'sqlite' || config('database.connections.sqlite.database') !== ':memory:') {
|
$this->routeTopicLogChannelsToTestingLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Laravel ruft setUpTraits() direkt nach dem Bootstrap und VOR jedem
|
||||||
|
* Datenbank-Trait auf (RefreshDatabase, DatabaseTruncation, …). Der
|
||||||
|
* Guard muss hier stehen — in setUp() nach parent::setUp() käme er erst
|
||||||
|
* an, wenn RefreshDatabase die falsche Datenbank schon geleert hat
|
||||||
|
* (Umsetzungsaudit 25.09.2026, N22).
|
||||||
|
*/
|
||||||
|
protected function setUpTraits(): array
|
||||||
|
{
|
||||||
|
$this->assertTestDatabaseIsInMemorySqlite();
|
||||||
|
|
||||||
|
return parent::setUpTraits();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft die Verbindung, die der DatabaseManager tatsächlich als Standard
|
||||||
|
* auflösen wird — ohne sie zu öffnen.
|
||||||
|
*/
|
||||||
|
protected function assertTestDatabaseIsInMemorySqlite(): void
|
||||||
|
{
|
||||||
|
$default = (string) DB::getDefaultConnection();
|
||||||
|
$connection = (array) config('database.connections.'.$default, []);
|
||||||
|
|
||||||
|
if ($default !== 'sqlite' || ($connection['driver'] ?? null) !== 'sqlite' || ($connection['database'] ?? null) !== ':memory:') {
|
||||||
throw new RuntimeException(sprintf(
|
throw new RuntimeException(sprintf(
|
||||||
'Tests laufen gegen „%s“ statt SQLite :memory: — vermutlich liegt ein Config-Cache. `php artisan config:clear` und erneut starten.',
|
'Tests laufen gegen „%s“ (%s) statt SQLite :memory: — vermutlich liegt ein Config-Cache. `php artisan config:clear` und erneut starten.',
|
||||||
config('database.default'),
|
$default,
|
||||||
|
(string) ($connection['database'] ?? '?'),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->routeTopicLogChannelsToTestingLog();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue