archive: Beweisarchiv — verschlüsselte Einträge, Legal Hold, Purge mit Tombstone, Restore-Abgleich, Auslöser und Admin-Seite (Bildkennzeichnung Block F)
- archive_entries (append-only) + archive_access_log; Disk archive außerhalb des Web-Roots; eigener ARCHIVE_KEY (archive:key), AES-256-CBC, SHA-256 am Eintrag - EvidenceArchive: record/open/manifest/export (signiertes ZIP), setLegalHold/releaseLegalHold (Frist läuft neu), purgeDue, exportTombstones, reconcileAfterRestore - PressReleaseArchiver: image_removed (Original, Snapshot, Bestätigungen), pm_depublished (letzter veröffentlichter Stand), gdpr_change (Anonymisierung, Korrekturen), label_corrected - Hooks in Remover, depublish/archive/changeStatusFromAdmin/deleteFromAdmin, AnonymizationService, CorrectionService, ImageCorrectionService - Scheduler archive:purge 03:20 und archive:export-tombstones 03:30; archive:reconcile-after-restore im Runbook - Admin ▸ Management ▸ Beweisarchiv (Liste, Legal Hold mit Pflichtvermerk, Öffnen/Export nur mit Zweck) - Scope-Wächter: neue Befehle nutzen withoutGlobalScope(PortalScope::class) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
da167b713b
commit
b5b70416f2
30 changed files with 1746 additions and 6 deletions
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Beweisarchiv (Bildkennzeichnung Block F, Decision-Update 4.2): ein Eintrag
|
||||
* je Ereignis mit Verweis auf den verschlüsselten Payload im Archiv-Storage.
|
||||
*
|
||||
* Append-only: Es gibt nur Anlegen, Tombstone (deleted_at, Payload weg,
|
||||
* Zeile bleibt ohne Inhalt) und legal_hold setzen/aufheben. Das Audit-Log
|
||||
* der Anwendung verweist nur auf die id, nie auf Altwerte.
|
||||
*
|
||||
* `archive_access_log`: jeder lesende Zugriff mit Admin, Zeitpunkt und
|
||||
* Pflichtangabe des Zwecks.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('archive_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('subject_type', 80);
|
||||
$table->unsignedBigInteger('subject_id');
|
||||
$table->unsignedBigInteger('press_release_id')->nullable();
|
||||
$table->string('event', 30);
|
||||
$table->string('actor_type', 20);
|
||||
$table->unsignedBigInteger('actor_id')->nullable();
|
||||
$table->string('reason_category', 30);
|
||||
$table->json('summary')->nullable();
|
||||
$table->string('payload_path', 512)->nullable();
|
||||
$table->char('payload_hash', 64)->nullable();
|
||||
$table->unsignedBigInteger('payload_bytes')->nullable();
|
||||
$table->date('retain_until');
|
||||
$table->boolean('legal_hold')->default(false);
|
||||
$table->string('legal_hold_note', 1000)->nullable();
|
||||
$table->unsignedBigInteger('legal_hold_set_by')->nullable();
|
||||
$table->timestamp('legal_hold_set_at')->nullable();
|
||||
$table->timestamp('deleted_at')->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->index(['subject_type', 'subject_id'], 'archive_entries_subject_idx');
|
||||
$table->index(['press_release_id'], 'archive_entries_press_release_idx');
|
||||
$table->index(['retain_until', 'legal_hold', 'deleted_at'], 'archive_entries_purge_idx');
|
||||
$table->index(['event', 'created_at'], 'archive_entries_event_created_idx');
|
||||
});
|
||||
|
||||
Schema::create('archive_access_log', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('entry_id')->constrained('archive_entries')->cascadeOnDelete();
|
||||
$table->foreignId('admin_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('action', 20);
|
||||
$table->string('purpose', 500);
|
||||
$table->timestamp('accessed_at');
|
||||
|
||||
$table->index(['entry_id', 'accessed_at'], 'archive_access_entry_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('archive_access_log');
|
||||
Schema::dropIfExists('archive_entries');
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue