dev: Monitoring-Plan (Phasen 1–3, Entscheidungen M1–M9, Protokoll), Deploy-Checkliste v1.1.0, Runbook um Versionsdatei und Composer-Schritt ergänzt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-05 12:53:06 +00:00
parent 8769a71100
commit 22b62dfc1f
3 changed files with 361 additions and 1 deletions

View file

@ -379,7 +379,14 @@ Doku archiviert unter `dev/archive/optimize/cutover-runbook.md`.
### SFTP-Deploy-Checkliste (jedes Mal) ### SFTP-Deploy-Checkliste (jedes Mal)
1. Geänderte Dateien hochladen. 0. **Version:** Ausgeliefert wird ein Git-Tag nach SemVer (`v1.1.0`; Feature-Deploy
mittlere Stelle, Hotfix letzte). Lokal `git describe --tags --always --dirty > RELEASE`
und die Datei `RELEASE` mit hochladen — daraus lesen `config('app.version')`,
GlitchTip, `/health` und der Systemstatus die Version. Ablauf eines Deploys mit
Paket statt Einzeldateien: `dev/betrieb/deploy-v1.1.0.md`.
1. Geänderte Dateien hochladen. Bei geändertem `composer.lock`:
`composer install --no-dev --optimize-autoloader`, danach — falls
`public/vendor/livewire/` existiert — `php artisan vendor:publish --tag=livewire:assets --force`.
2. `php artisan view:clear && php artisan view:cache` — **immer bei 2. `php artisan view:clear && php artisan view:cache` — **immer bei
Blade-/Volt-Änderungen** (auch Livewire-Komponenten sind Blade-Dateien). Blade-/Volt-Änderungen** (auch Livewire-Komponenten sind Blade-Dateien).
3. `php artisan config:cache` — nur bei `.env`- oder `config/`-Änderungen. 3. `php artisan config:cache` — nur bei `.env`- oder `config/`-Änderungen.

View file

@ -0,0 +1,163 @@
# Deploy v1.1.0 — Checkliste
**Stand:** 05.10.2026 · **Live bisher:** Stand `98c465b` (Deploy 28.09.2026, ca. 14:34)
**Inhalt:** alles danach — Blöcke 9 und 10 aus dem Audit
([`ENTWICKLUNGSPLAN.md`](../2026-09-11-audit/ENTWICKLUNGSPLAN.md), Deploy-Plan
dort), Betriebsüberwachung Phase 1 ([`monitoring-plan.md`](monitoring-plan.md)),
Composer-Update, Versionsnummer.
Umfang gegenüber Live: 8 additive Migrationen, 2 neue Composer-Pakete
(`sentry/sentry-laravel`, Abhängigkeiten), 48 Paket-Updates, neue `.env`-Werte,
neue Assets (beide Builds), 2 neue Scheduler-Tasks (21 statt 19). Keine
gelöschten Code-Dateien.
Die Schritte aus dem Deploy-Plan der Blöcke 9/10 sind hier eingeordnet und
mit **(B9/10 Nr.)** gekennzeichnet.
## A — Lokal vorbereiten
1. Arbeitskopie committen, `main` per Fast-Forward nachziehen, Tag setzen:
`git tag -a v1.1.0 -m "v1.1.0 — Audit-Blöcke 9/10, Betriebsüberwachung Phase 1"`.
Push von `main` und Tag nach `origin`.
2. Auf dem Tag: `composer install`, `npm ci && npm run build`, volle Testsuite
(`php artisan test --compact --parallel`), Larastan.
3. Versionsdatei und Paket bauen (im Projektverzeichnis, auf dem Tag):
```bash
git describe --tags --always --dirty > RELEASE # muss genau "v1.1.0" ergeben
git -c core.quotepath=off diff --name-only --diff-filter=AMR 98c465b v1.1.0 \
-- . ':!dev' ':!docs' ':!tests' > deploy-files.txt
tar czf deploy-v1.1.0.tgz -T deploy-files.txt public/build RELEASE
```
Ergibt `RELEASE` etwas anderes als `v1.1.0` (z. B. `-dirty`), ist die
Arbeitskopie nicht sauber — erst klären.
4. Paket per SFTP nach `~/` auf den Server laden.
## B — Live vorbereiten (vor dem Wartungsfenster)
5. **DB-Backup** ziehen; `ARCHIVE_KEY` liegt getrennt gesichert vor. (B9/10 Nr. 4)
6. **Dateibackup** für den Rollback (inkl. `vendor/`, ohne `storage/`):
```bash
cd ~ && tar czf backup-vor-v1.1.0.tgz --exclude=./storage --exclude=./node_modules -C ~/pressekonto.com .
```
7. **`.env` auf Live ergänzen**:
| Variable | Wert | Herkunft |
|---|---|---|
| `NEWSROOM_API_ASSIGNMENT_USER_IDS` | `13993` (vorher prüfen: `select id from users where email = 'info@connektar.de'`) | E19 |
| `NEWSLETTER_SENDING_ENABLED` | `false` | E23 |
| `BOUNCE_UNSIGNED_KIND` | `policy` | E25/O01 |
| `SENTRY_LARAVEL_DSN` | DSN Projekt 1 (errors.adametz.media) | M1 |
| `SENTRY_BROWSER_DSN` | DSN Projekt 2 | M1 |
| `SENTRY_ENVIRONMENT` | `production` | |
| `SENTRY_RELEASE` / `APP_VERSION` | **leer lassen** — kommt aus `RELEASE` | |
| `MONITORING_NTFY_URL` | `https://push.adametz.media` | M3 |
| `MONITORING_NTFY_TOPIC` | `pressekonto-ops` | |
| `MONITORING_NTFY_TOKEN` | Token von `pressekonto-alerts` | |
| `MONITORING_HEALTH_TOKEN` | **neuer** Zufallswert, nicht der lokale (`openssl rand -hex 24`) | 1.5 |
| `MONITORING_ALERT_EMAIL` | Ops-Postfach prüfen (leer = `LEGAL_CONTACT_EMAIL`) | |
| `UMAMI_PORTAL_ENABLED` | `false` — erst nach DSE-Anpassung | M6 |
| `UMAMI_IDENTIFY_USERS` | `false` — erst nach DSE-Anpassung | M6 |
Optionale Werte (Standards greifen) siehe B9/10 Nr. 1.
8. **Hauptkategorien** gegen `config/canonical.php` prüfen:
`select id, slug from category_translations where locale = 'de' and category_id between 1 and 14`. (B9/10 Nr. 2)
9. **Uploadgrenzen** PHP `upload_max_filesize`/`post_max_size` und nginx
`client_max_body_size` ≥ 20M (Ploi: PHP-Einstellungen bzw. nginx-Konfiguration). (B9/10 Nr. 3)
10. **Ausgehend erreichbar** vom Server: `curl -sI https://errors.adametz.media` und
`curl -sI https://push.adametz.media` (beide antworten).
## C — Deploy (SSH, Wartungsfenster ca. 3–5 Minuten)
```bash
cd ~/pressekonto.com
php artisan down --retry=60
tar xzf ~/deploy-v1.1.0.tgz
composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force # 8 Migrationen, additiv (B9/10 Nr. 6)
[ -d public/vendor/livewire ] && php artisan vendor:publish --tag=livewire:assets --force
php artisan config:cache
php artisan route:cache
php artisan view:clear && php artisan view:cache
php artisan event:cache
php artisan schedule:clear-cache # alte 24-h-Sperren (B9/10 Nr. 7)
php artisan queue:restart
# PHP-FPM neu laden (Ploi: Server ▸ PHP ▸ Restart) — OPcache hält sonst alten Code
php artisan up
```
Schlägt `composer install` oder `migrate` fehl: **nicht** `up`, sondern Rollback (Abschnitt G).
## D — Direkt danach (Prüfungen)
11. `php artisan about --only=pressekonto` → Version `v1.1.0`.
12. Admin ▸ Reports ▸ Betriebsüberwachung: Version `v1.1.0`; nach 1–2 Minuten
Scheduler grün, Plattenplatz grün; `archive_key`, `archive_pending`,
`archive_sweep` grün (nach Nr. 14).
13. `curl -s -H "Authorization: Bearer <MONITORING_HEALTH_TOKEN>" https://pressekonto.com/health`
→ `"status":"ok"`, `"version":"v1.1.0"`. Direkt nach dem Deploy kann der
Scheduler noch fehlen (503) — eine Minute warten.
14. `php artisan archive:retry-pending` einmal von Hand. (B9/10 Nr. 8)
15. `php artisan company-memberships:api-assignments` sichten; `--remove` nur bei
Treffern ohne Freischaltung. (B9/10 Nr. 9)
16. `php artisan press-releases:assign-canonicals`. (B9/10 Nr. 10)
17. `php artisan press-release-images:sweep-orphan-originals` (Trockenlauf) und
`php artisan archive:sweep-orphans --dry-run`. (B9/10 Nr. 11, 12)
18. `php artisan schedule:list` → **21** Tasks, darunter `monitoring:heartbeat`
(minütlich), `queue:prune-failed` (03:55), `press-release-images:sweep-orphan-originals`
(So 04:45), `press-releases:purge-events` (03:50). (B9/10 Nr. 13)
19. **GlitchTip:** `php artisan tinker --execute 'report(new RuntimeException("Deploy-Test v1.1.0"));'`
→ erscheint in Projekt 1 mit Umgebung `production`, Release `v1.1.0`.
20. **ntfy:** `php artisan tinker --execute 'App\Support\OperationalAlert::send("Deploy v1.1.0 live");'`
→ Push auf dem Handy (und Mail an das Ops-Postfach).
21. `storage/logs/laravel-*.log` und `monitoring-*.log` auf neue Fehler prüfen.
## E — Abnahme mit dem Testkonto
22. Anmelden per Passwort, Magic Link und Google; danach einige Seiten
aufrufen. Ein eingeloggter Nutzer, der einen eigenen Magic Link öffnet,
landet auf dem Ziel. (B9/10 Nr. 14)
23. Einmal absichtlich falsches Passwort → Admin ▸ Reports ▸ Verlauf zeigt
„Anmeldung fehlgeschlagen … falsches Passwort“. (1.7)
24. Titelbild mit 13–15 MiB hochladen. (B9/10 Nr. 15)
25. `sitemap.xml`: englische Meldungen unter `/en/press-release/…`;
`article:modified_time` = Veröffentlichung/letzte Textänderung. (B9/10 Nr. 16)
26. Admin ▸ Newsletter: Sperrhinweis, „Senden“ aus. (B9/10 Nr. 17)
27. Nach dem nächsten `mail:fetch-bounces`: unsignierte Rückläufer als Richtlinie
„(unsigniert)“, ohne Sperre. (B9/10 Nr. 18)
28. Der Smoke-Test aus dem Runbook (Abschnitt 8): drei Domains, robots/sitemap,
Test-PM durch die Klassifikation, Formulare mit Mail.
## F — Danach
29. **Externer Monitor** (Uptime Kuma o. Ä.) auf `https://pressekonto.com/health`,
Header `Authorization: Bearer …`, 60 s, 2 Wiederholungen, Benachrichtigung über
ntfy (`push.adametz.media`, `pressekonto-ops`). (1.5)
30. **KAS-Passwort rotieren** (B9/10 Nr. 19).
31. **Datenschutzerklärung** (Betreiber/Anwalt): Rückläufer-Protokoll (B9/10 Nr. 20),
Fehlerprotokollierung GlitchTip (User-ID, URL, Browser, Fehlermeldung, 90 Tage),
fehlgeschlagene Anmeldungen im Verlauf (IP nur als HMAC). Danach — und erst
dann — `UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS` per `.env` + `config:cache`.
32. Restore-Übung (B9/10 Nr. 21).
33. Protokolle ergänzen: Deploy-Datum und Ergebnisse in `ENTWICKLUNGSPLAN.md`
(B9/10 Nr. 22) und `monitoring-plan.md`.
34. Backups `~/deploy-v1.1.0.tgz` und `~/backup-vor-v1.1.0.tgz` nach ein paar
stabilen Tagen löschen.
## G — Rollback
```bash
cd ~/pressekonto.com
php artisan down
tar xzf ~/backup-vor-v1.1.0.tgz -C ~/pressekonto.com # alter Code inkl. vendor/
php artisan config:cache && php artisan route:cache && php artisan view:clear && php artisan view:cache && php artisan event:cache
php artisan queue:restart # + PHP-FPM neu laden
php artisan up
```
Die Migrationen sind additiv; der alte Code läuft mit den neuen Spalten und
Tabellen. Nur bei Datenproblemen das DB-Backup aus Nr. 5 einspielen und danach
die Archiv-Schritte aus Runbook Abschnitt 9 befolgen.

