nx_health_check.nx source
↩ module page · 125 lines · 4093 B
1// nx_health_check.nx -- overall substrate health verdict.
2//
3// Per [[feedback-self-surfacing-intelligence-staged-autonomy]]:
4// substrate emits a coarse GREEN / YELLOW / RED verdict aggregating
5// all known indicators. Operator sees one color first; can drill
6// into the dashboard for details.
7//
8// Verdict rules (Q10 alert_score from nx_audit_dashboard):
9// GREEN : alert_score < 200 AND colony_status == HEALTHY
10// YELLOW : alert_score 200-768 OR colony_status == ALERTED
11// RED : alert_score > 768 OR colony_status >= RESPONDING
12//
13// Composes:
14// nx_audit_dashboard -- the snapshot consumed for verdict
15// nx_recovery -- RED verdict triggers recovery procedure
16// nx_book -- RED entry written when transitioning into RED
17
18import "nx_syscalls.nx"
19import "nx_tier.nx"
20import "nx_audit_dashboard.nx"
21
22// ===== Sealed enum: NxHealthVerdict ===============================
23
24const NX_HC_GREEN: nx_int = 0
25const NX_HC_YELLOW: nx_int = 1
26const NX_HC_RED: nx_int = 2
27const NX_HC_N_VERDICTS: nx_int = 3
28
29func nx_hc_verdict_is_valid(v: nx_int) -> nx_int {
30 if v < 0 { return 0 }
31 if v >= NX_HC_N_VERDICTS { return 0 }
32 return 1
33}
34
35// ===== Struct: NxHealthState ======================================
36//
37// Persistent state tracking transitions across health-check calls.
38// Used to detect "transitioned into RED" events (vs already RED) so
39// the substrate writes one book entry per transition, not on every
40// poll.
41
42struct NxHealthState {
43 last_verdict: nx_int,
44 last_check_us: nx_size,
45 consecutive_red: nx_int,
46 transition_count: nx_int,
47}
48
49func nx_health_state_new() -> *NxHealthState {
50 let s: *NxHealthState = (sys_mmap(32)) as *NxHealthState
51 s.last_verdict = NX_HC_GREEN
52 s.last_check_us = 0
53 s.consecutive_red = 0
54 s.transition_count = 0
55 return s
56}
57
58// ===== nx_health_check ============================================
59//
60// Compute the verdict from a dashboard snapshot. Update state
61// counters. Returns the verdict.
62
63func nx_health_check(state: *NxHealthState,
64 dash: *NxAuditDashboardSnapshot,
65 now_us: nx_size) -> nx_int {
66 state.last_check_us = now_us
67 let score: nx_int = nx_audit_dashboard_alert_score_q10(dash)
68
69 var v: nx_int = NX_HC_GREEN
70 if dash.colony_status >= 1 { v = NX_HC_YELLOW } // ALERTED
71 if score >= 200 {
72 if v == NX_HC_GREEN { v = NX_HC_YELLOW }
73 }
74 if dash.colony_status >= 2 { v = NX_HC_RED } // RESPONDING+
75 if score > 768 { v = NX_HC_RED }
76
77 if v != state.last_verdict {
78 state.transition_count = state.transition_count + 1
79 }
80 if v == NX_HC_RED {
81 state.consecutive_red = state.consecutive_red + 1
82 } else {
83 state.consecutive_red = 0
84 }
85 state.last_verdict = v
86 return v
87}
88
89// ===== nx_health_just_transitioned_to ============================
90//
91// Predicate: did the LAST nx_health_check transition INTO this
92// verdict? Used by callers to fire one-shot side effects (write
93// book entry, trigger recovery, alert operator).
94
95func nx_health_just_transitioned_to(state: *NxHealthState,
96 verdict: nx_int) -> nx_int {
97 if state.last_verdict == verdict {
98 if state.transition_count > 0 { return 1 }
99 }
100 return 0
101}
102
103func nx_health_last_verdict(state: *NxHealthState) -> nx_int {
104 return state.last_verdict
105}
106
107func nx_health_consecutive_red(state: *NxHealthState) -> nx_int {
108 return state.consecutive_red
109}
110
111// ===== nx_health_should_trigger_recovery ==========================
112//
113// Returns 1 if recovery procedure should fire. Rules:
114// - last_verdict == RED AND consecutive_red >= 2 (sustained, not flap)
115// OR
116// - just_transitioned_to(RED) for first time
117
118func nx_health_should_trigger_recovery(state: *NxHealthState) -> nx_int {
119 if state.last_verdict != NX_HC_RED { return 0 }
120 if state.consecutive_red >= 2 { return 1 }
121 if state.transition_count > 0 {
122 if state.consecutive_red == 1 { return 1 }
123 }
124 return 0
125}