hub v0.159.0: a refused whole-guest backup has its own alarm (R-1150); a reinstall re-issues the claim code and the off-site login when the old host is stale or off (R-1154)
R-1150: the agent's localapi:backup-cadence-cap is a separate per-host state in HostCapabilityChecker: alone it raises whole_guest_backup_refused (warning, plain text, agent reason in details) and whole_guest_backup_refusal_cleared (info); other critical caps keep agent_capability_degraded/_recovered unchanged. New types operator-only and hub-origin only (not in allowedEventTypes: the suspected controller must not post them). agent_capability_degraded/_recovered join operatorOnlyEvents (the checker's "never the customer" claim was configuration only). R-1154: handleHostEnroll's reuse path re-issues (claim code + off-site login) when no controller wait is open and either deliberate_off_at is held or both the host report and the controller report are older than alerting.stale_threshold (wired from main); once per silence (host_reenroll_reissued record, also written at mint); a live box gets nothing re-issued and the skip is recorded (host_reenroll_not_reissued). 05 section 8 [DESIGN]. Tests red-proved; go build/vet/test green.
This commit is contained in:
1 parent
e4f022a0d9
commit
5121536f10
14 files changed
+995
-33
No files matched your search
@@ -880,6 +880,22 @@ var operatorOnlyEvents = map[string]bool{
|
||||
// definition: someone pushed a catalog change asking for container rights the box does not give. The household can
|
||||
// do nothing; its app page says the app keeps its last good definition. Registered in the same commit that mints it.
|
||||
"app_definition_refused": true,
|
||||
// R-1150 (v0.159.0). The agent refused a whole-guest backup a guest asked for beyond its schedule (the R-973
|
||||
// cadence cap): the guest's controller may be misbehaving or compromised — the operator's question, never the
|
||||
// household's. Hub-origin only (monitor.HostCapabilityChecker; deliberately NOT in api.allowedEventTypes — the
|
||||
// suspected controller must not be able to post or pre-empt it). Listed in the SAME commit that mints them.
|
||||
"whole_guest_backup_refused": true,
|
||||
"whole_guest_backup_refusal_cleared": true,
|
||||
// R-1150 (v0.159.0): the agent's privileged-capability pair. Its checker has said „Customer is NEVER notified"
|
||||
// since v0.44.0, and only configuration (enabled_events) made that true — the R-97c shape. Now structural.
|
||||
"agent_capability_degraded": true,
|
||||
"agent_capability_recovered": true,
|
||||
// R-1154 (v0.159.0): a host-enroll that reused the old host record — re-issued the clean-slate credentials, or
|
||||
// did NOT because the box is live. Info (recorded, never mailed), host ids and report ages: the operator's
|
||||
// record. Hub-origin only (api/reenroll.go; not in api.allowedEventTypes — a box must not plant the
|
||||
// once-per-silence record). Listed in the SAME commit that mints them.
|
||||
"host_reenroll_reissued": true,
|
||||
"host_reenroll_not_reissued": true,
|
||||
}
|
||||
|
||||
// IsOperatorOnly reports whether an event type is barred from customer dispatch. Exported so the
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package notify
|
||||
|
||||
import "testing"
|
||||
|
||||
// R-1150 (v0.159.0) — the refused whole-guest backup pair and the agent's capability pair never reach a
|
||||
// household, under the configuration that would break it: the customer HAS the type in enabled_events and
|
||||
// HAS an e-mail. The operator still gets the warning. (The api package pins the other register: none of
|
||||
// these is in allowedEventTypes, so no box can POST one.)
|
||||
//
|
||||
// COMPANION RED-PROOF (observed 2026-10-19): with the four lines removed from operatorOnlyEvents this fails
|
||||
// with
|
||||
//
|
||||
// r1150_operator_only_test.go: whole_guest_backup_refused: the household was mailed (customer@example.com) — enabled_events opted it in
|
||||
// r1150_operator_only_test.go: agent_capability_degraded: the household was mailed (customer@example.com) — enabled_events opted it in
|
||||
//
|
||||
// Restored.
|
||||
func TestR1150_RefusedBackupAndCapabilityEventsNeverReachTheCustomer(t *testing.T) {
|
||||
for _, et := range []string{"whole_guest_backup_refused", "agent_capability_degraded"} {
|
||||
st := opOnlyStore(t)
|
||||
if err := st.SaveNotificationPrefs("c1", "customer@example.com", []string{et}, 6); err != nil {
|
||||
t.Fatalf("SaveNotificationPrefs: %v", err)
|
||||
}
|
||||
rec := &sentTo{}
|
||||
d := opOnlyDispatcher(t, st, rec)
|
||||
d.ProcessEvent("c1", et, "warning", "Host c1-abc123: test", `{"host_id":"c1-abc123"}`, "hub")
|
||||
var customer, operator bool
|
||||
for _, to := range rec.to {
|
||||
customer = customer || to == "customer@example.com"
|
||||
operator = operator || to == "operator@felhom.eu"
|
||||
}
|
||||
if customer {
|
||||
t.Errorf("%s: the household was mailed (customer@example.com) — enabled_events opted it in", et)
|
||||
}
|
||||
if !operator {
|
||||
t.Errorf("%s: the operator was not mailed — the guard must be customer-only", et)
|
||||
}
|
||||
}
|
||||
for _, et := range []string{"whole_guest_backup_refusal_cleared", "agent_capability_recovered"} {
|
||||
if !operatorOnlyEvents[et] {
|
||||
t.Errorf("%s must be operator-only (the pair's other edge)", et)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user