archive: Auftragstabelle statt Marker, exklusiver Abschluss, Quarantäne, Restore über Payload-Identität und signierte Exporte, Schlüsselrotation (Umsetzungsaudit 29.09.: A-01 bis A-15, OPT-6, 5.1 d; 02.10.: O04, O05, O07, O08)
- A-01/A-02/A-04: archive_pending_jobs je Vorgang (ArchivePendingQueue), Depublikationsstand am Auftrag festgehalten, Nachholer ohne Schlüssel FAILURE; archive_pending_at nur noch Sammelmarker - O07: Direktweg und Nachholer beanspruchen den Auftrag unter Zeilensperre - O08: fehlende Snapshot-Datei → Verweis auf den image_removed-Eintrag oder offener Auftrag, nie ein still unvollständiger Nachweis - A-03/A-06: Sweep verschiebt in die Quarantäne, Pause beim Restore, archive:reregister; Restore-Abgleich löscht Anwendungskopien erneut - O04/O05: Restore ordnet über den Payload-Pfad zu; unsignierte Exporte nur mit --accept-unsigned, reregister bricht bei ungültigem/fehlendem Export ab - A-05, A-07 bis A-15: Altbestand als möglicherweise veröffentlicht, Hold- Historie, Zweck per POST, Nachweise gelöschter Bilder nur mit compliance:manage, Anhänge auf ihrer Disk, Schlüssel-ID und ARCHIVE_PREVIOUS_KEYS, HMAC per HKDF, archive:verify-export - 5.1 d: Depublikation im Meldeverfahren trägt legal_request_id PressReleaseService.php enthält zusätzlich K-01 (Prüfung im Publish-Lock), N19 (archive() gibt Kopien frei) und N09 (markImagesPublished nur für auslieferbare Bilder), weil die Datei ungeteilt committet ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c82f279fef
commit
60650baeb8
29 changed files with 2918 additions and 412 deletions
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Beweisarchiv (Umsetzungsaudit 29.09.2026, A-02/A-04/A-09/A-10): ein
|
||||
* Auftrag je ausstehendem Archiveintrag statt eines einzigen Zeitstempels
|
||||
* an der Meldung. Der Auftrag entsteht in der fachlichen Transaktion und
|
||||
* trägt, was der Nachholer braucht — bei `pm_depublished` den Stand zum
|
||||
* Zeitpunkt der Depublikation (verschlüsselt, APP_KEY wie die
|
||||
* Redaction-Snapshots). Er wird gelöscht, sobald der Eintrag geschrieben
|
||||
* ist; `press_releases.archive_pending_at` bleibt als Sammelmarker
|
||||
* (Systemstatus, Kompatibilität) und fällt erst mit dem letzten Auftrag.
|
||||
*
|
||||
* Kein Fremdschlüssel auf press_releases: Aufträge sollen eine
|
||||
* Hartlöschung der Meldung überleben und im Systemstatus sichtbar bleiben.
|
||||
* Neue, kleine Tabelle — läuft sofort.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('archive_pending_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('press_release_id');
|
||||
$table->string('event', 30);
|
||||
$table->string('subject_type', 80)->nullable();
|
||||
$table->unsignedBigInteger('subject_id')->nullable();
|
||||
$table->string('reason_category', 30)->nullable();
|
||||
$table->unsignedBigInteger('legal_request_id')->nullable();
|
||||
$table->unsignedBigInteger('actor_id')->nullable();
|
||||
$table->string('note', 500)->nullable();
|
||||
$table->longText('payload')->nullable();
|
||||
$table->unsignedInteger('attempts')->default(0);
|
||||
$table->string('last_error', 1000)->nullable();
|
||||
$table->timestamp('last_attempted_at')->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->index(['press_release_id'], 'archive_pending_jobs_press_release_idx');
|
||||
$table->index(['event', 'created_at'], 'archive_pending_jobs_event_created_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('archive_pending_jobs');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Beweisarchiv (Umsetzungsaudit 29.09.2026):
|
||||
*
|
||||
* - `key_id` (A-15): kurze Kennung des Schlüssels, mit dem der Payload
|
||||
* verschlüsselt wurde. Nach einer Rotation (ARCHIVE_PREVIOUS_KEYS)
|
||||
* entschlüsselt das Archiv mit dem passenden Schlüssel; Bestand ohne
|
||||
* Kennung probiert alle bekannten Schlüssel.
|
||||
* - Index auf `payload_path` (OPT-6): Orphan-Sweep, Wiederregistrierung und
|
||||
* Restore-Abgleich suchen nach Pfad.
|
||||
*
|
||||
* archive_entries ist klein und ohne FULLTEXT — ADD COLUMN und ADD INDEX
|
||||
* laufen online.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('archive_entries', function (Blueprint $table) {
|
||||
$table->char('key_id', 16)->nullable()->after('payload_bytes');
|
||||
$table->index(['payload_path'], 'archive_entries_payload_path_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('archive_entries', function (Blueprint $table) {
|
||||
$table->dropIndex('archive_entries_payload_path_idx');
|
||||
$table->dropColumn('key_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Beweisarchiv (Umsetzungsaudit 29.09.2026, A-07): Name und E-Mail des
|
||||
* Admins werden mitgeschrieben. `admin_id` bleibt `nullOnDelete` (eine
|
||||
* Kontolöschung soll nicht am Zugriffsprotokoll scheitern) — nach einer
|
||||
* Löschung weist die Zeile den Zugreifenden trotzdem aus. Kleine Tabelle,
|
||||
* ADD COLUMN läuft sofort.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('archive_access_log', function (Blueprint $table) {
|
||||
$table->string('admin_name')->nullable()->after('admin_id');
|
||||
$table->string('admin_email')->nullable()->after('admin_name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('archive_access_log', function (Blueprint $table) {
|
||||
$table->dropColumn(['admin_name', 'admin_email']);
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue