code wiki / (root) / nx_source_health.nx

nx_source_health.nx source

↩ module page · 186 lines · 9153 B

1// nx_source_health.nx -- SOURCE HEALTH, THE DECISION + THE PERSISTED VERDICT (/compare/mediaingest R1; contract 2// symbol sh_preflight). LIB: pure classification + a tiny per-host verdict table. The heavy I/O probe (fetch + 3// bot-wall classify) lives in the thin program nx_source_health_probe so THIS lib's import closure stays small 4// (nx_syscalls only) and the gate can prove every branch with planted numbers -- the same lib/program split that 5// keeps a gate honest and dodges the nx_cc module-limit that a fetch+antibot closure would hit here. 6// 7// WHY: Harvestr fast-fails a null-routed CDN (its April-2026 Coomer route-around) before queueing work against it; 8// ours had nx_site_liveness for OUR sites only. This is the general per-host pre-flight: probe once, PERSIST the 9// verdict, and let every capture read it so a dead host is skipped WITHOUT re-probing on every asset. 10// 11// FIVE STATES (a host that cannot be classified must not read as UP -- abstain, never flatter): 12// SH_UP the host answered (any 2xx/3xx, or a 4xx/5xx where the HOST is alive but the path is bad) 13// SH_SLOW it answered but slower than the slow bar -- usable, flagged 14// SH_DEAD connect failed / timed out -- null-routed or down; skip and say so 15// SH_WALLED it answered with a bot-wall (nx_antibot verdict) -- classified, never solved 16// SH_UNKNOWN never probed / stale -- the third state, so "no evidence" is not "healthy" 17// license_tier: ORIGINAL 18import "nx_syscalls.nx" 19 20const SH_UP: i64 = 0 21const SH_SLOW: i64 = 1 22const SH_DEAD: i64 = 2 23const SH_WALLED: i64 = 3 24const SH_UNKNOWN: i64 = 4 25 26// slow bar: a host taking longer than this to answer its root is SLOW. DATA (conf overrides); named for purpose. 27const SH_DEFAULT_SLOW_MS: i64 = 8000 28// recheck horizon: a verdict older than this is UNKNOWN again (a dead host may recover; a wall may lift). 30 min. 29const SH_DEFAULT_TTL_MS: i64 = 1800000 30const SH_SLOW_CONF: *u8 = "knowledge/status/source_health_slow_ms.conf" 31const SH_TABLE: *u8 = "knowledge/status/source_health.tbl" 32 33// nx_antibot verdict codes (mirrored so the caller can pass abt_classify's result straight in; CLEAN=0 is not a wall) 34const SH_ABT_CLEAN: i64 = 0 35 36// ---- PURE POLICY (deterministic, gate-tested; no I/O) ---- 37// connect_ok: 1 the TCP+TLS connect succeeded, 0 it failed. status: the HTTP status (0 if no response). elapsed_ms: 38// wall time to first byte. wall_code: nx_antibot's abt_classify result (0=clean, >0 a wall class). slow_ms: the bar. 39func sh_decide(connect_ok: i64, status: i64, elapsed_ms: i64, wall_code: i64, slow_ms: i64) -> i64 { 40 if connect_ok == 0 { return SH_DEAD } // no connection at all -> null-routed / down 41 if wall_code != SH_ABT_CLEAN { return SH_WALLED } // the host answered but with a wall 42 if status <= 0 { return SH_DEAD } // connected, then no HTTP response -> treat as dead (fail-closed) 43 if elapsed_ms > slow_ms { return SH_SLOW } 44 return SH_UP 45} 46 47func sh_verdict_str(v: i64) -> *u8 { 48 if v == SH_UP { return "UP" as *u8 } 49 if v == SH_SLOW { return "SLOW" as *u8 } 50 if v == SH_DEAD { return "DEAD" as *u8 } 51 if v == SH_WALLED { return "WALLED" as *u8 } 52 return "UNKNOWN" as *u8 53} 54 55// should the capture path SKIP a host with this verdict? DEAD and WALLED are skip; UP/SLOW proceed; UNKNOWN proceeds 56// (probe it by trying -- an unprobed host is not assumed dead). A skip is the fast-fail Harvestr's pre-flight gives. 57func sh_should_skip(v: i64) -> i64 { 58 if v == SH_DEAD { return 1 } 59 if v == SH_WALLED { return 1 } 60 return 0 61} 62 63// ---- the configured slow bar (conf override, named default) ---- 64func sh_atoi(s: *u8, n: i64) -> i64 { 65 var v: i64 = 0; var i: i64 = 0; var any: i64 = 0 66 while i < n { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); any = 1 } } i = i + 1 } 67 if any == 0 { return 0 - 1 } 68 return v 69} 70func sh_slow_ms() -> i64 { 71 let szp: *i64 = sys_mmap(16) as *i64 72 szp[0] = 0 73 let buf: *u8 = sys_read_file(SH_SLOW_CONF, szp) 74 if (buf as i64) == 0 { return SH_DEFAULT_SLOW_MS } 75 if szp[0] <= 0 { return SH_DEFAULT_SLOW_MS } 76 var cap: i64 = szp[0]; if cap > 32 { cap = 32 } 77 let v: i64 = sh_atoi(buf, cap) 78 if v <= 0 { return SH_DEFAULT_SLOW_MS } 79 return v 80} 81 82// ---- the persisted per-host verdict table (flat, like the pace table; one 32-byte record per host) ---- 83// record: host_hash(8) | verdict(8) | checked_ms(8) | reserved(8). Open-addressed, SH_SLOTS slots. 84const SH_SLOTS: i64 = 4096 85const SH_REC: i64 = 32 86const SH_TBL_BYTES: i64 = 131072 // SH_SLOTS * SH_REC 87const SH_OFF_HASH: i64 = 0 88const SH_OFF_VERDICT: i64 = 8 89const SH_OFF_CHECKED: i64 = 16 90const SH_MAGIC_5381: i64 = 5381 91 92func sh_hash(h: *u8, n: i64) -> i64 { 93 var x: i64 = SH_MAGIC_5381; var i: i64 = 0 94 while i < n { x = (((x << 5) + x) + (h[i] as i64)) & 0x7fffffffffffffff; i = i + 1 } 95 if x == 0 { x = 1 } 96 return x 97} 98func sh_ld(tbl: *u8, off: i64) -> i64 { var v: i64=0; var i: i64=0; while i<8 { v = v | ((tbl[off+i] as i64) << (i*8)); i=i+1 } return v } 99func sh_st(tbl: *u8, off: i64, val: i64) -> i64 { var i: i64=0; while i<8 { tbl[off+i] = ((val >> (i*8)) & 0xff) as u8; i=i+1 } return 0 } 100 101func sh_load_tbl_into(dst: *u8) -> i64 { 102 var z: i64 = 0 103 while z < SH_TBL_BYTES { dst[z] = 0 as u8; z = z + 1 } 104 let lp: *i64 = sys_mmap(16) as *i64 105 let src: *u8 = sys_read_file(SH_TABLE, lp) 106 if (src as i64) != 0 { if lp[0] >= SH_TBL_BYTES { 107 var j: i64 = 0; while j < SH_TBL_BYTES { dst[j] = src[j]; j = j + 1 } 108 } } 109 return 0 110} 111func sh_load_tbl() -> *u8 { let d: *u8 = sys_mmap(SH_TBL_BYTES); sh_load_tbl_into(d); return d } 112func sh_save_tbl(tbl: *u8) -> i64 { 113 let fd: i64 = sys_openat_wr(SH_TABLE, 0x1a4) 114 if fd < 0 { return 0 - 1 } 115 var w: i64 = 0 116 while w < SH_TBL_BYTES { let k: i64 = sys_write(fd, (tbl as i64 + w) as *u8, SH_TBL_BYTES - w); if k <= 0 { sys_close(fd); return 0 - 1 } w = w + k } 117 sys_close(fd); return 0 118} 119// find the slot for hh (existing or first empty), linear probe. On a full table evict the OLDEST checked_ms so a 120// write always lands (the same fail-open lesson R15 taught the pace table). -1 only if tbl is null. 121func sh_slot(tbl: *u8, hh: i64) -> i64 { 122 if (tbl as i64) == 0 { return 0 - 1 } 123 var s: i64 = hh % SH_SLOTS 124 var tries: i64 = 0 125 while tries < SH_SLOTS { 126 let stored: i64 = sh_ld(tbl, s * SH_REC) 127 if stored == 0 { return s } 128 if stored == hh { return s } 129 s = (s + 1) % SH_SLOTS 130 tries = tries + 1 131 } 132 // full -> evict oldest checked_ms 133 var victim: i64 = 0 134 var best: i64 = sh_ld(tbl, SH_OFF_CHECKED) 135 var i: i64 = 1 136 while i < SH_SLOTS { let ck: i64 = sh_ld(tbl, i*SH_REC + SH_OFF_CHECKED); if ck < best { best = ck; victim = i } i = i + 1 } 137 var f: i64 = 0; while f < SH_REC { tbl[victim*SH_REC + f] = 0 as u8; f = f + 1 } 138 return victim 139} 140 141// persist a verdict for a host (checked_ms = now). Loads, writes, saves. Returns 0 ok. 142func sh_record(host: *u8, host_len: i64, verdict: i64, now_ms: i64) -> i64 { 143 let tbl: *u8 = sh_load_tbl() 144 let hh: i64 = sh_hash(host, host_len) 145 let s: i64 = sh_slot(tbl, hh) 146 if s < 0 { return 0 - 1 } 147 let off: i64 = s * SH_REC 148 sh_st(tbl, off + SH_OFF_HASH, hh) 149 sh_st(tbl, off + SH_OFF_VERDICT, verdict) 150 sh_st(tbl, off + SH_OFF_CHECKED, now_ms) 151 sh_save_tbl(tbl) 152 return 0 153} 154 155// THE PRE-FLIGHT ENTRY POINT (contract symbol). A caller PROBES the host (the fetch I/O lives in the thin program 156// nx_source_health_probe, kept out of this lib so its closure stays small) and hands the raw results here; this 157// classifies (sh_decide) AND persists (sh_record) in one call and returns the verdict. So the decision and the 158// record can never disagree, and the whole thing is gate-testable with planted numbers -- no network in the lib. 159// connect_ok: 1/0 did TCP+TLS connect | status: HTTP status (0 if none) | elapsed_ms: time to first byte 160// wall_code: nx_antibot abt_classify result (0=clean) | now_ms: sys_now_realtime_ms() 161func sh_preflight(host: *u8, host_len: i64, connect_ok: i64, status: i64, elapsed_ms: i64, wall_code: i64, now_ms: i64) -> i64 { 162 let v: i64 = sh_decide(connect_ok, status, elapsed_ms, wall_code, sh_slow_ms()) 163 sh_record(host, host_len, v, now_ms) 164 return v 165} 166 167// read the persisted verdict for a host; SH_UNKNOWN if absent or older than the TTL (stale -> re-probe). 168func sh_lookup(host: *u8, host_len: i64, now_ms: i64) -> i64 { 169 let tbl: *u8 = sh_load_tbl() 170 let hh: i64 = sh_hash(host, host_len) 171 var s: i64 = hh % SH_SLOTS 172 var tries: i64 = 0 173 while tries < SH_SLOTS { 174 let stored: i64 = sh_ld(tbl, s * SH_REC) 175 if stored == 0 { return SH_UNKNOWN } 176 if stored == hh { 177 let off: i64 = s * SH_REC 178 let checked: i64 = sh_ld(tbl, off + SH_OFF_CHECKED) 179 if now_ms - checked > SH_DEFAULT_TTL_MS { return SH_UNKNOWN } // stale -> re-probe 180 return sh_ld(tbl, off + SH_OFF_VERDICT) 181 } 182 s = (s + 1) % SH_SLOTS 183 tries = tries + 1 184 } 185 return SH_UNKNOWN 186}