code wiki / _hdl_build / nx_swarm_discover.nx

nx_swarm_discover.nx source

↩ module page · 222 lines · 10420 B

1// nx_swarm_discover.nx -- SF-DISCOVER: find GPU inference capacity on the LAN and register it. 2// 3// WHY. The swarm's endpoint SSOT (knowledge/swarm_nodes.conf) is HAND-MAINTAINED, and that is how 4// the estate lost its GPU for weeks: DHCP moved the laptop .193 -> .192 and five hardcoded copies 5// went stale in the same instant while every swarm gate still read GREEN. Binding the five copies to 6// one row fixed the DUPLICATION; it did not fix the fact that a human still has to notice and type. 7// ★★★★★A REGISTRY THAT ONLY A HUMAN CAN UPDATE GOES STALE AT EXACTLY THE MOMENT IT MATTERS -- when 8// hardware moves, which is the same moment nobody is watching. 9// 10// MEASURED 2026-08-04, and it is the reason this exists: a sweep of every live host on 192.168.8.0/24 11// found EXACTLY ONE inference endpoint (the laptop 5080 at .192:7861). The "west dual 3090" row in 12// the SSOT pointed at 10.0.4.13 -- a different, unreachable subnet -- so the estate believed it had 13// 48GB of VRAM it could not touch. ★A CONFIG ROW IS A CLAIM ABOUT THE WORLD, AND AN UNVERIFIED ONE 14// IS JUST A WISH WITH AN IP ADDRESS. 15// 16// WHAT IT DOES: probe a host range for real inference endpoints, VERIFY each by asking it to 17// identify itself, and report capacity. With `apply`, APPEND newly-found endpoints to the SSOT. 18// ⛔IT NEVER DELETES OR REWRITES AN EXISTING ROW. A discovery pass that can silently drop a node is 19// a worse failure than a stale row: the stale row fails loudly at dispatch, a deleted one vanishes. 20// Absent hosts are REPORTED, never auto-removed -- a host being off is not proof it is gone. 21// 22// nx_swarm_discover scan [a.b.c] -- probe .1-.254, report what actually answers 23// nx_swarm_discover apply [a.b.c] -- same, then APPEND unregistered finds to swarm_nodes.conf 24// license_tier: ORIGINAL expect_exit: 0 25// module: nishi-core.swarm.discover 26import "nx_syscalls.nx" 27import "nx_estate_path.nx" 28import "nx_connect.nx" 29const SD_MAGIC_1024: i64 = 1024 30 31const SD_PORT_SD: i64 = 7861 // sd-server / our engine (OpenAI-shaped) 32const SD_PORT_OLLAMA: i64 = 11434 // ollama 33const SD_HOST_LO: i64 = 1 34const SD_HOST_HI: i64 = 254 35const SD_BUF: i64 = 65536 36const SD_TMO_MS: i64 = 400 // bounded: a full /24 must not take an hour 37 38func sw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 39func sn(v: i64) -> i64 { 40 let t: *u8 = sys_mmap(32); let b: *u8 = sys_mmap(32) 41 var m: i64 = v; var k: i64 = 0 42 if m == 0 { t[0] = 48 as u8; k = 1 } 43 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 44 var i: i64 = 0 45 while i < k { b[i] = t[k - 1 - i]; i = i + 1 } 46 sys_write(1, b, k); return 0 47} 48func sd_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 49func sd_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var a: i64 = o; while s[i] != (0 as u8) { d[a] = s[i]; a = a + 1; i = i + 1 } return a } 50func sd_catn(d: *u8, o: i64, v: i64) -> i64 { 51 let t: *u8 = sys_mmap(32) 52 var m: i64 = v; var k: i64 = 0 53 if m == 0 { t[0] = 48 as u8; k = 1 } 54 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 55 var a: i64 = o; var j: i64 = k - 1 56 while j >= 0 { d[a] = t[j]; a = a + 1; j = j - 1 } 57 return a 58} 59 60// GET <path> from a.b.c.d:port. Returns bytes read, or -1. Bounded connect AND bounded read, so a 61// black-holed host costs SD_TMO_MS, not a 127-second kernel timeout. 62func sd_probe(a: i64, b: i64, c: i64, d: i64, port: i64, path: *u8, out: *u8, cap: i64) -> i64 { 63 let fd: i64 = sys_socket(2, 1, 0) 64 if fd < 0 { return 0 - 1 } 65 sys_set_socket_timeout(fd, 2) 66 let sa: *u8 = sys_mmap(16) 67 sa[0] = 2 as u8; sa[1] = 0 as u8 68 sa[2] = ((port >> 8) & 0xff) as u8; sa[3] = (port & 0xff) as u8 69 sa[4] = a as u8; sa[5] = b as u8; sa[6] = c as u8; sa[7] = d as u8 70 var z: i64 = 8 71 while z < 16 { sa[z] = 0 as u8; z = z + 1 } 72 if nx_connect_bounded(fd, sa, 16, SD_TMO_MS) != 0 { sys_close(fd); return 0 - 1 } 73 let req: *u8 = sys_mmap(SD_MAGIC_1024) 74 var o: i64 = sd_cat(req, 0, "GET " as *u8) 75 o = sd_cat(req, o, path) 76 o = sd_cat(req, o, " HTTP/1.1\r\nHost: probe\r\nConnection: close\r\n\r\n" as *u8) 77 sys_write(fd, req, o) 78 var t: i64 = 0 79 var r: i64 = 1 80 while r > 0 { 81 if t >= cap { r = 0 } else { r = sys_read(fd, ((out as i64) + t) as *u8, cap - t); if r > 0 { t = t + r } } 82 } 83 sys_close(fd) 84 return t 85} 86 87func sd_has(buf: *u8, n: i64, needle: *u8) -> i64 { 88 let m: i64 = sd_len(needle) 89 if m == 0 { return 0 } 90 if m > n { return 0 } 91 var i: i64 = 0 92 while i <= n - m { 93 var j: i64 = 0 94 var same: i64 = 1 95 while j < m { if buf[i + j] != needle[j] { same = 0; j = m } else { j = j + 1 } } 96 if same == 1 { return 1 } 97 i = i + 1 98 } 99 return 0 100} 101 102// Is this a REAL inference endpoint, or just something listening? ★A PORT BEING OPEN IS NOT A 103// CAPABILITY -- a DSM login page answered our first sweep on :8000 and would have been "a node". 104// Verified = it identifies as a model server. 105func sd_verify(a: i64, b: i64, c: i64, d: i64, port: i64, buf: *u8) -> i64 { 106 var n: i64 = 0 107 if port == SD_PORT_OLLAMA { 108 n = sd_probe(a, b, c, d, port, "/api/tags" as *u8, buf, SD_BUF) 109 if n > 0 { if sd_has(buf, n, "models" as *u8) == 1 { return 1 } } 110 return 0 111 } 112 n = sd_probe(a, b, c, d, port, "/v1/models" as *u8, buf, SD_BUF) 113 if n <= 0 { return 0 } 114 if sd_has(buf, n, "\"object\"" as *u8) == 1 { return 1 } 115 if sd_has(buf, n, "\"data\"" as *u8) == 1 { return 1 } 116 if sd_has(buf, n, "model" as *u8) == 1 { return 1 } 117 return 0 118} 119 120// already in the SSOT? (substring match on "a.b.c.d:port" in an uncommented row) 121func sd_registered(a: i64, b: i64, c: i64, d: i64, port: i64) -> i64 { 122 let buf: *u8 = sys_mmap(SD_BUF) 123 let fd: i64 = ep_open_rd("knowledge/swarm_nodes.conf" as *u8) 124 if fd < 0 { return 0 } 125 var n: i64 = 0 126 var r: i64 = 1 127 while r > 0 { r = sys_read(fd, ((buf as i64) + n) as *u8, SD_BUF - n); if r > 0 { n = n + r } } 128 sys_close(fd) 129 let want: *u8 = sys_mmap(64) 130 var o: i64 = sd_catn(want, 0, a); want[o] = 46 as u8; o = o + 1 131 o = sd_catn(want, o, b); want[o] = 46 as u8; o = o + 1 132 o = sd_catn(want, o, c); want[o] = 46 as u8; o = o + 1 133 o = sd_catn(want, o, d); want[o] = 58 as u8; o = o + 1 134 o = sd_catn(want, o, port) 135 want[o] = 0 as u8 136 return sd_has(buf, n, want) 137} 138 139func sd_append_row(a: i64, b: i64, c: i64, d: i64, port: i64, idx: i64) -> i64 { 140 var fd: i64 = sys_openat_append("knowledge/swarm_nodes.conf" as *u8, 420) 141 if fd < 0 { fd = sys_openat_append("/volume1/homes/elderwesto/nishihost/knowledge/swarm_nodes.conf" as *u8, 420) } 142 if fd < 0 { return 0 - 1 } 143 let ln: *u8 = sys_mmap(512) 144 var o: i64 = sd_cat(ln, 0, "R\tgpu-found-" as *u8) 145 o = sd_catn(ln, o, idx) 146 o = sd_cat(ln, o, "\tdiscovered\t" as *u8) 147 o = sd_catn(ln, o, a); ln[o] = 46 as u8; o = o + 1 148 o = sd_catn(ln, o, b); ln[o] = 46 as u8; o = o + 1 149 o = sd_catn(ln, o, c); ln[o] = 46 as u8; o = o + 1 150 o = sd_catn(ln, o, d); ln[o] = 58 as u8; o = o + 1 151 o = sd_catn(ln, o, port) 152 o = sd_cat(ln, o, "\tauto-discovered inference endpoint\tVERIFIED by /v1/models or /api/tags at discovery time; re-verify before relying on it\n" as *u8) 153 var w: i64 = 0 154 while w < o { let r: i64 = sys_write(fd, ((ln as i64) + w) as *u8, o - w); if r <= 0 { w = o } else { w = w + r } } 155 sys_close(fd) 156 return 0 157} 158 159func main(argc: i64, argv: *i64) -> i64 { 160 ep_anchor() 161 var apply: i64 = 0 162 if argc >= 2 { let v: *u8 = argv[1] as *u8; if v[0] == (97 as u8) { apply = 1 } } 163 // default subnet 192.168.8 -- the estate LAN 164 var oa: i64 = 192 165 var ob: i64 = 168 166 var oc: i64 = 8 167 if argc >= 3 { 168 let s: *u8 = argv[2] as *u8 169 var f: i64 = 0 170 var i: i64 = 0 171 var vv: i64 = 0 172 var seen: i64 = 0 173 while f < 3 { 174 let ch: i64 = s[i] & 0xff 175 if ch >= 48 { if ch <= 57 { vv = vv * 10 + (ch - 48); seen = 1; i = i + 1 } else { i = i + 1 } } 176 else { 177 if seen == 1 { 178 if f == 0 { oa = vv } else { if f == 1 { ob = vv } else { oc = vv } } 179 f = f + 1; vv = 0; seen = 0 180 if ch == 0 { f = 3 } 181 } else { if ch == 0 { f = 3 } else { i = i + 1 } } 182 } 183 } 184 if seen == 1 { if f < 3 { oc = vv } } 185 } 186 187 sw("=== NX-SWARM-DISCOVER scanning \x00" as *u8); sn(oa); sw(".\x00" as *u8); sn(ob); sw(".\x00" as *u8); sn(oc) 188 sw(".1-254 for inference endpoints (bounded probes)\n\x00" as *u8) 189 190 let buf: *u8 = sys_mmap(SD_BUF) 191 var found: i64 = 0 192 var added: i64 = 0 193 var h: i64 = SD_HOST_LO 194 while h <= SD_HOST_HI { 195 var pi: i64 = 0 196 while pi < 2 { 197 var port: i64 = SD_PORT_SD 198 if pi == 1 { port = SD_PORT_OLLAMA } 199 if sd_verify(oa, ob, oc, h, port, buf) == 1 { 200 found = found + 1 201 sw(" FOUND \x00" as *u8); sn(oa); sw(".\x00" as *u8); sn(ob); sw(".\x00" as *u8); sn(oc) 202 sw(".\x00" as *u8); sn(h); sw(":\x00" as *u8); sn(port) 203 if sd_registered(oa, ob, oc, h, port) == 1 { sw(" (already in the SSOT)\n\x00" as *u8) } 204 else { 205 if apply == 1 { 206 if sd_append_row(oa, ob, oc, h, port, found) == 0 { added = added + 1; sw(" -> APPENDED to swarm_nodes.conf\n\x00" as *u8) } 207 else { sw(" -> APPEND FAILED (conf unwritable)\n\x00" as *u8) } 208 } else { sw(" UNREGISTERED (run `apply` to add)\n\x00" as *u8) } 209 } 210 } 211 pi = pi + 1 212 } 213 h = h + 1 214 } 215 sw("NX-SWARM-DISCOVER endpoints_found=\x00" as *u8); sn(found) 216 sw(" appended=\x00" as *u8); sn(added) 217 sw("\nenvelope: probes are BOUNDED and verification requires a model-server response, so an open\n\x00" as *u8) 218 sw("port alone never becomes a node. Absent hosts are NOT removed -- a machine being off is not\n\x00" as *u8) 219 sw("proof it is gone, and a deleted row fails silently where a stale one fails loudly.\n\x00" as *u8) 220 if found == 0 { sw("NX-SWARM-DISCOVER NONE -- no inference capacity answered on this subnet\n\x00" as *u8) } 221 return 0 222}