Leite Diagnosezustand im Statuspfad ab
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
852bba6264
commit
4ba3b4ddef
5 changed files with 61 additions and 0 deletions
|
|
@ -199,6 +199,7 @@ Ergaenzt seit dem ersten Geruest:
|
||||||
- Backend ergaenzt den Read-Pfad um `received_at` und eine einfache `stale`-Ableitung
|
- Backend ergaenzt den Read-Pfad um `received_at` und eine einfache `stale`-Ableitung
|
||||||
- Backend bietet zusaetzlich eine kleine Uebersicht aller zuletzt meldenden Screens
|
- Backend bietet zusaetzlich eine kleine Uebersicht aller zuletzt meldenden Screens
|
||||||
- Backend validiert den Statuspfad jetzt enger auf erlaubte Lifecycle-/Connectivity-Werte und leitet `stale` aus dem gemeldeten Intervall ab
|
- Backend validiert den Statuspfad jetzt enger auf erlaubte Lifecycle-/Connectivity-Werte und leitet `stale` aus dem gemeldeten Intervall ab
|
||||||
|
- Backend leitet im Read-Pfad zusaetzlich ein kompaktes `derived_state` fuer Diagnosekonsumenten ab
|
||||||
- dateibasierte Agent-Konfiguration zusaetzlich zu Env-Overrides
|
- dateibasierte Agent-Konfiguration zusaetzlich zu Env-Overrides
|
||||||
- strukturierte Agent-Logs mit internem Health-Snapshot und signalgesteuertem Shutdown
|
- strukturierte Agent-Logs mit internem Health-Snapshot und signalgesteuertem Shutdown
|
||||||
- erster periodischer HTTP-Status-Reporter im Agent
|
- erster periodischer HTTP-Status-Reporter im Agent
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,17 @@ Zusaetzlich fuegt das Backend im Read-Pfad derzeit hinzu:
|
||||||
|
|
||||||
- `received_at` als serverseitigen Annahmezeitpunkt des letzten gueltigen Reports
|
- `received_at` als serverseitigen Annahmezeitpunkt des letzten gueltigen Reports
|
||||||
- `stale` als einfache serverseitige Einordnung, ob der letzte Report bereits veraltet wirkt
|
- `stale` als einfache serverseitige Einordnung, ob der letzte Report bereits veraltet wirkt
|
||||||
|
- `derived_state` als zusammengefasste Diagnoseeinschaetzung fuer Konsumenten des Read-Pfads
|
||||||
|
|
||||||
`stale` ist aktuell bewusst nur eine kleine Diagnosehilfe fuer die Entwicklungsstufe und noch kein vollstaendiges Online-/Offline-Modell fuer spaetere Admin-Oberflaechen.
|
`stale` ist aktuell bewusst nur eine kleine Diagnosehilfe fuer die Entwicklungsstufe und noch kein vollstaendiges Online-/Offline-Modell fuer spaetere Admin-Oberflaechen.
|
||||||
Die Schwelle wird derzeit einfach aus dem gemeldeten `heartbeat_every_seconds` abgeleitet: mehr als zwei Intervalle ohne neuen Report gelten als veraltet.
|
Die Schwelle wird derzeit einfach aus dem gemeldeten `heartbeat_every_seconds` abgeleitet: mehr als zwei Intervalle ohne neuen Report gelten als veraltet.
|
||||||
|
|
||||||
|
`derived_state` wird aktuell bewusst einfach abgeleitet:
|
||||||
|
|
||||||
|
- `offline` bei `stale = true` oder `server_connectivity = offline`
|
||||||
|
- `degraded` bei `server_connectivity = degraded|unknown` oder wenn `status` nicht `running` ist
|
||||||
|
- `online` in den verbleibenden Faellen
|
||||||
|
|
||||||
## Abgrenzung
|
## Abgrenzung
|
||||||
|
|
||||||
Noch nicht Teil dieser Stufe:
|
Noch nicht Teil dieser Stufe:
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ func handleGetLatestPlayerStatus(store playerStatusStore) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
record.Stale = isStale(record, store.Now())
|
record.Stale = isStale(record, store.Now())
|
||||||
|
record.DerivedState = deriveState(record)
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, record)
|
writeJSON(w, http.StatusOK, record)
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +134,7 @@ func handleListLatestPlayerStatuses(store playerStatusStore) http.HandlerFunc {
|
||||||
filtered := make([]playerStatusRecord, 0, len(records))
|
filtered := make([]playerStatusRecord, 0, len(records))
|
||||||
for i := range records {
|
for i := range records {
|
||||||
records[i].Stale = isStale(records[i], store.Now())
|
records[i].Stale = isStale(records[i], store.Now())
|
||||||
|
records[i].DerivedState = deriveState(records[i])
|
||||||
if wantConnectivity != "" && records[i].ServerConnectivity != wantConnectivity {
|
if wantConnectivity != "" && records[i].ServerConnectivity != wantConnectivity {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -183,3 +185,15 @@ func staleThresholdFor(record playerStatusRecord) time.Duration {
|
||||||
|
|
||||||
return 2 * time.Minute
|
return 2 * time.Minute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deriveState(record playerStatusRecord) string {
|
||||||
|
if record.Stale || record.ServerConnectivity == "offline" {
|
||||||
|
return "offline"
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.ServerConnectivity == "degraded" || record.ServerConnectivity == "unknown" || record.Status != "running" {
|
||||||
|
return "degraded"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "online"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type playerStatusRecord struct {
|
||||||
ServerConnectivity string `json:"server_connectivity,omitempty"`
|
ServerConnectivity string `json:"server_connectivity,omitempty"`
|
||||||
ReceivedAt string `json:"received_at,omitempty"`
|
ReceivedAt string `json:"received_at,omitempty"`
|
||||||
Stale bool `json:"stale,omitempty"`
|
Stale bool `json:"stale,omitempty"`
|
||||||
|
DerivedState string `json:"derived_state,omitempty"`
|
||||||
ServerURL string `json:"server_url,omitempty"`
|
ServerURL string `json:"server_url,omitempty"`
|
||||||
MQTTBroker string `json:"mqtt_broker,omitempty"`
|
MQTTBroker string `json:"mqtt_broker,omitempty"`
|
||||||
HeartbeatEverySeconds int `json:"heartbeat_every_seconds,omitempty"`
|
HeartbeatEverySeconds int `json:"heartbeat_every_seconds,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,10 @@ func TestHandleGetLatestPlayerStatus(t *testing.T) {
|
||||||
if got, want := response.Stale, false; got != want {
|
if got, want := response.Stale, false; got != want {
|
||||||
t.Fatalf("response.Stale = %v, want %v", got, want)
|
t.Fatalf("response.Stale = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if got, want := response.DerivedState, "degraded"; got != want {
|
||||||
|
t.Fatalf("response.DerivedState = %q, want %q", got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleGetLatestPlayerStatusMarksStaleRecords(t *testing.T) {
|
func TestHandleGetLatestPlayerStatusMarksStaleRecords(t *testing.T) {
|
||||||
|
|
@ -329,6 +333,10 @@ func TestHandleGetLatestPlayerStatusMarksStaleRecords(t *testing.T) {
|
||||||
if got, want := response.Stale, true; got != want {
|
if got, want := response.Stale, true; got != want {
|
||||||
t.Fatalf("response.Stale = %v, want %v", got, want)
|
t.Fatalf("response.Stale = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if got, want := response.DerivedState, "offline"; got != want {
|
||||||
|
t.Fatalf("response.DerivedState = %q, want %q", got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleGetLatestPlayerStatusUsesHeartbeatIntervalForFreshness(t *testing.T) {
|
func TestHandleGetLatestPlayerStatusUsesHeartbeatIntervalForFreshness(t *testing.T) {
|
||||||
|
|
@ -361,6 +369,36 @@ func TestHandleGetLatestPlayerStatusUsesHeartbeatIntervalForFreshness(t *testing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleGetLatestPlayerStatusDerivesOnlineState(t *testing.T) {
|
||||||
|
store := newInMemoryPlayerStatusStore()
|
||||||
|
store.now = func() time.Time {
|
||||||
|
return time.Date(2026, 3, 22, 16, 0, 30, 0, time.UTC)
|
||||||
|
}
|
||||||
|
store.Save(playerStatusRecord{
|
||||||
|
ScreenID: "online-screen",
|
||||||
|
Timestamp: "2026-03-22T16:00:00Z",
|
||||||
|
Status: "running",
|
||||||
|
ServerConnectivity: "online",
|
||||||
|
ReceivedAt: "2026-03-22T16:00:00Z",
|
||||||
|
HeartbeatEverySeconds: 30,
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/online-screen/status", nil)
|
||||||
|
req.SetPathValue("screenId", "online-screen")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleGetLatestPlayerStatus(store)(w, req)
|
||||||
|
|
||||||
|
var response playerStatusRecord
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||||
|
t.Fatalf("Unmarshal() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := response.DerivedState, "online"; got != want {
|
||||||
|
t.Fatalf("response.DerivedState = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleGetLatestPlayerStatusNotFound(t *testing.T) {
|
func TestHandleGetLatestPlayerStatusNotFound(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/missing/status", nil)
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/missing/status", nil)
|
||||||
req.SetPathValue("screenId", "missing")
|
req.SetPathValue("screenId", "missing")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue