dev: Umsetzungsaudits 29.09. und 02.10., Entwicklungsplan Blöcke 9 und 10 mit bestätigten Entscheidungen E19–E34 und Deploy-Plan, Runbook ergänzt
- UMSETZUNGSAUDIT-2026-09-29.md (Bestandsaufnahme) und -2026-10-02.md - ENTWICKLUNGSPLAN.md: Block 9 und 10, Entscheidungen E19–E34 vom Betreiber am 02.10.2026 bestätigt (E25 revidiert), Deploy-Plan A–E, Protokoll - deploy-runbook.md: vollständige Scheduler-Tabelle, Restore-Schritte mit --accept-unsigned/--without-export, Zugangsdaten aus Abschnitt 7 entfernt (stehen weiter in der Historie — Passwort wird rotiert) - Bildplan-Protokoll mit Korrektur zu „Altbestand geschützt" (A-05) - dev/README.md und dev/roadmap.md verweisen auf den aktuellen Stand - .env.example: neue Schlüssel aus Block 9/10 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5ec252da12
commit
aa47e113fe
8 changed files with 787 additions and 21 deletions
|
|
@ -168,16 +168,36 @@ Crontab des Web-Users:
|
|||
* * * * * cd /var/www/pressekonto && php artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
Aktive Tasks (`php artisan schedule:list`):
|
||||
Aktive Tasks (`php artisan schedule:list`, Stand 29.09.2026 — maßgeblich ist
|
||||
`routes/console.php`; `ScheduleConfigurationTest` prüft Sperrdauer und
|
||||
Minutenkollisionen):
|
||||
|
||||
| Zeitplan | Task | Ohne Cron passiert … |
|
||||
|---|---|---|
|
||||
| jede Minute | `press-releases:aggregate-views` | Aufrufzahlen bleiben stehen (Ereignisse sammeln sich) |
|
||||
| alle 5 Min | `press-releases:publish-scheduled` | **geplante PMs werden nie veröffentlicht** |
|
||||
| täglich 03:00 | `magic-links:purge --days=30` | Token-Tabelle wächst |
|
||||
| täglich 04:30 | `billing:generate-manual-invoices` | manuelle Rechnungen fehlen |
|
||||
| So 04:00 | `press-releases:purge-drafts --days=180` | Alt-Entwürfe bleiben liegen |
|
||||
| monatl. 1. 00:05 | `press-releases:reset-monthly-quota` | **Kontingente werden nie zurückgesetzt** |
|
||||
| alle 10 Min | `mail:fetch-bounces` | Rückläufer werden nicht gesperrt |
|
||||
| alle 15 Min | `market:refresh` | Börsendaten im Ticker veralten |
|
||||
| täglich 03:00 | `magic-links:purge --days=30` | Token-Tabelle wächst |
|
||||
| täglich 03:10 | `snapshots:purge` | Schwärzungs-Snapshots bleiben über 30 Tage liegen |
|
||||
| täglich 03:20 | `archive:purge` | Beweisarchiv hält Einträge über die Frist |
|
||||
| täglich 03:25 | `conversion:purge-events` | Konversionsereignisse und `email_bounces` über 395 Tage |
|
||||
| täglich 03:30 | `archive:export-tombstones` | Restore-Abgleich ohne aktuellen Löschstand |
|
||||
| täglich 03:35 | `archive:retry-pending` | offene Archivaufträge werden nicht nachgeholt |
|
||||
| täglich 03:40 | `archive:sweep-orphans` | verwaiste Archivdateien bleiben (Quarantäne statt Löschen) |
|
||||
| täglich 03:50 | `press-releases:purge-events` | Aufruf-, Crawler- und Feed-Protokolle über 395 Tage |
|
||||
| täglich 04:15 | `billing:expire-grandfathering` | abgelaufene Bestandsschutz-Verträge laufen weiter |
|
||||
| täglich 04:20 | `mail:check-domains --stale --limit=500` | DNS-Befunde der Kontaktdomains veralten |
|
||||
| täglich 04:30 | `billing:generate-manual-invoices` | manuelle Rechnungen fehlen |
|
||||
| täglich 05:10 | `press-releases:check-index-status` | Indexierungsbericht bekommt keine neuen Daten |
|
||||
| So 04:00 | `press-releases:purge-drafts --days=180` | Alt-Entwürfe bleiben liegen |
|
||||
| So 04:45 | `press-release-images:sweep-orphan-originals --execute --hours=48` | Originale abgebrochener Uploads bleiben liegen |
|
||||
| monatl. 1. 00:05 | `press-releases:reset-monthly-quota` | **Kontingente werden nie zurückgesetzt** |
|
||||
|
||||
Jeder Task hält seine `withoutOverlapping()`-Sperre nur kurz über der
|
||||
erwarteten Laufzeit (Audit K-09). Nach einem Absturz mitten im Lauf gibt
|
||||
spätestens dann der nächste Lauf wieder frei; sofort mit
|
||||
`php artisan schedule:clear-cache`.
|
||||
|
||||
**Monitoring (R5.3):** Jeder Scheduler-Task hat ein `->onFailure()`, der
|
||||
Queue-Worker einen `Queue::failing()`-Listener. Fehlschläge werden als
|
||||
|
|
@ -193,7 +213,7 @@ Endpoint in Stripe (Live-Mode) registrieren: `https://pressekonto.de/stripe/webh
|
|||
Events: die Cashier-Defaults (Subscriptions, Invoices, Payment-Intents).
|
||||
|
||||
## 7. Erst-Einrichtung (nur beim allerersten Go-Live)
|
||||
# kopieren der Bilddaten per rync #@jk:8#PN§!4V02wR:rV#
|
||||
# Bilddaten per rsync kopieren (Zugangsdaten stehen im Passwortmanager, nicht im Repository)
|
||||
|
||||
rsync -avz --partial ssh-w01eb4f6@w008b36f.kasserver.com:/www/htdocs/w01eb4f6/presseecho.de/web/thumbnails/ \
|
||||
dev/migration/presseecho/thumbnails/
|
||||
|
|
@ -288,13 +308,42 @@ Newsroom-Übersicht, eine alte PM-Detailseite (lädt Bild + Anhang?), Firmenlogo
|
|||
(`config:cache` … `queue:restart`).
|
||||
- Migrationen sind additiv gehalten; kein automatisches `migrate:rollback` in Prod —
|
||||
im Fehlerfall DB-Backup einspielen (Backup **vor** jedem Deploy mit Migrationen!).
|
||||
- **Nach jedem DB-Restore:** `php artisan archive:reconcile-after-restore` ausführen.
|
||||
Das Beweisarchiv (Bildkennzeichnung Block F) löscht Einträge nach Frist; eine
|
||||
eingespielte Sicherung würde gelöschte Zeilen und Payloads zurückbringen. Der
|
||||
Abgleich liest den täglich exportierten Tombstone-Stand
|
||||
(`storage/app/archive/tombstones/latest.json`) **und** die Tombstone-Zeilen der
|
||||
Datenbank und entfernt wiederhergestellte Payloads erneut. Exit-Code ≠ 0 heißt:
|
||||
mindestens eine Datei ließ sich nicht löschen — Log prüfen, Lauf wiederholen.
|
||||
- **Nach jedem DB-Restore — Beweisarchiv in dieser Reihenfolge** (Umsetzungsaudit
|
||||
29.09.2026, A-03/A-06/A-15):
|
||||
1. **Sofort** `php artisan archive:sweep-orphans --pause` (der Sweep läuft
|
||||
nachts um 03:40; nach einem DB-Restore fehlen jüngeren Einträgen die Zeilen,
|
||||
ihre Payloads sähen verwaist aus). Seit 29.09. verschiebt der Sweep ohnehin nur
|
||||
in die Quarantäne (`storage/app/archive/quarantine/JJJJ-MM-TT/…`,
|
||||
`ARCHIVE_QUARANTINE_DAYS`, Standard 30 Tage) — die Pause verhindert auch das.
|
||||
Harte Alternative ohne DB: `ARCHIVE_SWEEP_PAUSED=true` + `config:cache`.
|
||||
2. `php artisan archive:reconcile-after-restore` — liest den täglich exportierten
|
||||
Tombstone-Stand (`tombstones/latest.json`, seit 29.09. mit HMAC) **und** die
|
||||
Tombstone-Zeilen der Datenbank, entfernt wiederhergestellte Payloads erneut und
|
||||
löscht die mit dem Backup zurückgekommenen Kopien in der Anwendung
|
||||
(Bestätigungen, Korrekturen, weich gelöschte Bildzeile) erneut. Setzt den
|
||||
Sweep selbst aus. Zuordnung über den Payload-Pfad, nicht die ID: passt eine
|
||||
Export-Zeile zu einer DB-Zeile mit gleicher ID, aber anderem Payload, wird
|
||||
nichts angefasst („passen nicht zur Datenbank", Exit ≠ 0, Log prüfen).
|
||||
„Signatur ungültig" = Export verworfen, nur DB-Tombstones abgeglichen,
|
||||
Exit ≠ 0 — Export-Herkunft prüfen; fehlt ein Schlüssel in
|
||||
`ARCHIVE_PREVIOUS_KEYS`? Ein **unsignierter** Export wird seit 02.10.2026
|
||||
ebenfalls verworfen (ein entferntes `hmac` sähe genauso aus); nur wenn er
|
||||
nachweislich von vor dem 29.09.2026 stammt, mit `--accept-unsigned`
|
||||
wiederholen. Exit ≠ 0 sonst: mindestens eine Datei ließ sich nicht löschen
|
||||
— Log prüfen, Lauf wiederholen.
|
||||
3. `php artisan archive:reregister --dry-run`, dann `php artisan archive:reregister`
|
||||
— legt für Payloads ohne Zeile (Einträge nach dem Backup-Zeitpunkt, auch aus der
|
||||
Quarantäne) die Zeile aus `manifest.json` wieder an. Laut Tombstone-Export
|
||||
gelöschte Payloads bleiben außen vor. **Bricht ab**, wenn der Export ungültig
|
||||
oder unsigniert ist (wie Schritt 2, `--accept-unsigned` nur für Altexporte)
|
||||
oder ganz fehlt — ohne Export lassen sich gelöschte Einträge nicht von
|
||||
verlorenen unterscheiden; nur bewusst mit `--without-export`. Legal Hold
|
||||
steht nicht im Manifest: wiederhergestellte Einträge (Zusammenfassung
|
||||
`reregistered_at`) im Admin prüfen.
|
||||
4. `php artisan archive:retry-pending` (offene Archivaufträge nachholen; Zähler im
|
||||
Admin-Systemstatus) und `php artisan archive:sweep-orphans --dry-run`.
|
||||
5. `php artisan archive:sweep-orphans --resume` — Kachel „Beweisarchiv-Orphan-Sweep"
|
||||
im Systemstatus wird wieder grün.
|
||||
- **Achtung bei Restore der Archiv-Disk:** Wird `storage/app/archive` selbst auf
|
||||
einen älteren Stand zurückgesetzt, ist auch `tombstones/latest.json` alt. Dann
|
||||
**vor** dem Abgleich den jüngsten Tombstone-Export von außerhalb des Hosts
|
||||
|
|
@ -302,13 +351,23 @@ Newsroom-Übersicht, eine alte PM-Detailseite (lädt Bild + Anhang?), Firmenlogo
|
|||
oder sicherstellen, dass die Datenbank den aktuellen Stand hat — der Abgleich
|
||||
nimmt dann die DB-Tombstones. Ein automatisches externes Exportziel ist nicht
|
||||
gebaut (Betreiberentscheidung offen, Umsetzungsaudit 25.09.2026, 5.1 c).
|
||||
- **Nach dem Restore ebenfalls:** `php artisan archive:retry-pending` (holt
|
||||
Archiveinträge nach, deren Ablage beim Vorgang fehlgeschlagen ist; Zähler im
|
||||
Admin-Systemstatus) und `php artisan archive:sweep-orphans --dry-run`
|
||||
(Payload-Dateien ohne Zeile).
|
||||
- `ARCHIVE_KEY` (Beweisarchiv) und `CONFIRMATION_IP_KEY` liegen nur in der
|
||||
Ploi-Umgebung. `ARCHIVE_KEY` getrennt vom DB-Backup sichern — ohne ihn ist das
|
||||
Archiv unlesbar; erzeugen mit `php artisan archive:key`.
|
||||
Archiv unlesbar; erzeugen mit `php artisan archive:key`. Fehlt er (z. B. nach
|
||||
`config:cache` ohne Schlüssel), meldet der Systemstatus „Beweisarchiv-Schlüssel"
|
||||
kritisch und `archive:retry-pending` bricht ab, ohne Aufträge anzufassen.
|
||||
- **Schlüsselrotation `ARCHIVE_KEY`** (A-15): neuen Schlüssel mit
|
||||
`php artisan archive:key` erzeugen und getrennt sichern; den bisherigen Wert
|
||||
**vorne** in `ARCHIVE_PREVIOUS_KEYS` (kommagetrennt) eintragen, den neuen als
|
||||
`ARCHIVE_KEY` setzen, `config:cache`, `queue:restart`. Jeder Eintrag trägt die
|
||||
ID seines Schlüssels (`archive_entries.key_id`); alte Einträge bleiben mit dem
|
||||
früheren Schlüssel lesbar, neue werden mit dem neuen verschlüsselt. Einen
|
||||
früheren Schlüssel nur entfernen, wenn der Systemstatus keine Einträge mit
|
||||
unbekanntem Schlüssel meldet (praktisch: erst nach Ablauf aller damit
|
||||
verschlüsselten Einträge). Anwaltsexporte werden mit einem per HKDF abgeleiteten
|
||||
Schlüssel signiert; `php artisan archive:verify-export <zip>` prüft neue und alte
|
||||
Exporte (vor dem 29.09. mit dem Archivschlüssel signiert) gegen alle bekannten
|
||||
Schlüssel.
|
||||
|
||||
## 10. Betrieb nach Go-Live (Stand 20.07.2026 — System ist LIVE)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue