code wiki / _hdl_build / nx_swapguard_beat.nx

nx_swapguard_beat.nx source

↩ module page · 130 lines · 6770 B

1// nx_swapguard_beat.nx -- SWAP-EXHAUSTION TRIPWIRE (2026-07-29, seq1256 lineage). Today's mgmt_api 2// and tools_api wedges were both preceded by HOURS of exhausted swap (12-196kB free) with no alarm 3// anywhere -- long-running seg-store consumers leak into swap (seq905/947 class) and then park in 4// uninterruptible sleep. This beat reads /proc/meminfo SwapFree every scheduler cycle and, on breach, 5// (a) appends an evidence line and (b) files a sev8 debt row THROUGH nx_debt whose desc is CONSTANT -- 6// nx_debt add is content-idempotent (banked law 07-20), so hourly re-alarms dedupe to ONE standing row 7// and the board (which gates nx_ws_cycle work-emission) becomes the alarm surface. ALARM-ONLY by 8// design: auto-recycle needs the sanctioned restart paths (seq1256) and graduates only after this 9// tripwire proves calibrated. 10// scheduler contract: jobs.tsv fork+execs with NO args. argv[1]=floor_kb override (testing). 11// floor default 2097152 kB = 2GiB, DERIVED: swap total ~24GiB; every observed wedge sat at <200kB 12// for hours, fresh-boot free is >20GiB -- 2GiB (~8%) alarms early while never false-firing on a 13// healthy host (rule-11: promote to conf when field-tuned). 14// trend line: <epoch>\tSwapFree=<kb>\t<OK|ALARM>\n -> knowledge/status/swapguard_trend.jsonl 15// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 healthy, 1 alarm 16import "nx_sovjson_lib.nx" 17import "nx_syscalls.nx" 18import "nx_tool_run.nx" 19import "nx_gate_verdict.nx" 20 21const SG_FLOOR_KB: i64 = 2097152 22const SG_MODE: i64 = 420 23const SG_BUF: i64 = 8192 24const SG_HEALTHY_WITNESS_KB: i64 = 20000000 // ~19GiB: a comfortably-above-floor value for the gate's good-polarity witness 25 26func sg_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 27func sg_append(path: *u8, buf: *u8, n: i64) -> i64 { 28 let fd: i64 = sys_openat_append(path, SG_MODE) 29 if fd < 0 { return 0 - 1 } 30 sys_write(fd, buf, n) 31 sys_fsync(fd) 32 sys_close(fd) 33 return 0 34} 35// parse SwapFree kB out of /proc/meminfo; -1 if absent 36func sg_swapfree() -> i64 { 37 let b: *u8 = sys_mmap(SG_BUF) 38 let fd: i64 = sys_openat_rd("/proc/meminfo" as *u8) 39 if fd < 0 { return 0 - 1 } 40 var got: i64 = 0 41 var r: i64 = 1 42 while r > 0 { r = sys_read(fd, ((b as i64) + got) as *u8, SG_BUF - got); if r > 0 { got = got + r } if got >= SG_BUF { r = 0 } } 43 sys_close(fd) 44 let key: *u8 = "SwapFree:" as *u8 45 let kl: i64 = sj_vlen(key) 46 var i: i64 = 0 47 while i + kl < got { 48 var eq: i64 = 1 49 var k: i64 = 0 50 while k < kl { if b[i + k] != key[k] { eq = 0; k = kl } else { k = k + 1 } } 51 if eq == 1 { 52 var v: i64 = 0 53 var j: i64 = i + kl 54 var any: i64 = 0 55 var go: i64 = 1 56 while go == 1 { 57 go = 0 58 if j < got { 59 let c: i64 = b[j] as i64 60 if c == 32 { if any == 0 { j = j + 1; go = 1 } } 61 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; j = j + 1; go = 1 } } 62 } 63 } 64 if any == 1 { return v } 65 return 0 - 1 66 } 67 i = i + 1 68 } 69 return 0 - 1 70} 71// PURE breach decision -- one definition, main AND the gate call it (semantic consistency by construction). 72// Breach iff SwapFree unreadable (sf<0, treat as broken host) OR below the floor. 73func sg_breach(sf: i64, floor: i64) -> i64 { 74 if sf < 0 { return 1 } 75 if sf < floor { return 1 } 76 return 0 77} 78func sg_selftest() -> i64 { 79 let ctr: *i64 = gv_ctr() 80 gv_head("nx_swapguard_beat selftest -- breach threshold bite-proven both polarities" as *u8) 81 // floor = 2GiB (2097152 kB). Each cell fires on the BAD side, silent on the GOOD side. 82 gv_bite("T1 below-floor breaches, above-floor clears" as *u8, sg_breach(100, SG_FLOOR_KB), sg_breach(SG_HEALTHY_WITNESS_KB, SG_FLOOR_KB), ctr) 83 gv_bite("T2 unreadable-swap (sf<0) breaches, exact-floor clears" as *u8, sg_breach(0 - 1, SG_FLOOR_KB), sg_breach(SG_FLOOR_KB, SG_FLOOR_KB), ctr) 84 gv_bite("T3 zero-free breaches, floor+1 clears" as *u8, sg_breach(0, SG_FLOOR_KB), sg_breach(SG_FLOOR_KB + 1, SG_FLOOR_KB), ctr) 85 let rc: i64 = gv_verdict("NX-SWAPGUARD-GATE" as *u8, ctr, "swap-exhaustion breach decision: below-floor/unreadable fire, at-or-above clear" as *u8) 86 return rc 87} 88func main(argc: i64, argv: *i64) -> i64 { 89 if argc > 1 { if sj_lit_eq(argv[1] as *u8, 0, sj_vlen(argv[1] as *u8), "selftest" as *u8) == 1 { let g: i64 = sg_selftest(); sys_exit(g); return g } } 90 var sgfloor: i64 = SG_FLOOR_KB 91 if argc > 1 { sgfloor = sj_atoi_z(argv[1] as *u8) } 92 let sf: i64 = sg_swapfree() 93 let now: i64 = sys_now_realtime_sec() 94 let ln: *u8 = sys_mmap(SG_BUF) 95 var o: i64 = sj_catn(ln, 0, now) 96 ln[o] = 9 as u8 97 o = o + 1 98 o = sj_cat(ln, o, "SwapFree=" as *u8) 99 o = sj_catn(ln, o, sf) 100 ln[o] = 9 as u8 101 o = o + 1 102 let sgalarm: i64 = sg_breach(sf, sgfloor) 103 if sgalarm == 1 { o = sj_cat(ln, o, "ALARM" as *u8) } else { o = sj_cat(ln, o, "OK" as *u8) } 104 ln[o] = 10 as u8 105 o = o + 1 106 sg_append("knowledge/status/swapguard_trend.jsonl" as *u8, ln, o) 107 if sgalarm == 0 { sg_puts("SWAPGUARD OK\n" as *u8); sys_exit(0); return 0 } 108 // file the STANDING alarm row through the sovereign board verb; CONSTANT desc = content-idempotent 109 let elf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_debt.elf" as *u8 110 // CONSTANT desc BY DESIGN (2026-07-29 seq1271 root fix): nx_debt dedup now matches OPEN rows 111 // only, so while the alarm row stands, hourly re-alarms dedupe to it; once eaten (swap 112 // recovered), the next real breach re-files cleanly. The day-bucket workaround is deleted. 113 let av: *i64 = sys_mmap(64) as *i64 114 av[0] = elf as i64 115 av[1] = ("add" as *u8) as i64 116 av[2] = ("8" as *u8) as i64 117 av[3] = ("platform" as *u8) as i64 118 av[4] = ("SWAP-EXHAUSTION TRIPWIRE FIRING (nx_swapguard_beat): host SwapFree is below the derived 2GiB floor. The seq905/947 seg-store leak class is filling swap; long-running daemons (tools_api/mgmt_api pattern, wedges of 2026-07-29) park in uninterruptible sleep and the control plane wedges NEXT. Recycle the leaky daemon (holder-inode kill recipe, census memory + ctnr_recover.sh) and advance the ss_close consumer migration. Eat when swap recovers; re-files on the next breach." as *u8) as i64 119 av[5] = 0 120 let out: *u8 = sys_mmap(SG_BUF) 121 let ol: *i64 = sys_mmap(16) as *i64 122 let rc: i64 = tr_run_capture(elf, av, out, SG_BUF, ol) 123 sg_puts("SWAPGUARD ALARM filed=" as *u8) 124 let m: *u8 = sys_mmap(64) 125 var mo: i64 = sj_catn(m, 0, rc) 126 m[mo] = 10 as u8 127 sys_write(1, m, mo + 1) 128 sys_exit(1) 129 return 1 130}