c393d8529a
The dead-app check (source of app_start_failed and app_stopped_unhealthy) now gates on a crash-aware
boot grace (internal/crashboot): 15 min when the host crash guard's last boot was UNCLEAN and within
30 min of the controller start, otherwise 90 s. The fact is read from the agent's local API
(GET /host/crash-guard, agentapi.Client.CrashGuard). UNKNOWN - no agent, an older agent's 404, no
crash-guard state - is a normal boot. The decision is logged once ("boot grace ...: ... (R-856)").
NEEDS AN AGENT CHANGE to take effect: GET /host/crash-guard serving the guard's state.json fields
(present, last_boot_at, last_boot_unclean, tripped). Until then every box keeps 90 s.
Tests: TestR856_CrashBootHoldsTheMailsForTheLongGrace, TestR856_NormalBootKeeps90s,
TestR856_FactReadLateInTheNormalGraceStillCounts, TestR856_AgentProbeReadsTheCrashGuardState,
TestR856_CrashGuardDecodesAndAnOlderAgentIs404, TestR856_DeadAppCheckWaitsOnTheCrashAwareGrace,
TestR856_NormalGraceIsTheDeadAppBootGrace.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
34 lines
1.2 KiB
Go
34 lines
1.2 KiB
Go
package agentapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// CrashGuardState mirrors the agent's GET /host/crash-guard (R-856, `09` §3 decision 143): what the
|
|
// host's crash guard (`felhom-crash-guard`, `11` §5.9) recorded about the most recent HOST boot. The
|
|
// agent reads /var/lib/felhom-crash-guard/state.json (0644) and passes these fields through.
|
|
//
|
|
// Present=false: the host has no crash guard or no state yet. An agent that predates the route answers
|
|
// 404 (a *StatusError) — both mean UNKNOWN, and the controller then keeps its normal boot grace.
|
|
type CrashGuardState struct {
|
|
Present bool `json:"present"`
|
|
LastBootAt string `json:"last_boot_at,omitempty"` // RFC3339 UTC ("2006-01-02T15:04:05Z")
|
|
LastBootUnclean bool `json:"last_boot_unclean"`
|
|
Tripped bool `json:"tripped"`
|
|
}
|
|
|
|
// CrashGuard calls GET /host/crash-guard.
|
|
func (c *Client) CrashGuard(ctx context.Context) (CrashGuardState, error) {
|
|
var out CrashGuardState
|
|
body, err := c.get(ctx, "/host/crash-guard")
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if err := json.Unmarshal(body, &out); err != nil {
|
|
return out, fmt.Errorf("agentapi: decode /host/crash-guard: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|