v0.173.0 — R-77: endpoint-drift detection, samba protected-set gate, channel log honesty
Source: felhom.eu/documentation/audits/DIAG-agent-channel-2026-07-26.md bootstrap.DetectEndpointDrift names a controller.yaml vs bootstrap.json local_api.endpoint divergence -- one ERROR carrying BOTH values and BOTH paths, its own event type local_api_endpoint_drift, and its own Hungarian banner shown ABOVE the channel banner because drift is the cause and "agent unreachable" the symptom. It writes NOTHING: reconciling from bootstrap.json would clobber a correct controller.yaml on any half-provisioned or hand-repaired guest, so the authority ruling is deferred to R-78. Fail-safe silent on absent/unparseable/ incomplete bootstrap and on an empty endpoint (ensureLocalAPI's fill-if-missing path is untouched). Fingerprint compared as a BOOLEAN only; token never compared, logged or exposed. EffectiveProtected now gates samba on Enabled && UserSet, mirroring BOTH of reconcileSambaAt's early returns, and the doc comment is corrected in the same change -- it claimed "detection and deployment agree in both directions" while citing only !smb.Enabled, an assertion that went false when !smb.UserSet was added. Not over-suppressed: sharing on WITH a password and a dead container still alarms. Channel log: the debounce placeholder is stateUnconfirmed (rendered "unseeded") instead of "up", so a born-down channel no longer logs "up->down" and orUnseeded stops being dead code. Logging only -- the placeholder is still matched in the re-arm condition, so F2 born-down alerting is byte-for-byte unchanged and all nine pre-existing channelhealth tests pass. Tests 951 -> 959, all green. Red-proofs A (both directions), E and F. MinAgent unchanged; felhom-agent untouched.
This commit is contained in:
@@ -42,6 +42,10 @@ type AlertManager struct {
|
||||
// out-of-band, state-based, self-clearing model as agentChannelAlert: passing an empty slice when
|
||||
// every deployed app is running clears the banner with no manual dismissal.
|
||||
deadAppAlerts []Alert
|
||||
// endpointDriftAlert (R-77) is set/cleared at startup by the local_api drift check. Separate from
|
||||
// agentChannelAlert on purpose: drift is usually the CAUSE and "agent unreachable" the SYMPTOM,
|
||||
// and during the 2026-07-25 outage only the symptom was visible.
|
||||
endpointDriftAlert *Alert
|
||||
}
|
||||
|
||||
// NewAlertManager creates a new AlertManager.
|
||||
@@ -77,6 +81,28 @@ func (am *AlertManager) SetAgentChannelAlert(down bool, msg string) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetEndpointDriftAlert sets (drift=true) or clears the local_api endpoint-drift banner (R-77).
|
||||
//
|
||||
// It is deliberately a SEPARATE alert from SetAgentChannelAlert: during the 2026-07-25 outage the
|
||||
// generic "agent unreachable" banner was the ONLY signal, and it looked like a dead agent. The two
|
||||
// can also be true at once — a drifted endpoint usually CAUSES the channel to be down — so folding
|
||||
// them together would hide the actionable one behind the symptom.
|
||||
func (am *AlertManager) SetEndpointDriftAlert(drift bool, msg string) {
|
||||
am.mu.Lock()
|
||||
defer am.mu.Unlock()
|
||||
if !drift {
|
||||
am.endpointDriftAlert = nil
|
||||
return
|
||||
}
|
||||
am.endpointDriftAlert = &Alert{
|
||||
ID: "local-api-endpoint-drift",
|
||||
Level: "error",
|
||||
Message: msg,
|
||||
Link: "/settings",
|
||||
LinkText: "Beállítások",
|
||||
}
|
||||
}
|
||||
|
||||
// DeadApp is a deployed app the health loop found not-running (fix-3). State is the container-state
|
||||
// string for the display (e.g. "stopped"/"exited").
|
||||
type DeadApp struct {
|
||||
@@ -244,7 +270,7 @@ func (am *AlertManager) GetAlerts(excludeIDs ...string) []Alert {
|
||||
am.mu.RLock()
|
||||
defer am.mu.RUnlock()
|
||||
|
||||
if len(am.alerts) == 0 && am.agentChannelAlert == nil && len(am.deadAppAlerts) == 0 {
|
||||
if len(am.alerts) == 0 && am.agentChannelAlert == nil && am.endpointDriftAlert == nil && len(am.deadAppAlerts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -254,6 +280,12 @@ func (am *AlertManager) GetAlerts(excludeIDs ...string) []Alert {
|
||||
}
|
||||
|
||||
var result []Alert
|
||||
// Endpoint drift first: it is the actionable CAUSE, and the channel-down banner below is usually
|
||||
// just its symptom. Showing the symptom above the cause is what made the 2026-07-25 outage read
|
||||
// as an infrastructure blip for 17.5 h.
|
||||
if am.endpointDriftAlert != nil && !exclude[am.endpointDriftAlert.ID] {
|
||||
result = append(result, *am.endpointDriftAlert)
|
||||
}
|
||||
// Channel-down is prepended (highest priority — the agent link being dead breaks disk/storage UI).
|
||||
if am.agentChannelAlert != nil && !exclude[am.agentChannelAlert.ID] {
|
||||
result = append(result, *am.agentChannelAlert)
|
||||
|
||||
Reference in New Issue
Block a user