*/ public const DETECTORS = [ // ------------------------------------------------------------------ // Findet Dinge, an die niemand gedacht hat // ------------------------------------------------------------------ // Diese Gruppe braucht kein Vorwissen ueber den konkreten Fehler. Der // Massstab kommt nicht aus einem Kopf, sondern aus der Wirklichkeit: // aus dem eigenen Verlauf, aus der Vorversion, aus den anderen // Installationen derselben Fassung. 'novel.error_signature' => [ 'origin' => self::ORIGIN_SERVER, 'finds' => 'Fehlersignatur, die es in dieser Fassung noch nie gab', 'needs' => 'Fehler-Stream (bugtracker_items mit error_level)', ], 'novel.log_template' => [ 'origin' => self::ORIGIN_SERVER, 'finds' => 'Logzeilen-Schablone, die noch nie auftrat', 'needs' => 'Logauszuege im Fehler-Stream', ], 'silence.periodic' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Bekanntes wiederkehrendes Ereignis bleibt aus - der still gestorbene Hintergrundjob', 'needs' => 'Ein Melder, der das Ereignis bestaetigt, solange es stattfindet', ], 'silence.heartbeat' => [ 'origin' => self::ORIGIN_SERVER, 'finds' => 'Instanz meldet sich nicht mehr', 'needs' => 'Watchdog-Monitor', ], 'deviation.self' => [ 'origin' => self::ORIGIN_SERVER, 'finds' => 'Abweichung vom eigenen Verlauf der letzten Tage', 'needs' => 'Kennzahlen im MetricStore, mindestens 30 Messpunkte aelter als zwei Stunden', ], 'deviation.fleet' => [ 'origin' => self::ORIGIN_SERVER, 'finds' => 'Ausreisser gegenueber anderen Installationen derselben Fassung', 'needs' => 'mehrere Installationen mit derselben app_version', ], 'regression.build' => [ 'origin' => self::ORIGIN_SERVER, 'finds' => 'Kennzahl verschiebt sich nach einem Update gegenueber der Vorgaengerfassung', 'needs' => 'app_version am Monitor und Kennzahlen ueber den Rollout hinweg', ], // ------------------------------------------------------------------ // Die Zeitachse // ------------------------------------------------------------------ // Ein Test laeuft Millisekunden auf frischem Zustand, ein // Kundenprozess Wochen. Alles, was sich ansammelt, ist fuer Tests // strukturell unsichtbar. 'trend.monotonic' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Kennzahl waechst ueber mehrere Fenster nur nach oben - Speicher-, Handle-, Cache-Leck', 'needs' => 'Eine Messreihe ueber Stunden, von wem auch immer gemeldet', ], 'trend.saturation' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Kennzahl naehert sich einer harten Grenze - Platte, Verbindungspool, Dateihandles', 'needs' => 'bekannte Obergrenze', ], 'trend.latency' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Antwortzeiten driften ueber Tage nach oben - etwa Index-Degradation', 'needs' => 'Zeitmessung an einer wiederkehrenden Operation, regelmaessig gemeldet', ], // ------------------------------------------------------------------ // Stellen, an denen still verziehen wird // ------------------------------------------------------------------ // Der ergiebigste und billigste Fang: Zaehler auf die Stellen, an // denen die Software heute schon still verzeiht. Dort sitzen die // Fehler, die niemand meldet. 'swallowed.exception' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'catch-Bloecke, die einen Fehler schlucken und weitermachen', 'needs' => 'Ein Melder an den catch-Bloecken, die einen Fehler schlucken', ], 'fallback.usage' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Rueckfall auf den degradierten Pfad haeuft sich', 'needs' => 'Ein Melder im Ausweichzweig', ], 'retry.storm' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Wiederholungsversuche haeufen sich', 'needs' => 'Ein Melder an der Wiederholung', ], 'probe.failed' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Registrierte Probe schlaegt fehl', 'needs' => 'Ein Pruefwerkzeug, das die Pruefung ausfuehrt und das Ergebnis meldet', ], 'probe.slow' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Probe antwortet, aber deutlich langsamer als ueblich', 'needs' => 'Wie probe.failed, zusaetzlich mit Zeitmessung', ], // ------------------------------------------------------------------ // Fachliche Zusicherungen // ------------------------------------------------------------------ // Die einzige Gruppe, die tatsaechlich "Unit-Test zur Laufzeit" ist - // und fuer ihren Zweck ist genau das richtig. Sie deckt die Klasse ab, // die sonst komplett durchfaellt: semantisch falsche, aber // unauffaellige Ergebnisse. Der Bericht zeigt eine falsche Zahl, die // Rundung stimmt seit Monaten nicht, ein Mandant sieht Daten eines // anderen. Nichts stuerzt ab, keine Kennzahl weicht ab, keine Signatur // ist neu - kein anderer Detektor sieht das. 'invariant.violated' => [ 'origin' => self::ORIGIN_CLIENT, 'finds' => 'Vom Projekt formulierte Zusicherung gegen echte Daten verletzt', 'needs' => 'Ein Melder, der die Zusicherung gegen echte Daten prueft', ], ]; /** @return list */ public static function names(): array { return array_keys(self::DETECTORS); } public static function isKnown(string $detector): bool { return isset(self::DETECTORS[$detector]); } /** * Wo entsteht dieser Befund normalerweise? * * Unbekannte Detektoren gelten als clientseitig - ein Projekt darf einen * eigenen Namen melden, ohne dass der Katalog dafuer erweitert werden * muss. Es taucht dann in der Auswertung als eigene Reihe auf und faellt * beim Durchsehen auf, statt still verworfen zu werden. */ public static function originOf(string $detector): string { return self::DETECTORS[$detector]['origin'] ?? self::ORIGIN_CLIENT; } /** * Nur die Detektoren einer Herkunft. * * @return array */ public static function byOrigin(string $origin): array { return array_filter( self::DETECTORS, static fn(array $meta): bool => $meta['origin'] === $origin ); } }