KI-Kennzeichnung (Art. 50 KI-VO): Pflicht-Deklaration für Text & Bilder
Selbstdeklaration des KI-Einsatzes je PM (Text) und je Bild inkl. Submit-Gate im PressReleaseService, versioniertem Audit-Trail (press_release_ai_declarations) und öffentlichem KI-Label in Einreicher-Stimme. Bestätigung im Submit-Modal ist Pflicht. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
74e82eb643
commit
807fbc1b80
16 changed files with 833 additions and 3 deletions
19
app/Services/PressRelease/AiDeclarationRequiredException.php
Normal file
19
app/Services/PressRelease/AiDeclarationRequiredException.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\PressRelease;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* KI-Kennzeichnung (Art. 50 KI-VO, Decision-Update 08.07.2026): Ohne
|
||||
* vollständige Selbst-Deklaration — KI-Grad des Textes, KI-Kennzeichnung
|
||||
* jedes Bildes und verbindliche Bestätigung — kein Submit. Speichern als
|
||||
* Entwurf bleibt immer frei.
|
||||
*/
|
||||
class AiDeclarationRequiredException extends RuntimeException
|
||||
{
|
||||
public function __construct(string $message = 'Für das Einreichen ist die vollständige KI-Kennzeichnung erforderlich (Angabe zum Text, zu jedem Bild sowie die Bestätigung).')
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ use App\Mail\PressReleasePublished;
|
|||
use App\Mail\PressReleaseRejected;
|
||||
use App\Models\AdminPreset;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\PressReleaseAiDeclaration;
|
||||
use App\Models\PressReleaseImage;
|
||||
use App\Models\PressReleaseStatusLog;
|
||||
use App\Services\Admin\AdminPerformanceCache;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
|
@ -52,6 +54,11 @@ class PressReleaseService
|
|||
throw new QuotaExceededException;
|
||||
}
|
||||
|
||||
// KI-Kennzeichnung (Art. 50 KI-VO): ohne vollständige Deklaration
|
||||
// kein Submit — die Editoren validieren das bereits im Modal, dieser
|
||||
// Guard sichert alle Aufrufpfade (API, ältere Entwürfe) ab.
|
||||
$this->assertAiDeclarationComplete($pressRelease);
|
||||
|
||||
$previous = $pressRelease->status;
|
||||
|
||||
if ($word = $this->blacklist->findInPressRelease($pressRelease)) {
|
||||
|
|
@ -75,6 +82,11 @@ class PressReleaseService
|
|||
]);
|
||||
$this->logStatusChange($pressRelease, $previous, PressReleaseStatus::Review, null, 'customer');
|
||||
|
||||
// Versionierter Audit-Trail der KI-Deklaration: jeder Submit erzeugt
|
||||
// einen neuen, unveränderlichen Eintrag (Abs.-50-Verteidigung —
|
||||
// "Kunde hat am Datum X Option Y deklariert und bestätigt").
|
||||
$this->logAiDeclaration($pressRelease);
|
||||
|
||||
// KI-Klassifikation asynchron anstoßen (Red Flag). Das Routing anhand
|
||||
// des Ergebnisses übernimmt der Job über routeByClassification().
|
||||
ClassifyPressRelease::dispatch($pressRelease->id)->onQueue('classification');
|
||||
|
|
@ -397,6 +409,52 @@ class PressReleaseService
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KI-Kennzeichnung vollständig? Pflicht sind der deklarierte KI-Grad des
|
||||
* Textes, die verbindliche Bestätigung sowie eine Kennzeichnung für
|
||||
* jedes Bild (Art. 50 KI-VO, Decision-Update 08.07.2026).
|
||||
*/
|
||||
private function assertAiDeclarationComplete(PressRelease $pressRelease): void
|
||||
{
|
||||
if ($pressRelease->ai_text_declaration === null || $pressRelease->ai_declaration_confirmed_at === null) {
|
||||
throw new AiDeclarationRequiredException;
|
||||
}
|
||||
|
||||
if ($pressRelease->images()->whereNull('ai_declaration')->exists()) {
|
||||
throw new AiDeclarationRequiredException(
|
||||
__('Bitte für jedes Bild die KI-Kennzeichnung angeben (Originalfoto, KI-generiert oder KI-bearbeitet) — im Bild-Manager der Bearbeiten-Seite.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt den unveränderlichen Audit-Eintrag der aktuellen Deklaration —
|
||||
* inkl. Bild-Snapshot zum Submit-Zeitpunkt.
|
||||
*/
|
||||
private function logAiDeclaration(PressRelease $pressRelease): void
|
||||
{
|
||||
$imageDeclarations = $pressRelease->images()
|
||||
->orderBy('sort_order')
|
||||
->get(['id', 'path', 'ai_declaration'])
|
||||
->map(fn (PressReleaseImage $image): array => [
|
||||
'image_id' => $image->id,
|
||||
'path' => $image->path,
|
||||
'declaration' => $image->ai_declaration?->value,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
PressReleaseAiDeclaration::query()->create([
|
||||
'press_release_id' => $pressRelease->id,
|
||||
'user_id' => $pressRelease->user_id,
|
||||
'company_id' => $pressRelease->company_id,
|
||||
'text_declaration' => $pressRelease->ai_text_declaration?->value,
|
||||
'image_declarations' => $imageDeclarations ?: null,
|
||||
'confirmed_at' => $pressRelease->ai_declaration_confirmed_at,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function logStatusChange(
|
||||
PressRelease $pressRelease,
|
||||
?PressReleaseStatus $from,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue