# Pseudo-Events

> Log-Tabelle-Polling

---

Webtrees hat keinen Event-Bus, daher werden Core-Aktionen (Logins, fehlgeschlagene Logins, Logouts, Datensatz-Edits, Fehler, Suchen) durch **Polling der webtrees-Log-Tabelle** (`app/Log.php`, Tabelle `log`) detektiert und pro neuem passenden Log-Eintrag ein Ereignis ausgelöst. Es ist **standardmäßig deaktiviert**: nichts läuft, bis der angebotene `cronjob:pseudo-events`-Job aktiviert wird.

**Eingebaute Events** (ein `cj_event` pro neuer Log-Zeile, Payload = die Zeile):

| Ereignis | Log-Filter (log_type, Nachricht-Präfix) | Feuer bei |
| :--- | :--- | :--- |
| `cronjob:log-auth-failed` | `auth`, `Login failed` | ein Login-Versuch fehlgeschlagen (falsches Passwort, unbekannter Benutzer, nicht verifiziert, nicht genehmigt, keine Session-Cookies) |
| `cronjob:log-auth-login` | `auth`, `Login:` | ein Benutzer eingeloggt |
| `cronjob:log-auth-logout` | `auth`, `Logout:` | ein Benutzer ausgeloggt |
| `cronjob:log-error` | `error`, – | eine Ausnahme erfasst und geloggt (die Nachricht ist der Trace) |
| `cronjob:log-edit-update` | `edit`, `Update:` | ein Datensatz aktualisiert (`GedcomRecord::updateRecord()`) |
| `cronjob:log-edit-delete` | `edit`, `Delete:` | ein Datensatz gelöscht (`GedcomRecord::deleteRecord()`) |
| `cronjob:log-search` | `search`, – | eine Suche ausgeführt (**eine Log-Zeile pro durchsuchtem Baum**) |

**Payload:** `log_id`, `log_time`, `log_message` (auf 250 Zeichen gekürzt, UTF-8-safe) plus `gedcom_id` / `user_id`, wenn die Log-Zeile sie trägt. Die Nachrichten-Präfixe sind die hartkodierten englischen Strings des aktuellen Cores; ein Core-Update, das sie ändert, stoppt das passende Ereignis still (kein Fallback per Design) — die Event-Katalog-Beschreibungen und die `test-pseudo-events`-Suite fixieren die aktuelle Zuordnung.

**So läuft es**

- **Ein Trigger, ein Cooldown.** Die Detektion läuft nur vom angebotenen `cronjob:pseudo-events`-Job (`*/5 * * * *` per Default), im Kindprozess des Ticks — isoliert von jeder Web-Anfrage und durch den Job-Timeout begrenzt, sodass ein schwerer Detektor nie eine Page-Load blockieren oder verlangsamen kann. Ein 5-Minuten-Cooldown (ein `data/` mtime-Marker) plus ein Lock halten wiederholte Läufe günstig und idempotent; der Cooldown begrenzt auch einen häufigeren Job-Cron.
- **Vom Job aktiviert, pro Ereignis gated.** Das Aktivieren des `cronjob:pseudo-events`-Jobs schaltet die Detektion ein (es gibt keinen separaten Schalter — der Tick führt nur aktivierte Jobs aus). Ein Detektor, dessen Ereignis kein aktiver ereignisgesteuerter Job abhört, wird komplett übersprungen — keine Abfrage, kein Push.
- **Checkpoint + Backpressure.** Jeder Detektor erinnert sich an die höchste `log_id`, die er verarbeitet hat (Statusdatei). Höchstens 200 Zeilen werden pro Lauf pro Detektor ausgelöst; der Checkpoint rückt nur bis zur letzten ausgelösten Zeile vor, sodass eine Flut über mehrere Läufe abgearbeitet wird — kein Datenverlust, nur Latenz.
- **Baseline beim ersten Lauf.** Die erste Detektion erfasst nur die aktuelle max `log_id` und löst nichts aus, sodass das Aktivieren des Jobs nicht rückwirkend auf bestehende Daten feuert. (Erneutes Abhören eines zuvor nicht abgehörten Events holt seinen Backlog nach, begrenzt wie oben.)
- **Reset, nicht neu feuern, bei Tabellenschrumpfung.** Wenn die Tabelle unter einen Checkpoint schrumpfte (TRUNCATE / DB-Restore — einfache Zeilend deletions können das nicht, MySQL Auto-Increment läuft nach Deletes weiter), baselined der Detektor still neu.
- **Offline-sicher & isoliert.** Übersprungen, während `data/offline.txt` existiert; ein kaputter Detektor bricht nie die anderen; die Detektion ist serialisiert (flock) und der Status wird atomar nach `data/cronjob/pseudo-events.json` persistiert.

Die **Log-Tabelle ist die Quelle der Wahrheit**: unter einer Flut führt die Koaleszierung des Ticks einen Job einmal pro Ereignisnamen pro Tick mit dem neuesten Payload aus — ältere Zeilen werden nicht neu zugestellt, aber sie bleiben in der Log-Tabelle, und `log_id` im Payload ermöglicht einem Job, der Vollständigkeit braucht, den Bereich selbst neu zu lesen.

> Wenn ein Ereignisname verschwindet, markiert die Admin-Tabelle solche Trigger mit dem **unknown event**-Badge.

Detektierte Events gehen in dieselbe `cj_event`-Queue wie Webhooks und werden vom normalen Event-Drain des Ticks konsumiert. Um darauf zu reagieren, erstellen Sie einen ereignisgesteuerten Job, dessen **Ereignisname** passt (z.B. bei `cronjob:log-auth-failed` Alarm, bei `cronjob:log-edit-update` neu indexieren).
