code wiki / (root) / nx_netscope_heal_loop.nx

nx_netscope_heal_loop.nx source

↩ module page · 122 lines · 5463 B

1// nx_netscope_heal_loop.nx -- NX-NETSCOPE rung 3, part 2: the live MAPE-K 2// loop's SAFETY BRAIN. Pure, deterministic, sovereign (raw NishiLang, no 3// deps) -> KAT-able offline. These are the "first do no harm" guards that 4// turn the decision core (nx_netscope_heal.nx) into a self-healing loop 5// that converges instead of thrashing. Concepts (re-implemented, no code 6// copied): AWS "Exponential Backoff and Jitter" (Full Jitter), the 7// circuit breaker (Nygard/Fowler), and a token-bucket retry budget 8// (metastable-failure avoidance). The remaining thin I/O wrapper (real 9// sys_clock_nanosleep sleep + set-resolver) is the deployment step. 10// 11// license_tier: ORIGINAL 12 13import "nx_netscope_heal.nx" 14const HLOOP_MAGIC_2654435761: i64 = 2654435761 15const HLOOP_MAGIC_1103515245: i64 = 1103515245 16const HLOOP_MAGIC_12345: i64 = 12345 17 18// ---- loop step outcomes (sealed) ---- 19const HLOOP_STABLE: i64 = 0 // healthy -> no action, resume monitoring 20const HLOOP_ACT: i64 = 1 // execute the switch now 21const HLOOP_WAIT: i64 = 2 // back off (out_wait ms) then retry 22const HLOOP_GIVEUP: i64 = 3 // stop safely (no candidate / budget spent) 23 24// ---- circuit-breaker states ---- 25const HCB_CLOSED: i64 = 0 26const HCB_OPEN: i64 = 1 27const HCB_HALFOPEN: i64 = 2 28 29struct HealCircuit { state: i64, fails: i64, opened_ms: i64 } // 24 B 30const HEAL_CIRCUIT_BYTES: i64 = 24 31struct HealBudget { tokens: i64, last_refill_ms: i64 } // 16 B 32const HEAL_BUDGET_BYTES: i64 = 16 33 34// ---- deterministic jitter (sovereign LCG mix; seedable for KATs) ---- 35// Anti-synchronization only (not crypto): a fleet must not retry in 36// lockstep. Returns a value in [0, range). 37func heal_jitter(seed: i64, attempt: i64, range: i64) -> i64 { 38 if range <= 0 { return 0 } 39 var h: i64 = (seed + attempt * HLOOP_MAGIC_2654435761) * HLOOP_MAGIC_1103515245 + HLOOP_MAGIC_12345 40 h = h & 0x7fffffffffffffff // force non-negative 41 return h % range 42} 43 44// Full Jitter backoff: sleep in [0, min(cap_ms, base_ms * 2^attempt)]. 45// The exponential CEILING grows then clamps to cap; the actual wait is a 46// uniform sample below it (AWS Full Jitter -- best anti-storm behavior). 47func heal_backoff_ms(base_ms: i64, cap_ms: i64, attempt: i64, seed: i64) -> i64 { 48 var ceil: i64 = base_ms 49 var a: i64 = 0 50 while a < attempt { 51 ceil = ceil * 2 52 if ceil >= cap_ms { ceil = cap_ms; a = attempt } else { a = a + 1 } 53 } 54 if ceil > cap_ms { ceil = cap_ms } 55 if ceil < 0 { ceil = cap_ms } // overflow guard 56 return heal_jitter(seed, attempt, ceil + 1) 57} 58 59// ---- circuit breaker (per candidate) ---- 60func heal_cb_init(cb: *HealCircuit) -> i64 { 61 cb.state = HCB_CLOSED; cb.fails = 0; cb.opened_ms = 0 62 return 0 63} 64// allow a trial now? CLOSED=yes; OPEN=no until open_ms elapsed (then 65// HALF-OPEN allows ONE trial); HALF-OPEN=yes (the trial). 66func heal_cb_allow(cb: *HealCircuit, now_ms: i64, open_ms: i64) -> i64 { 67 if cb.state == HCB_CLOSED { return 1 } 68 if cb.state == HCB_OPEN { 69 if (now_ms - cb.opened_ms) >= open_ms { cb.state = HCB_HALFOPEN; return 1 } 70 return 0 71 } 72 return 1 // HALF-OPEN: permit the single probe 73} 74func heal_cb_on_fail(cb: *HealCircuit, now_ms: i64, threshold: i64) -> i64 { 75 if cb.state == HCB_HALFOPEN { cb.state = HCB_OPEN; cb.opened_ms = now_ms; return 0 } 76 cb.fails = cb.fails + 1 77 if cb.fails >= threshold { cb.state = HCB_OPEN; cb.opened_ms = now_ms } 78 return 0 79} 80func heal_cb_on_success(cb: *HealCircuit) -> i64 { 81 cb.state = HCB_CLOSED; cb.fails = 0 82 return 0 83} 84 85// ---- retry budget (token bucket: bounds total remediation churn) ---- 86func heal_budget_init(b: *HealBudget, tokens: i64) -> i64 { 87 b.tokens = tokens; b.last_refill_ms = 0 88 return 0 89} 90// refill one token per refill_ms (cap max_tokens), then try to spend one. 91func heal_budget_try(b: *HealBudget, now_ms: i64, refill_ms: i64, max_tokens: i64) -> i64 { 92 if (now_ms - b.last_refill_ms) >= refill_ms { 93 if b.tokens < max_tokens { b.tokens = b.tokens + 1 } 94 b.last_refill_ms = now_ms 95 } 96 if b.tokens > 0 { b.tokens = b.tokens - 1; return 1 } 97 return 0 98} 99 100// ---- the composed MAPE-K step ---- 101// Given the decision-core action + the candidate's circuit state + the 102// retry budget + the attempt count, decide the loop's next move and the 103// backoff wait. This is the PLAN/EXECUTE gate -- pure, so the whole loop 104// is KAT-able by scripting the inputs. 105func heal_loop_next(decision_action: i64, cb: *HealCircuit, budget: *HealBudget, 106 attempt: i64, now_ms: i64, 107 base_ms: i64, cap_ms: i64, cb_open_ms: i64, 108 refill_ms: i64, max_tokens: i64, seed: i64, 109 out_wait: *i64) -> i64 { 110 *out_wait = 0 111 if decision_action == HEAL_KEEP { return HLOOP_STABLE } // healthy 112 if decision_action == HEAL_GIVEUP { return HLOOP_GIVEUP } // nothing correct reachable 113 // decision == HEAL_SWITCH: we WANT to act. 114 if heal_cb_allow(cb, now_ms, cb_open_ms) == 0 { 115 *out_wait = heal_backoff_ms(base_ms, cap_ms, attempt, seed) // candidate cooling down 116 return HLOOP_WAIT 117 } 118 if heal_budget_try(budget, now_ms, refill_ms, max_tokens) == 0 { 119 return HLOOP_GIVEUP // remediation budget spent -> stop, don't thrash 120 } 121 return HLOOP_ACT // safe to switch now 122}