nx_netscope_heal.nx source
↩ module page · 157 lines · 6857 B
1// nx_netscope_heal.nx -- NX-NETSCOPE rung 3: goal-directed SELF-HEALING
2// connectivity agent (DECISION CORE). The north-star payload: a device
3// given a goal ("reach nishifamily.com") auto-selects the BEST reachable
4// resolver/path, switches/fails over, and re-verifies -- without flapping.
5//
6// This is the PURE, DETERMINISTIC decision core (no I/O), isolated so it
7// is KAT-able offline (fault-inject a synthetic probe matrix -> assert the
8// chosen resolver + heal action). The live MAPE-K loop (monitor tick,
9// EXECUTE set-resolver, backoff+jitter sleep, circuit breaker) wraps this
10// next increment. Design grounded (research workflow wf_ca778575) in:
11// - Happy Eyeballs v2/v3 (RFC 8305/8306): RACE candidates, pick best --
12// but "best" = lowest RTT among CORRECT answers (a fast WRONG answer,
13// NXDOMAIN/SERVFAIL/timeout, never wins over a slower correct one).
14// - MAPE-K autonomic loop (monitor->analyze->plan->execute->verify).
15// - Hysteresis / RTT-banding (RFC 2439 RFD spirit): don't switch for a
16// marginal gain -> no flapping.
17//
18// Reuses nxv_cell + ProbeCell + NXNS_* from the shipped probe/verdict
19// layers; composes nx_dns_probe_matrix_parallel results (no new deps).
20//
21// license_tier: ORIGINAL
22
23import "nx_netscope_verdict.nx"
24
25// ---- heal actions + reasons (sealed) ----
26const HEAL_KEEP: i64 = 0
27const HEAL_SWITCH: i64 = 1
28const HEAL_GIVEUP: i64 = 2
29
30const HEAL_R_HEALTHY: i64 = 0 // incumbent still best -> no change
31const HEAL_R_INCUMBENT_FAILED: i64 = 1 // incumbent no longer correct -> fail over
32const HEAL_R_BETTER_FOUND: i64 = 2 // a meaningfully faster correct resolver
33const HEAL_R_NO_CANDIDATE: i64 = 3 // nothing correct reachable -> give up safely
34
35// hysteresis band: only switch for a *better* incumbent if the gain
36// exceeds this (anti-flap). Config key heal.rtt_band_ms (CLAUDE.md ยง11).
37const HEAL_BAND_MS: i64 = 20
38
39// HealDecision: 5 i64 = 40 bytes.
40struct HealDecision {
41 action: i64, // HEAL_KEEP / SWITCH / GIVEUP
42 target_idx: i64, // chosen resolver index (into servers[]), -1 if give-up
43 target_ip: i64, // the resolved answer ip (BE low32), 0 if none
44 target_rtt: i64, // chosen resolver's latency_ms
45 reason: i64, // HEAL_R_*
46}
47const HEAL_DECISION_BYTES: i64 = 40
48
49// A cell is ELIGIBLE iff it returned a CORRECT answer: OK_A, and -- for an
50// owned name that must live on the LAN -- a private (RFC1918) address (a
51// public answer to an owned name is a hairpin = wrong path, demoted).
52func heal_cell_eligible(cell: *ProbeCell, require_private: i64) -> i64 {
53 if cell.verdict != NXNS_OK_A { return 0 }
54 if require_private == 1 { if cell.is_rfc1918 != 1 { return 0 } }
55 return 1
56}
57
58// heal_select_best: the Happy-Eyeballs correctness-gated race winner --
59// lowest RTT among ELIGIBLE (correct) resolvers. Deterministic: on an RTT
60// tie the lowest index wins (strict `<`). Returns server index or -1.
61func heal_select_best(cells: *ProbeCell, ns: i64, nh: i64, col: i64, require_private: i64) -> i64 {
62 var best: i64 = 0 - 1
63 var best_rtt: i64 = 0
64 var s: i64 = 0
65 while s < ns {
66 let cell: *ProbeCell = nxv_cell(cells, nh, s, col)
67 if heal_cell_eligible(cell, require_private) == 1 {
68 if best == (0 - 1) {
69 best = s
70 best_rtt = cell.latency_ms
71 } else {
72 if cell.latency_ms < best_rtt { best = s; best_rtt = cell.latency_ms }
73 }
74 }
75 s = s + 1
76 }
77 return best
78}
79
80// heal_decide: the PLAN step. Given the frozen probe matrix, the current
81// incumbent resolver, and the goal's privacy requirement, decide KEEP /
82// SWITCH / GIVEUP with a reason. Pure + deterministic -> KAT-able.
83func heal_decide(cells: *ProbeCell, ns: i64, nh: i64, col: i64,
84 require_private: i64, incumbent_idx: i64, band_ms: i64,
85 out: *HealDecision) -> i64 {
86 out.action = HEAL_GIVEUP
87 out.target_idx = 0 - 1
88 out.target_ip = 0
89 out.target_rtt = 0
90 out.reason = HEAL_R_NO_CANDIDATE
91
92 let best: i64 = heal_select_best(cells, ns, nh, col, require_private)
93 if best == (0 - 1) { return HEAL_GIVEUP } // nothing correct -> safe give-up
94 let bcell: *ProbeCell = nxv_cell(cells, nh, best, col)
95
96 // is the incumbent still a CORRECT answer?
97 var inc_ok: i64 = 0
98 if incumbent_idx >= 0 {
99 if incumbent_idx < ns {
100 let icell: *ProbeCell = nxv_cell(cells, nh, incumbent_idx, col)
101 if heal_cell_eligible(icell, require_private) == 1 { inc_ok = 1 }
102 }
103 }
104
105 if inc_ok == 0 {
106 // incumbent failed (or none set) -> fail over to the best correct one
107 out.action = HEAL_SWITCH
108 out.target_idx = best
109 out.target_ip = bcell.ipv4_packed
110 out.target_rtt = bcell.latency_ms
111 out.reason = HEAL_R_INCUMBENT_FAILED
112 return HEAL_SWITCH
113 }
114
115 // incumbent still correct: only switch for a MEANINGFUL gain (hysteresis).
116 let icell2: *ProbeCell = nxv_cell(cells, nh, incumbent_idx, col)
117 if best != incumbent_idx {
118 if (icell2.latency_ms - bcell.latency_ms) > band_ms {
119 out.action = HEAL_SWITCH
120 out.target_idx = best
121 out.target_ip = bcell.ipv4_packed
122 out.target_rtt = bcell.latency_ms
123 out.reason = HEAL_R_BETTER_FOUND
124 return HEAL_SWITCH
125 }
126 }
127 out.action = HEAL_KEEP
128 out.target_idx = incumbent_idx
129 out.target_ip = icell2.ipv4_packed
130 out.target_rtt = icell2.latency_ms
131 out.reason = HEAL_R_HEALTHY
132 return HEAL_KEEP
133}
134
135// heal_render: easy-for-anyone plain-language heal ACTION line.
136func heal_render(out: *HealDecision, servers: *i64, host_name: *u8, host_len: i64) -> i64 {
137 nxv_w("NX-NETSCOPE . SELF-HEAL ", 26); sys_write(1, host_name, host_len); nxv_w("\n", 1)
138 if out.action == HEAL_KEEP {
139 nxv_w(" [OK] HEALTHY -- using ", 24); nxv_ip(servers[out.target_idx])
140 nxv_w(" (", 2); nxv_dec(out.target_rtt); nxv_w("ms), no change needed.\n", 23)
141 return 0
142 }
143 if out.action == HEAL_SWITCH {
144 if out.reason == HEAL_R_INCUMBENT_FAILED {
145 nxv_w(" [!!] HEALED -- your main resolver was failing; switched to ", 60)
146 nxv_ip(servers[out.target_idx]); nxv_w(" -> ", 4); nxv_ip(out.target_ip)
147 nxv_w(" (", 2); nxv_dec(out.target_rtt); nxv_w("ms). Connection restored.\n", 26)
148 return 0
149 }
150 nxv_w(" [OK] OPTIMIZED -- found a faster correct resolver ", 51)
151 nxv_ip(servers[out.target_idx]); nxv_w(" (", 2); nxv_dec(out.target_rtt); nxv_w("ms).\n", 5)
152 return 0
153 }
154 nxv_w(" [XX] CANNOT HEAL -- no resolver returns a correct answer.\n", 59)
155 nxv_w(" FIX: check this device's link, or add a working resolver.\n", 64)
156 return 0
157}