Watchdog-Anleitung vollständig überarbeitet
- Alle drei Codebeispiele trugen das geseedete Demo-Token fest im Quelltext.
Es ist an die Source "srv-db-01" gebunden — wer es übernommen hätte, wäre für
jeden anderen Dienst abgewiesen worden. Jetzt Umgebungsvariable und eine
Anleitung, wie man ein eigenes Token erzeugt.
- Der Ratschlag "sende beim Beenden einen Ping mit Status stopped" beschrieb
etwas, das die API nicht konnte. Statt die Anleitung an die Lücke anzupassen,
ist die Lücke geschlossen: status akzeptiert jetzt "stopped" und
"maintenance". Der Evaluator lässt solche Monitore in Ruhe, statt wenige
Minuten nach jedem sauberen Shutdown einen Fehlalarm zu erzeugen.
- Neu dokumentiert: checks (Gesundheitszustand per Push, ohne offene Ports),
metrics samt Verlauf und Abweichungsvergleich, Alarmunterdrückung über die
Hierarchie, die Schwellen des Evaluators (2x warning, 4x down) und der
erforderliche Cron-Job.
Lizenz-Anleitung
- Neuer Abschnitt zum Antwortformat. Die Lizenz-Endpunkte antworten bewusst
ohne den status/error-Umschlag der übrigen API; das Feld status auf oberster
Ebene trägt den Lizenzzustand. Genau diese Besonderheit hatte ich beim Umbau
übersehen, weshalb sie jetzt ausdrücklich festgehalten ist — samt Tabelle
aller Zustände.
- Ergänzt: Deaktivierung braucht den shared_key, mit Beispiel für den .NET-
Client und curl. Verhalten bei Ratenbegrenzung.
UpdateService-Anleitung
- Prüf-Endpunkte dokumentiert (check, latest, releases) samt Antwortformat.
- Tabelle zum Versionsvergleich mit den Fällen, die vorher falsch liefen.
- Auto-Resolve beim Veröffentlichen beschrieben.
Agent-Prompt-Vorlage
- Fehler-Schnittstelle ergänzt, inklusive Hinweis auf "ignored": true, damit
ein Agent bekannte Fehler nicht untersucht.
Alle in der Dokumentation genannten API-Pfade und Scopes wurden maschinell
gegen Routing und TokenManager abgeglichen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rücksprung auf das Dashboard
Nach jedem Speichern landete man auf der Standard-Unteransicht des Moduls
statt dort, wo man gearbeitet hatte. Drei Ursachen:
- Die Handler setzten nur den Modul-Anker (#tab-watchdog). Damit blieb die
Unteransicht unbestimmt und es wurde die erste angezeigt.
- Die Wiederherstellung aus dem Anker leitete den Modulnamen aus dem Namen der
Unteransicht ab (sub-<modul>-<rest>). Für "sub-update-releases" ergab das
"update" statt "updateservice".
- Der zugehörige Navigationsknopf wurde über seinen Beschriftungstext gesucht,
der den Modulnamen nie enthält. Der Rückweg schlug also immer fehl.
Jetzt teilt das Formular beim Absenden mit, welche Unteransicht offen war
(_return), und der Server leitet dorthin zurück. Umgesetzt als zentraler
submit-Zuhörer, damit künftige Formulare das Verhalten erben. Die Zuordnung
Unteransicht -> Modul kommt aus derselben Liste, die auch die Navigation
aufbaut, und die Knöpfe tragen eine eindeutige Kennung statt Text.
Zusätzlich wird die Ansicht jetzt auch bei Vor- und Zurück-Navigation im
Browser wiederhergestellt.
Lizenzmodul
Vier Unteransichten für eine Aufgabe sind auf zwei sinnvolle reduziert:
- "Dashboard" zeigte drei Zahlen; sie stehen jetzt über der Liste.
- "Lizenz Bearbeiten & HW" ist entfallen. Zum Bearbeiten musste man die gerade
angeklickte Lizenz in einer Auswahlliste erneut suchen. Jetzt öffnet sich ein
Dialog direkt über der Tabelle, die Hardware-Liste steht daneben.
- "Settings" enthielt zwei fest verdrahtete URLs und ist in "Integration"
aufgegangen, zusammen mit einem Beispielaufruf.
- Der dort angezeigte "Ed25519 Public Key" war frei erfunden und wurde
entfernt; die Offline-Vorschau nennt jetzt das tatsächlich verwendete
HMAC-SHA256 und weist darauf hin, dass nur der Download signiert ist.
Nebenbei
Der Inhalt des Projekte-Moduls lag direkt im Modul-Container, ohne
subtab-content. Der Navigationseintrag "Projekte Übersicht" zeigte damit ins
Leere. Jetzt einheitlich wie alle anderen Module.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Seite brach beim Aufbau ab und lieferte im Browser
"switchMainTab is not defined": das Rendering endete vor dem Script-Block,
sodass keine einzige Funktion definiert wurde.
Ursache: e() war als ?string deklariert. PHP wandelt numerische Array-Schlüssel
automatisch in int um, sodass die Zeitraum-Auswahl im Fehler-Stream mit den
Schlüsseln 0/1/24/168/720 einen int übergab. Unter declare(strict_types=1) warf
das einen TypeError mitten in der Ausgabe.
Die enge Signatur war generell unpassend: mit ATTR_STRINGIFY_FETCHES = false
liefert PDO numerische Spalten als int, und genau diese Werte werden über e()
ausgegeben. Die Funktion nimmt jetzt jeden skalaren Typ entgegen.
Gefunden hat den Fehler die Selbstmeldung: die unbehandelte Exception landete
als Item im eigenen Bugtracker, inklusive Datei und Zeilennummer.
Die übrigen typisierten Helfer (dc_dt, dc_ago, getMonitorIconUrl, btUrl,
errUrl) wurden gegengeprüft; sie erhalten ausschließlich Strings bzw. Arrays.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fehler-Schnittstelle
- Neuer schlanker Eingang POST /api/errors/v1/report für den globalen
Exception-Handler einer Anwendung. Titel und Dringlichkeit leitet der Server
ab; gespeichert wird in derselben Tabelle wie der Bugtracker. Ein zweiter
Speicher wäre nur ein zweiter Ort, an dem man suchen müsste.
- error_level (fatal/error/warning) trennt die technische Art des Ereignisses
von der geschäftlichen Dringlichkeit. Ein Duplicate-Entry ist technisch ein
error, geschäftlich belanglos — beides zu vermischen war der Grund, warum
solche Meldungen als Bug im Dashboard landeten.
Ignore-Regeln gegen bekanntes Rauschen
- bugtracker_ignore_rules mit contains/regex/exception_class, Pflichtfeld für
die Begründung und optionaler Alarmschwelle.
- Ein Treffer bedeutet nicht "wegwerfen": Der Fehler wird weiterhin erfasst und
hochgezählt, bleibt aber aus der Übersicht heraus und löst keine
Benachrichtigung aus. Der Zähler ist der eigentliche Zweck — dass ein
bekannter Fehler auftritt, ist normal; dass er plötzlich hundertmal so oft
auftritt, ist ein Signal. Dafür das rollende Stundenfenster und
error.rate_exceeded.
- Neue Regeln lassen sich rückwirkend auf bestehende Einträge anwenden.
Gruppierung überarbeitet
- Der Schlüssel nahm bisher 300 Zeichen Stacktrace auf. Derselbe Fehler
zersplitterte dadurch, sobald ein Aufrufer den Stack einmal mitschickte und
einmal nicht. Jetzt zählt der Ursprungsort: bevorzugt die Dateiangabe, sonst
der erste Rahmen des Stacktrace.
- Die Normalisierung ersetzte nur Zahlen ab vier Stellen, wodurch
'AA-1' und 'BB-2' getrennt blieben. Werte in Anführungszeichen, die Ziffern
enthalten, gelten jetzt als veränderlich — der Schlüsselname bleibt erhalten,
sodass verschiedene Unique-Keys unterscheidbar sind. Mit 9 Testfällen belegt.
Metrik-Verlauf
- watchdog_metrics speichert numerische Heartbeat-Werte mit Zeitstempel.
Zuvor wurde metrics_json bei jedem Heartbeat überschrieben; damit ließ sich
"die Platte läuft seit drei Tagen voll" nicht erkennen, nur "sie ist voll".
- GET /api/watchdog/v1/metrics liefert den verdichteten Verlauf und die
Abweichung vom eigenen Sieben-Tage-Durchschnitt. Dieser relative Ansatz
braucht keine projektspezifischen Schwellwerte.
- Aufbewahrung 14 Tage, Bereinigung stündlich durch den Evaluator.
Health-Checks per Push statt Abruf
- Der Heartbeat nimmt ein checks-Objekt entgegen, das die Anwendung selbst
ermittelt. Das Deploymentcenter interpretiert die Namen nicht, es liest nur
ok und message — was "gesund" bedeutet, entscheidet jede Anwendung selbst.
Schlägt eine Prüfung fehl, wird ein als ok gemeldeter Heartbeat auf warning
herabgestuft.
- Bewusst ausgehend: auf den Zielmaschinen müssen keine Ports geöffnet werden.
Abhängigkeitsbewusste Alarmierung
- Fällt ein Monitor aus, dessen Parent selbst unten ist, wird der Alarm
unterdrückt. Der Zustand bleibt sichtbar. Vorher erzeugte ein ausgefallener
Hypervisor mit zwölf VMs dreizehn Meldungen für ein Problem.
- Mehrere Ebenen und fehlerhafte Hierarchien (Zyklen, gelöschte Parents) sind
abgesichert; mit 10 Testfällen belegt.
WebUI
- Neue Ansicht "Fehler-Stream" mit Filtern nach Projekt, Fehlerklasse,
Umgebung, Zeitraum und Sichtbarkeit sowie Volltextsuche und Pagination.
Stummgeschaltete Einträge sind standardmäßig ausgeblendet.
- Verwaltung der Ignore-Regeln inklusive Trefferzähler.
- Die Detailansicht zeigt Fehlerklasse, Stummschaltungsgrund und die Häufung
im laufenden Stundenfenster.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regression aus dem vorigen Commit
- /api/license/v1/validate lieferte die Antwort im neuen status/error-Umschlag.
Der Vertrag dieses Endpunkts ist aber bereits ausgerollt: das Feld "status"
auf oberster Ebene trägt den Lizenzzustand (valid, revoked, expired ...).
LicenseClient las dadurch "success" statt "valid" — jeder ausgelieferte
Client hätte seine Lizenz für ungültig gehalten. Die Lizenz-Endpunkte
antworten jetzt wieder ohne Umschlag (Http::raw).
Gefunden durch Ausführen der projekteigenen Test-Suite gegen den Server.
Packager
- FTP-Zugangsdaten standen als Standardwerte im Quelltext und zusätzlich in
packager.config.json und in der Integrationsanleitung. Alle drei Fundstellen
bereinigt; die Konfigurationsdatei ist nicht mehr versioniert. Zugangsdaten
kommen aus Datei, Umgebungsvariablen oder CLI-Argument, sonst bricht das
Programm mit einer klaren Meldung ab.
- Das Veröffentlichen sendet jetzt ein Token (updateservice:publish) und nutzt
den Endpunkt /api/updateservice/v1/publish.
- Fehler wurden von einem leeren catch verschluckt, und ohne Erfolgsfall wurde
gar nichts ausgegeben. Das Werkzeug meldete am Ende immer Erfolg und lieferte
Rückgabewert 0, selbst wenn FTP-Upload und API-Aufruf fehlgeschlagen waren.
Jetzt ehrliche Meldungen und Rückgabewerte 0/1/2.
- packager.config.json wurde vom csproj nie ins Ausgabeverzeichnis kopiert,
weshalb sie dort nie gefunden wurde und stets die hartkodierten Werte griffen.
UpdateClient
- IsVersionNewer entfernte die Vorabkennung, aber kein führendes "v". Damit
scheiterte Version.TryParse bei "v1.4.2" und es wurde auf einen
alphabetischen Vergleich zurückgefallen, in dem "v1.9.0" als neuer gilt als
"v1.10.0" — derselbe Fehler wie zuvor serverseitig im SQL. Ersetzt durch
einen vollständigen semantischen Vergleich, verifiziert mit 16 Testfällen.
- Der Rückfall auf die API lag in einem catch-Block, aber GetAsync wirft bei
einem 404 keine Exception. Fehlte die statische latest.json, brach die
Prüfung ab, statt die API zu befragen.
Dokumentation
- BUGTRACKER_INTEGRATION_GUIDE.md beschrieb denselben Workflow ein zweites Mal
und war bereits auseinandergelaufen: Aufrufe ohne Token, alte Pfade, weder
Claim/Lease noch Idempotenz. Ersetzt durch einen Verweis auf das gepflegte
Agenten-Handbuch samt Übersicht der Änderungen.
- UPDATESERVICE_INTEGRATION_GUIDE.md um Token, Umgebungsvariablen und
Rückgabewerte ergänzt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sicherheit
- install_db.php war ohne Authentifizierung erreichbar und setzte bei jedem
Aufruf das Admin-Passwort auf einen fest im Code stehenden Wert zurück.
Jetzt Auth-Pflicht; ein Konto wird nur bei leerer Benutzertabelle angelegt.
- Stored XSS im Bugtracker-Detail-Modal: Titel, Beschreibung, Fehlermeldung,
Stacktrace und Kommentare gingen ungefiltert durch innerHTML.
- report.php, projects.php und das Veröffentlichen von Releases verlangen jetzt
zwingend ein Token. Publish war zuvor völlig ungeschützt.
- CSRF-Token in allen Formularen, Session-Regenerierung nach Login,
Drosselung fehlgeschlagener Anmeldeversuche.
- Zugangsdaten aus der Versionskontrolle entfernt (Serverdaten.txt,
config.php, .htpasswd, deploy_config.json). Historie enthält sie weiterhin,
Rotation erforderlich (siehe docs/UPGRADE.md).
- Token-Validierung nur noch über SHA-256-Hash; expires_at wird ausgewertet.
Behobene 500er
- Audit::log() war in index.php weder eingebunden noch importiert. Jeder
Klick auf "Aktivierung freigeben" endete in einem Fatal Error.
- Derselbe benannte PDO-Platzhalter mehrfach je Statement (:id in
revokeToken/deleteToken, :q siebenfach in der Volltextsuche). Bei
EMULATE_PREPARES=false ist das nicht zulässig und warf HY093.
- Migration 005 nutzte dynamisches SQL, dessen Semikolons in String-Literalen
vom alten explode(';')-Installer als Statement-Ende gelesen wurden. Sie
schlug still fehl, wodurch push_id/target_agent/tags dauerhaft fehlten.
- Monitor-Umbenennung ohne Transaktion, verschachtelte Transaktionen im
RateLimiter.
Funktionale Korrekturen
- Der Watchdog-Evaluator fehlte vollständig: Monitor-Zustände änderten sich nur
beim Eintreffen eines Heartbeats, ein ausgefallenes System blieb dauerhaft
"up". Erster Lauf auf dem Produktivsystem: 7 von 10 Monitoren waren
tatsächlich seit über einem Tag nicht erreichbar.
- Das Feld "os" fehlte im Monitor-Dialog, wurde aber gespeichert und löschte
damit bei jedem Speichern das Betriebssystem.
- Der Resolve-Dialog existierte im HTML nicht; der Button war funktionslos.
- Versionsvergleich erfolgte lexikografisch, wodurch 1.9.0 als neuer galt
als 1.10.0.
- Schreiboperationen meldeten Erfolg auch für nicht existierende IDs.
- Post/Redirect/Get gegen doppelte Einträge beim Neuladen.
Neue Struktur
- src/bootstrap.php mit PSR-4-Autoloader ersetzt die require-Ketten.
- Core: Config, Http, Csrf, ApiAuth, Logger, Migrator, ErrorReporter.
- Migrator mit zeichenweisem SQL-Parser, dc_migrations und Baseline-Verfahren,
damit bestehende Installationen keine Beispieldaten zurückbekommen.
Agenten-Workflow
- Claim/Lease: Items werden exklusiv übernommen, damit nicht zwei Agenten am
selben Problem arbeiten. action=next holt und reserviert in einem Zug.
- Idempotenz über client_ref, Deduplizierung auch für Feature Requests,
Erkennung von Regressionen, automatische Eskalation des Schweregrads.
- Strukturierter Code-Kontext (repo_url, commit_sha, file_path, line_no).
- Delta-Abfragen über updated_since, Pagination, Bulk-Update.
- Beim Veröffentlichen eines Releases schließen sich Items mit passendem
resolved_in_build selbst.
- Ausgehende Webhooks mit HMAC-Signatur, /api/health, /api/openapi.json.
- Unbehandelte Fehler meldet die Plattform in ihren eigenen Bugtracker.
WebUI
- Serverseitige Filterung mit Pagination statt Rendern aller Datensätze.
- Migrations-Schranke, Evaluator-Warnung, Übersicht aktiver Agenten.
Zeitstempel liegen in der Datenbank durchgängig in UTC und werden für die
Anzeige in die App-Zeitzone umgerechnet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>