View file

@ -0,0 +1,190 @@
# Betriebsüberwachung, Bedienbarkeit, Nutzungsstatistik — Plan
**Stand:** 02.10.2026 · **Branch:** `technik/monitoring-phase-1`
Ziel: Fehler fallen auf, bevor Kunden sie melden — Serverfehler aus der
Programmierung ebenso wie Bedienungs- und Usability-Probleme. Dazu eine
Nutzungsstatistik, aus der sich ablesen lässt, wer was tut und wo Arbeit
aufhört.
## Entscheidungen (Betreiber, 02.10.2026)
| Nr. | Frage | Entscheidung |
|---|---|---|
| M1 | Fehlerwerkzeug | **GlitchTip, selbst gehostet** (wie Umami auf sys.adametz.media), angebunden über die offiziellen Sentry-SDKs (`sentry/sentry-laravel`, `@sentry/browser`) |
| M2 | Umami im Portal | **ja, mit Zuordnung zum Nutzer** (`umami.identify` mit der internen User-ID, keine E-Mail, keine Namen) |
| M3 | Alarmweg | **Mail bleibt**, zusätzlich **ntfy** (HTTP-POST, JSON) |
Bei der Umsetzung getroffen (Bestätigung offen):
| Nr. | Frage | Entscheidung |
|---|---|---|
| M4 | Personenbezug in GlitchTip | Nur die interne User-ID, `send_default_pii` aus, Request-Bodys nie (`max_request_body_size = never`) |
| M5 | Browserfehler-SDK | Wird erst beim ersten Fehler nachgeladen (dynamischer Import) — kein Ladegewicht auf fehlerfreien Seiten, dafür ohne Breadcrumbs |
| M6 | Umami-Schalter | Getrennt: `UMAMI_ENABLED` (Webseiten), `UMAMI_PORTAL_ENABLED` (Kundenbereich), `UMAMI_IDENTIFY_USERS` (Zuordnung). Portal und Zuordnung erst nach DSE-Anpassung einschalten |
| M7 | Umami im Admin, unter Impersonation, auf Auth-Seiten | Nicht getrackt: Admin-Nutzung verfälscht die Kundenstatistik, Impersonation würde Admin-Klicks dem Kunden zuschreiben, Auth-Seiten tragen Tokens im Pfad (`/reset-password/{token}`) |
| M8 | Query-Parameter an Umami | Nur `utm_*`, `src`, `ref`, `q`, `page` gehen raus; alles andere (Signaturen, Tokens) filtert `data-before-send` — gilt auch für die Webseiten |
| M9 | Fehlgeschlagene Logins | Im Verlauf mit User-Bezug (falls Konto existiert) und gekürztem HMAC der IP statt Klartext-IP |
## Phase 1 — Fehler fallen sofort auf
| Schritt | Inhalt | Stand |
|---|---|---|
| 1.1 | GlitchTip-Anbindung Server: `sentry/sentry-laravel`, `Integration::handles()` in `bootstrap/app.php`, Release aus `SENTRY_RELEASE`, User-ID als einziger Personenbezug | erledigt |
| 1.2 | Browserfehler (JS-Fehler, unbehandelte Promises) auf Webseiten, Auth-Seiten und Portal; eigener Vite-Eintrag `resources/js/error-reporting.js`, SDK erst beim ersten Fehler | erledigt |
| 1.3 | Fehler ohne Exception als GlitchTip-Warnung: 404 mit Referrer der eigenen Domain (kaputte interne Links), 403 und 429 für angemeldete Nutzer, 419 (Seite abgelaufen, Arbeit verloren). Je Signal gedrosselt | erledigt |
| 1.4 | Scheduler-Herzschlag (`monitoring:heartbeat`, minütlich) und Check im Systemstatus | erledigt |
| 1.5 | Tiefer Health-Endpunkt `/health` mit Token: DB, Cache, Queue-Rückstau, Scheduler-Herzschlag, Plattenplatz → 200/503 JSON, für Uptime Kuma bzw. Ploi | erledigt |
| 1.6 | ntfy als zweiter Alarmweg in `OperationalAlert` | erledigt |
| 1.7 | Fehlgeschlagene Logins, Login-Sperren und 2FA-Fehler im Aktivitäts-Verlauf | erledigt |
| 1.8 | `queue:prune-failed` (30 Tage) im Scheduler | erledigt |
| 1.9 | Umami: Portal (Kundenbereich) mit `identify`, Query-Filter, Web Vitals; Schalter getrennt (M6) | erledigt |
Signale, die **nicht** im Browser erfasst werden, weil der Server sie schon
sieht: fehlgeschlagene Livewire-Requests mit 5xx (Exception in GlitchTip) und
419 (Schritt 1.3).
## Phase 2 — Bedienprobleme sichtbar machen
- Validierungsfehler zentral zählen: Formular/Komponente × Feld × Regel, nie der Wert.
- Schritt-Tracking für die Kernabläufe (Registrierung, Newsroom anlegen,
PM erstellen/einreichen, Checkout, Bildbestätigung): `step_viewed` /
`step_completed`, pseudonymer Ablauf-Schlüssel, eigene kleine Tabelle,
Schreiben per `defer()`.
- Benannte Umami-Events (`data-umami-event`) an den Kern-Buttons.
- „Problem melden“ im Portal: URL, Browser, letzte GlitchTip-Event-ID, Freitext.
- Geschäftstrichter aus vorhandenen Daten (Registrierung → erste PM →
eingereicht → bezahlt → veröffentlicht → zweite PM), ohne neues Tracking.
## Phase 3 — Rückgänge und Abwanderung erkennen
- Tägliche Kennzahlen-Tabelle (Datum × Kennzahl × Dimension → Anzahl), nächtlich verdichtet.
- Alarme bei ausbleibender Aktivität: Einlieferer ohne Einlieferung, Tage
ohne Registrierung/Login/Zahlung, Funktionen deutlich unter dem 4-Wochen-Mittel.
- Überfällig-Liste je Kunde (übliches PM-Intervall × 2).
- Admin-Seite „Betrieb & Nutzung“; `conversion:funnel` und `api:usage-report` mit Oberfläche.
- Morgendliche Sammelmail.
- Löschfristen für `activity_events` und `api_usage_logs` (Vorschlag 395 Tage).
## Datenschutz (Betreiber/Anwalt, vor dem Einschalten)
- DSE §5 (Umami): Portal-Nutzung mit Zuordnung zur Nutzer-ID ist personenbezogen
(pseudonym) — Abschnitt ergänzen, bevor `UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS` an.
- Neuer Abschnitt Fehlerprotokollierung (GlitchTip, selbst gehostet; User-ID,
URL, Browser, Fehlermeldung; Frist = GlitchTip-Aufbewahrung, Vorschlag 90 Tage).
- Aktivitäts-Verlauf mit fehlgeschlagenen Anmeldungen (IP nur als gekürzter HMAC).
- Unabhängig davon offen: `activity_events`/`api_usage_logs` ohne Löschfrist und ohne DSE-Erwähnung.
## Live-Schritte Phase 1
Ausgeführt mit Deploy v1.1.0 — vollständiger Ablauf in [`deploy-v1.1.0.md`](deploy-v1.1.0.md).
**Infrastruktur (einmalig)**
1. GlitchTip auf sys.adametz.media (Docker Compose: web, worker, Postgres,
Redis; `GLITCHTIP_MAX_EVENT_LIFE_DAYS=90`). Zwei Projekte anlegen:
`pressekonto-server` (PHP/Laravel) und `pressekonto-browser` (JavaScript).
Alert-Regel je Projekt: Mail an das Ops-Postfach bei neuem Fehlertyp.
2. ntfy: Server (ntfy.sh oder selbst gehostet), Topic mit Zugriffsschutz,
Token anlegen. Optional GlitchTip-Webhook an ntfy über dessen
Template-Funktion.
3. Uptime Kuma (oder Ploi-Monitor) auf `https://pressekonto.com/health` mit
Header `Authorization: Bearer <MONITORING_HEALTH_TOKEN>`, Intervall 1 min,
Benachrichtigung über ntfy und Mail.
**`.env` auf Live**
```
SENTRY_LARAVEL_DSN=… # Projekt pressekonto-server
SENTRY_ENVIRONMENT=production
SENTRY_RELEASE= # leer lassen, siehe RELEASE-Datei unten
SENTRY_BROWSER_DSN=… # Projekt pressekonto-browser
MONITORING_HEALTH_TOKEN=<zufällig, ≥ 32 Zeichen>
MONITORING_NTFY_URL=https://push.adametz.media
MONITORING_NTFY_TOPIC=pressekonto-ops
MONITORING_NTFY_TOKEN=…
UMAMI_ENABLED=true # Webseiten
UMAMI_PORTAL_ENABLED=false # erst nach DSE-Anpassung
UMAMI_IDENTIFY_USERS=false # erst nach DSE-Anpassung
```
Danach `config:cache`, `npm run build`, `queue:restart`.
**Bei jedem Deploy (SFTP/SSH, solange es kein Git-Deploy gibt):** lokal vor
dem Upload `git describe --tags --always --dirty > RELEASE` und die Datei `RELEASE`
mit hochladen, danach auf dem Server `php artisan config:cache`. GlitchTip
ordnet Fehler so dem Stand zu (`…-dirty` = mit nicht committeten Änderungen
deployt). Beim späteren Git-Deploy schreibt das Deploy-Skript dieselbe Datei.
GlitchTip-Instanz: `errors.adametz.media`, Projekt 1 = Server, Projekt 2 = Browser.
Umgebungen: `production` (Live), `local` (Entwicklung).
## Umsetzungsprotokoll
### 02.10.2026 — Phase 1 umgesetzt (Branch `technik/monitoring-phase-1`)
Neue Abhängigkeiten: `sentry/sentry-laravel` ^4.28 (Composer, nur neue
Pakete), `@sentry/browser` ^11.3 (npm).
| Schritt | Dateien |
|---|---|
| 1.1 | `config/sentry.php` (veröffentlicht, gehärtet), `app/Support/Monitoring/ErrorReportScrubber.php`, `bootstrap/app.php` (`Integration::handles`) |
| 1.2 | `resources/js/error-reporting.js` + `error-reporting-sdk.js` (Chunk 32 kB gzip, nur bei Fehler), `resources/views/components/monitoring/browser-errors.blade.php`, eingebunden in `partials/head` (Portal), `web/layouts/web-master`, `components/layouts/auth/pressekonto`; Vite-Einträge in beiden Configs |
| 1.3 | `app/Support/Monitoring/ErrorSignal.php`, `app/Http/Middleware/ReportErrorSignals.php` (global) |
| 1.4 | `app/Console/Commands/MonitoringHeartbeat.php`, Scheduler minütlich, Check `scheduler` im Systemstatus |
| 1.5 | `app/Services/Monitoring/HealthProbe.php`, `app/Http/Controllers/HealthController.php`, Route `/health` (ohne Gruppe, throttle 30/min), Basic-Auth-Ausnahme; Check `disk` im Systemstatus |
| 1.6 | `app/Support/OperationalAlert.php` (Mail und ntfy unabhängig, beide Best-Effort) |
| 1.7 | `ActivityType::LoginFailed/LoginLockout/TwoFactorFailed`, `RecordAuthActivity` (Grund `unknown_account`/`no_password`/`wrong_password`, `ip_hash`), Login-Komponente feuert `Failed` und `Lockout` mit Adresse |
| 1.8 | `queue:prune-failed --hours=720` täglich 03:55 |
| 1.9 | `components/web/umami.blade.php` (Web + Portal, `data-tag`, `data-before-send`, `data-performance`, `identify('user-<id>')`), `config/services.php` (`portal_enabled`, `identify_users`) |
Nebenbefund: Das Umami-Snippet der Webseiten schickte bisher die volle Query
mit — signierte Claim-Links (`?signature=…`) landeten so in Umami. Der Filter
(M8) gilt jetzt auch dort.
Tests: `tests/Feature/Monitoring/*` (neu), `tests/Feature/Auth/FailedLoginActivityTest.php`
(neu), `OperationalAlertTest` und `UmamiSnippetTest` erweitert. Gesamtsuite
2312 grün (1 übersprungen), Larastan ohne Meldung, Pint sauber.
Nicht im Browser geprüft (kein GlitchTip/ntfy angebunden): das tatsächliche
Einliefern an GlitchTip und die Darstellung dort. Erster Test nach dem
Aufsetzen: im Browser `setTimeout(() => { throw new Error('GlitchTip-Test') })`
auf einer Portalseite, serverseitig `php artisan tinker --execute 'report(new RuntimeException("GlitchTip-Test"));'`.
### 05.10.2026 — Composer-Update
`composer update` innerhalb der bestehenden Grenzen (48 Updates, u. a.
Laravel 12.69.3, Livewire 4.4.7, Flux 2.20.1, Fortify 1.40.0); damit sind die
beiden `league/commonmark`-Advisories behoben (2.10.3), `composer audit` sauber.
- Livewire 4.4.7 rendert `ModelNotFoundException` im Testhelfer als 404,
statt sie zu werfen — zwei Tests auf `assertNotFound()` umgestellt
(`CustomerCompanyContextTest`, `ContactAccessTest`). Laufzeitverhalten unverändert.
- **Deploy:** Liegt auf Live `public/vendor/livewire/` (veröffentlichte Assets),
nach `composer install` `php artisan vendor:publish --tag=livewire:assets --force`
ausführen — sonst läuft ein altes `livewire.js` gegen das neue Backend.
Lokal war die Kopie schon vor dem Update veraltet (Hash 3dabe98a statt 8ea5922c).
### 05.10.2026 — GlitchTip angebunden (lokal)
DSNs eingetragen (errors.adametz.media, Projekte 1 und 2), lokal
`SENTRY_ENVIRONMENT=local`. Release-Kennung ohne Git auf dem Server:
`App\Support\Monitoring\Release` liest `SENTRY_RELEASE`, sonst die beim
Deploy hochgeladene Datei `RELEASE` (in `.gitignore`). Test-Events lokal
abgeschickt: Server-Exception (`report()`), toter interner Link per HTTP.
### 05.10.2026 — ntfy angebunden (lokal)
ntfy auf `push.adametz.media` (gleicher Server wie GlitchTip),
`auth-default-access: deny-all` (anonymes Lesen → 403 geprüft). Eigener
Benutzer `pressekonto-alerts` mit `write-only` auf `pressekonto-ops`, Token in
`MONITORING_NTFY_TOKEN`; Lesen über den persönlichen Benutzer in der App.
Test-Alert aus `OperationalAlert` ohne Fehler angenommen.
### 05.10.2026 — Versionsnummer
Versionen sind Git-Tags nach SemVer (`v1.0.0` Go-Live 20.07., nächster Deploy
`v1.1.0`). `config('app.version')` = `APP_VERSION` oder Datei `RELEASE`
(`git describe --tags --always --dirty`); dieselbe Kennung als GlitchTip-Release,
in `/health` (`version`), im Admin-Systemstatus und in `php artisan about`.
Grundlage für die spätere Changelog-Seite.