nx_iot_watchdog_test.nx source
↩ module page · 147 lines · 7662 B
1// nx_iot_watchdog_test.nx -- gate for the per-device link healer.
2//
3// Proves the operator's blink loop is closed automatically + MEASURED:
4// - sealed-enum validity (health / observation / action)
5// - NEVER-BRICK: the whole action alphabet is brick-safe + writes no
6// firmware; the classifier is FAIL-SAFE (out-of-range -> RED)
7// - init: default threshold / override / null guard
8// - per-transition coverage of every (health x observation) edge
9// - THE FULL BLINK LOOP end-to-end: HEALTHY -> (misses) UNREACHABLE ->
10// PAIRING -> REPROVISION -> READOPTED -> HEALTHY, with recover_count
11// incrementing once per auto-heal (the manual re-pairs spared) and
12// brick-safety asserted at every emitted action
13// - a SECOND cycle increments the measured count again
14// - self-recovery (UNREACHABLE -> SEEN) revives WITHOUT counting as a
15// re-provision (honest: it came back on its own)
16// - defensive: malformed observation is a no-op; null does not crash
17//
18// expect_exit: 0
19//
20// license_tier: ORIGINAL
21
22import "nx_syscalls_x86_64.nx"
23import "nx_iot_watchdog.nx"
24
25func main() -> i64 {
26 // ---- sealed-enum validity --------------------------------------
27 if nx_iot_wd_state_is_valid(WD_ST_HEALTHY) != 1 { return 1 }
28 if nx_iot_wd_state_is_valid(WD_ST_RECOVERING) != 1 { return 2 }
29 if nx_iot_wd_state_is_valid(WD_ST_N) != 0 { return 3 }
30 if nx_iot_wd_state_is_valid(-1) != 0 { return 4 }
31 if nx_iot_wd_obs_is_valid(WD_OBS_PAIRING) != 1 { return 5 }
32 if nx_iot_wd_obs_is_valid(WD_OBS_N) != 0 { return 6 }
33 if nx_iot_wd_action_is_valid(WD_ACT_REPROVISION) != 1 { return 7 }
34 if nx_iot_wd_action_is_valid(WD_ACT_N) != 0 { return 8 }
35
36 // ---- NEVER-BRICK: whole action alphabet ------------------------
37 var a: i64 = 0
38 while a < WD_ACT_N {
39 if nx_iot_wd_action_is_brick_safe(a) != 1 { return 10 }
40 if nx_iot_wd_action_writes_firmware(a) != 0 { return 11 }
41 a = a + 1
42 }
43 if nx_iot_wd_action_writes_firmware(WD_ACT_N) != 1 { return 12 } // fail-safe RED
44 if nx_iot_wd_action_is_brick_safe(WD_ACT_N) != 0 { return 13 }
45 if nx_iot_wd_action_is_brick_safe(-1) != 0 { return 14 }
46
47 // ---- init ------------------------------------------------------
48 let w: *IotWatchdog = sys_mmap(64) as *IotWatchdog
49 if nx_iot_wd_init(w, 7, 0, 100) != 0 { return 20 }
50 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 21 }
51 if nx_iot_wd_logical_id(w) != 7 { return 22 }
52 if nx_iot_wd_consec_misses(w) != 0 { return 23 }
53 if nx_iot_wd_recover_count(w) != 0 { return 24 }
54 if nx_iot_wd_tick_count(w) != 0 { return 25 }
55 if w.miss_threshold != NX_IOT_WD_MISS_THRESHOLD { return 26 }
56 if nx_iot_wd_init(w, 7, 5, 100) != 0 { return 27 }
57 if w.miss_threshold != 5 { return 28 }
58 if nx_iot_wd_init(0 as *IotWatchdog, 7, 0, 0) != -1 { return 29 }
59
60 // ---- HEALTHY + SEEN stays healthy; last_seen updates -----------
61 if nx_iot_wd_init(w, 1, 3, 100) != 0 { return 30 }
62 if nx_iot_wd_observe(w, WD_OBS_SEEN, 200) != WD_ACT_NONE { return 31 }
63 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 32 }
64 if w.last_seen_ms != 200 { return 33 }
65 if nx_iot_wd_tick_count(w) != 1 { return 34 }
66
67 // ---- HEALTHY + MISS -> MISSED (probe) --------------------------
68 if nx_iot_wd_observe(w, WD_OBS_MISS, 300) != WD_ACT_PROBE { return 35 }
69 if nx_iot_wd_state(w) != WD_ST_MISSED { return 36 }
70 if nx_iot_wd_consec_misses(w) != 1 { return 37 }
71
72 // ---- MISSED + SEEN -> HEALTHY (transient recovered) ------------
73 if nx_iot_wd_observe(w, WD_OBS_SEEN, 400) != WD_ACT_NONE { return 38 }
74 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 39 }
75 if nx_iot_wd_consec_misses(w) != 0 { return 40 }
76
77 // ---- accumulate misses to the threshold -> UNREACHABLE ---------
78 if nx_iot_wd_init(w, 1, 3, 100) != 0 { return 50 }
79 if nx_iot_wd_observe(w, WD_OBS_MISS, 110) != WD_ACT_PROBE { return 51 } // miss 1
80 if nx_iot_wd_observe(w, WD_OBS_MISS, 120) != WD_ACT_PROBE { return 52 } // miss 2
81 if nx_iot_wd_consec_misses(w) != 2 { return 53 }
82 let m3: i64 = nx_iot_wd_observe(w, WD_OBS_MISS, 130) // miss 3 == threshold
83 if m3 != WD_ACT_MARK_UNREACHABLE { return 54 }
84 if nx_iot_wd_state(w) != WD_ST_UNREACHABLE { return 55 }
85 if nx_iot_wd_is_down(w) != 1 { return 56 }
86 if nx_iot_wd_action_is_brick_safe(m3) != 1 { return 57 }
87
88 // ---- THE FULL BLINK LOOP (end to end, the headline) ------------
89 // HEALTHY -> 3 misses -> UNREACHABLE -> PAIRING(blink) ->
90 // REPROVISION -> READOPTED -> HEALTHY, recover_count = 1.
91 if nx_iot_wd_init(w, 42, 3, 1000) != 0 { return 60 }
92 nx_iot_wd_observe(w, WD_OBS_MISS, 1010)
93 nx_iot_wd_observe(w, WD_OBS_MISS, 1020)
94 let b3: i64 = nx_iot_wd_observe(w, WD_OBS_MISS, 1030)
95 if b3 != WD_ACT_MARK_UNREACHABLE { return 61 }
96 if nx_iot_wd_state(w) != WD_ST_UNREACHABLE { return 62 }
97 // the blink: the fixture reverted to open pairing mode
98 let bp: i64 = nx_iot_wd_observe(w, WD_OBS_PAIRING, 1040)
99 if bp != WD_ACT_REPROVISION { return 63 }
100 if nx_iot_wd_state(w) != WD_ST_RECOVERING { return 64 }
101 if nx_iot_wd_action_is_brick_safe(bp) != 1 { return 65 }
102 // provision_softap re-provisioned + the anchor re-adopted it
103 let br: i64 = nx_iot_wd_observe(w, WD_OBS_READOPTED, 1050)
104 if br != WD_ACT_MARK_CURRENT { return 66 }
105 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 67 }
106 if nx_iot_wd_is_healthy(w) != 1 { return 68 }
107 if nx_iot_wd_recover_count(w) != 1 { return 69 } // ONE manual re-pair spared
108 if w.last_seen_ms != 1050 { return 70 }
109
110 // ---- a SECOND blink cycle increments the measured count --------
111 nx_iot_wd_observe(w, WD_OBS_MISS, 2010)
112 nx_iot_wd_observe(w, WD_OBS_MISS, 2020)
113 nx_iot_wd_observe(w, WD_OBS_MISS, 2030) // -> UNREACHABLE
114 nx_iot_wd_observe(w, WD_OBS_PAIRING, 2040) // -> RECOVERING
115 let c2: i64 = nx_iot_wd_observe(w, WD_OBS_SEEN, 2050) // back on LAN -> HEALED
116 if c2 != WD_ACT_MARK_CURRENT { return 71 }
117 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 72 }
118 if nx_iot_wd_recover_count(w) != 2 { return 73 } // measured exceed grows
119
120 // ---- self-recovery does NOT count as a re-provision ------------
121 // UNREACHABLE -> SEEN (came back on its own): revive, but it was not
122 // a manual-re-pair scenario, so recover_count is unchanged.
123 if nx_iot_wd_init(w, 9, 2, 100) != 0 { return 80 }
124 nx_iot_wd_observe(w, WD_OBS_MISS, 110)
125 nx_iot_wd_observe(w, WD_OBS_MISS, 120) // threshold 2 -> UNREACHABLE
126 if nx_iot_wd_state(w) != WD_ST_UNREACHABLE { return 81 }
127 let s1: i64 = nx_iot_wd_observe(w, WD_OBS_SEEN, 130)
128 if s1 != WD_ACT_MARK_CURRENT { return 82 }
129 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 83 }
130 if nx_iot_wd_recover_count(w) != 0 { return 84 } // NOT counted -- honest
131
132 // ---- HEALTHY -> PAIRING directly (sudden revert) ---------------
133 if nx_iot_wd_init(w, 9, 3, 100) != 0 { return 90 }
134 let h1: i64 = nx_iot_wd_observe(w, WD_OBS_PAIRING, 110)
135 if h1 != WD_ACT_REPROVISION { return 91 }
136 if nx_iot_wd_state(w) != WD_ST_RECOVERING { return 92 }
137
138 // ---- defensive: malformed obs no-op; null does not crash -------
139 if nx_iot_wd_init(w, 9, 3, 100) != 0 { return 95 }
140 let d1: i64 = nx_iot_wd_observe(w, 999, 110)
141 if d1 != WD_ACT_NONE { return 96 }
142 if nx_iot_wd_state(w) != WD_ST_HEALTHY { return 97 }
143 if nx_iot_wd_tick_count(w) != 0 { return 98 } // invalid obs not counted
144 if nx_iot_wd_observe(0 as *IotWatchdog, WD_OBS_SEEN, 0) != WD_ACT_NONE { return 99 }
145
146 return 0
147}