Transportiere Server-Connectivity im Statuspfad

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jesko Anschütz 2026-03-22 18:27:44 +01:00
parent a69135c0b9
commit 8f0f06ae25
8 changed files with 32 additions and 0 deletions

View file

@ -200,6 +200,7 @@ Ergaenzt seit dem ersten Geruest:
- 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
- Server-Connectivity-Zustand im Agent (`unknown`, `online`, `degraded`, `offline`) auf Basis der Report-Ergebnisse - Server-Connectivity-Zustand im Agent (`unknown`, `online`, `degraded`, `offline`) auf Basis der Report-Ergebnisse
- der HTTP-Statuspfad transportiert jetzt neben `status` auch `server_connectivity`
- lokales Compose-Grundgeruest fuer PostgreSQL und Mosquitto - lokales Compose-Grundgeruest fuer PostgreSQL und Mosquitto
## Arbeitsweise ## Arbeitsweise

View file

@ -26,6 +26,7 @@ Mindestens enthalten:
Aktuell zusaetzlich enthalten: Aktuell zusaetzlich enthalten:
- `server_connectivity`
- `server_url` - `server_url`
- `mqtt_broker` - `mqtt_broker`
- `heartbeat_every_seconds` - `heartbeat_every_seconds`
@ -39,6 +40,7 @@ Aktuell zusaetzlich enthalten:
"screen_id": "info01-dev", "screen_id": "info01-dev",
"ts": "2026-03-22T16:00:00Z", "ts": "2026-03-22T16:00:00Z",
"status": "running", "status": "running",
"server_connectivity": "online",
"server_url": "http://127.0.0.1:8080", "server_url": "http://127.0.0.1:8080",
"mqtt_broker": "tcp://127.0.0.1:1883", "mqtt_broker": "tcp://127.0.0.1:1883",
"heartbeat_every_seconds": 30, "heartbeat_every_seconds": 30,
@ -68,6 +70,8 @@ Zusätzlich zur Write-Route gibt es in dieser Stufe:
Dieser Endpunkt liefert den zuletzt akzeptierten Status fuer einen Screen zurueck. Dieser Endpunkt liefert den zuletzt akzeptierten Status fuer einen Screen zurueck.
Wenn fuer den Screen noch kein Status vorliegt, liefert das Backend `404` mit dem gemeinsamen Fehlerumschlag. Wenn fuer den Screen noch kein Status vorliegt, liefert das Backend `404` mit dem gemeinsamen Fehlerumschlag.
Der aktuell zurueckgelieferte Datensatz enthaelt damit sowohl den Lifecycle-Status (`status`) als auch den vom Agenten lokal abgeleiteten Reachability-Zustand (`server_connectivity`).
## Abgrenzung ## Abgrenzung
Noch nicht Teil dieser Stufe: Noch nicht Teil dieser Stufe:

View file

@ -176,6 +176,7 @@ func (a *App) reportStatus(ctx context.Context) {
err := a.reporter.Send(ctx, statusreporter.Snapshot{ err := a.reporter.Send(ctx, statusreporter.Snapshot{
Status: string(snapshot.Status), Status: string(snapshot.Status),
ServerConnectivity: string(snapshot.ServerConnectivity),
ScreenID: snapshot.ScreenID, ScreenID: snapshot.ScreenID,
ServerBaseURL: snapshot.ServerBaseURL, ServerBaseURL: snapshot.ServerBaseURL,
MQTTBroker: snapshot.MQTTBroker, MQTTBroker: snapshot.MQTTBroker,

View file

@ -12,6 +12,7 @@ import (
type Snapshot struct { type Snapshot struct {
Status string Status string
ServerConnectivity string
ScreenID string ScreenID string
ServerBaseURL string ServerBaseURL string
MQTTBroker string MQTTBroker string
@ -24,6 +25,7 @@ type statusPayload struct {
ScreenID string `json:"screen_id"` ScreenID string `json:"screen_id"`
Timestamp string `json:"ts"` Timestamp string `json:"ts"`
Status string `json:"status"` Status string `json:"status"`
ServerConnectivity string `json:"server_connectivity"`
ServerURL string `json:"server_url"` ServerURL string `json:"server_url"`
MQTTBroker string `json:"mqtt_broker"` MQTTBroker string `json:"mqtt_broker"`
HeartbeatEverySeconds int `json:"heartbeat_every_seconds"` HeartbeatEverySeconds int `json:"heartbeat_every_seconds"`
@ -88,6 +90,7 @@ func buildPayload(snapshot Snapshot, now time.Time) statusPayload {
ScreenID: snapshot.ScreenID, ScreenID: snapshot.ScreenID,
Timestamp: now.Format(time.RFC3339), Timestamp: now.Format(time.RFC3339),
Status: snapshot.Status, Status: snapshot.Status,
ServerConnectivity: snapshot.ServerConnectivity,
ServerURL: snapshot.ServerBaseURL, ServerURL: snapshot.ServerBaseURL,
MQTTBroker: snapshot.MQTTBroker, MQTTBroker: snapshot.MQTTBroker,
HeartbeatEverySeconds: snapshot.HeartbeatEverySeconds, HeartbeatEverySeconds: snapshot.HeartbeatEverySeconds,

View file

@ -14,6 +14,7 @@ func TestBuildPayloadFromSnapshot(t *testing.T) {
lastHeartbeatAt := time.Date(2026, 3, 22, 16, 0, 0, 0, time.UTC) lastHeartbeatAt := time.Date(2026, 3, 22, 16, 0, 0, 0, time.UTC)
snapshot := Snapshot{ snapshot := Snapshot{
Status: "running", Status: "running",
ServerConnectivity: "online",
ScreenID: "info01-dev", ScreenID: "info01-dev",
ServerBaseURL: "http://127.0.0.1:8080", ServerBaseURL: "http://127.0.0.1:8080",
MQTTBroker: "tcp://127.0.0.1:1883", MQTTBroker: "tcp://127.0.0.1:1883",
@ -36,6 +37,10 @@ func TestBuildPayloadFromSnapshot(t *testing.T) {
t.Fatalf("StartedAt = %q, want %q", got, want) t.Fatalf("StartedAt = %q, want %q", got, want)
} }
if got, want := payload.ServerConnectivity, "online"; got != want {
t.Fatalf("ServerConnectivity = %q, want %q", got, want)
}
if got, want := payload.LastHeartbeatAt, lastHeartbeatAt.Format(time.RFC3339); got != want { if got, want := payload.LastHeartbeatAt, lastHeartbeatAt.Format(time.RFC3339); got != want {
t.Fatalf("LastHeartbeatAt = %q, want %q", got, want) t.Fatalf("LastHeartbeatAt = %q, want %q", got, want)
} }
@ -67,6 +72,7 @@ func TestReporterSendStatus(t *testing.T) {
err := reporter.Send(context.Background(), Snapshot{ err := reporter.Send(context.Background(), Snapshot{
Status: "running", Status: "running",
ServerConnectivity: "online",
ScreenID: "info01-dev", ScreenID: "info01-dev",
ServerBaseURL: "http://127.0.0.1:8080", ServerBaseURL: "http://127.0.0.1:8080",
MQTTBroker: "tcp://127.0.0.1:1883", MQTTBroker: "tcp://127.0.0.1:1883",
@ -81,4 +87,8 @@ func TestReporterSendStatus(t *testing.T) {
if got, want := received.ScreenID, "info01-dev"; got != want { if got, want := received.ScreenID, "info01-dev"; got != want {
t.Fatalf("received.ScreenID = %q, want %q", got, want) t.Fatalf("received.ScreenID = %q, want %q", got, want)
} }
if got, want := received.ServerConnectivity, "online"; got != want {
t.Fatalf("received.ServerConnectivity = %q, want %q", got, want)
}
} }

View file

@ -10,6 +10,7 @@ type playerStatusRequest struct {
ScreenID string `json:"screen_id"` ScreenID string `json:"screen_id"`
Timestamp string `json:"ts"` Timestamp string `json:"ts"`
Status string `json:"status"` Status string `json:"status"`
ServerConnectivity string `json:"server_connectivity"`
ServerURL string `json:"server_url"` ServerURL string `json:"server_url"`
MQTTBroker string `json:"mqtt_broker"` MQTTBroker string `json:"mqtt_broker"`
HeartbeatEverySeconds int `json:"heartbeat_every_seconds"` HeartbeatEverySeconds int `json:"heartbeat_every_seconds"`
@ -59,6 +60,7 @@ func handlePlayerStatus(store playerStatusStore) http.HandlerFunc {
ScreenID: request.ScreenID, ScreenID: request.ScreenID,
Timestamp: request.Timestamp, Timestamp: request.Timestamp,
Status: request.Status, Status: request.Status,
ServerConnectivity: request.ServerConnectivity,
ServerURL: request.ServerURL, ServerURL: request.ServerURL,
MQTTBroker: request.MQTTBroker, MQTTBroker: request.MQTTBroker,
HeartbeatEverySeconds: request.HeartbeatEverySeconds, HeartbeatEverySeconds: request.HeartbeatEverySeconds,

View file

@ -6,6 +6,7 @@ type playerStatusRecord struct {
ScreenID string `json:"screen_id"` ScreenID string `json:"screen_id"`
Timestamp string `json:"ts"` Timestamp string `json:"ts"`
Status string `json:"status"` Status string `json:"status"`
ServerConnectivity string `json:"server_connectivity,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"`

View file

@ -14,6 +14,7 @@ func TestHandlePlayerStatusAccepted(t *testing.T) {
"screen_id": "info01-dev", "screen_id": "info01-dev",
"ts": "2026-03-22T16:00:00Z", "ts": "2026-03-22T16:00:00Z",
"status": "running", "status": "running",
"server_connectivity": "online",
"server_url": "http://127.0.0.1:8080", "server_url": "http://127.0.0.1:8080",
"mqtt_broker": "tcp://127.0.0.1:1883", "mqtt_broker": "tcp://127.0.0.1:1883",
"heartbeat_every_seconds": 30, "heartbeat_every_seconds": 30,
@ -50,6 +51,10 @@ func TestHandlePlayerStatusAccepted(t *testing.T) {
if got, want := stored.ScreenID, "info01-dev"; got != want { if got, want := stored.ScreenID, "info01-dev"; got != want {
t.Fatalf("stored.ScreenID = %q, want %q", got, want) t.Fatalf("stored.ScreenID = %q, want %q", got, want)
} }
if got, want := stored.ServerConnectivity, "online"; got != want {
t.Fatalf("stored.ServerConnectivity = %q, want %q", got, want)
}
} }
func TestHandlePlayerStatusRejectsInvalidJSON(t *testing.T) { func TestHandlePlayerStatusRejectsInvalidJSON(t *testing.T) {
@ -171,6 +176,7 @@ func TestHandleGetLatestPlayerStatus(t *testing.T) {
ScreenID: "info01-dev", ScreenID: "info01-dev",
Timestamp: "2026-03-22T16:00:00Z", Timestamp: "2026-03-22T16:00:00Z",
Status: "running", Status: "running",
ServerConnectivity: "degraded",
ServerURL: "http://127.0.0.1:8080", ServerURL: "http://127.0.0.1:8080",
MQTTBroker: "tcp://127.0.0.1:1883", MQTTBroker: "tcp://127.0.0.1:1883",
HeartbeatEverySeconds: 30, HeartbeatEverySeconds: 30,
@ -196,6 +202,10 @@ func TestHandleGetLatestPlayerStatus(t *testing.T) {
if got, want := response.ScreenID, "info01-dev"; got != want { if got, want := response.ScreenID, "info01-dev"; got != want {
t.Fatalf("response.ScreenID = %q, want %q", got, want) t.Fatalf("response.ScreenID = %q, want %q", got, want)
} }
if got, want := response.ServerConnectivity, "degraded"; got != want {
t.Fatalf("response.ServerConnectivity = %q, want %q", got, want)
}
} }
func TestHandleGetLatestPlayerStatusNotFound(t *testing.T) { func TestHandleGetLatestPlayerStatusNotFound(t *testing.T) {