code wiki / (root) / nx_svc_budget_gate.nx

nx_svc_budget_gate.nx source

↩ module page · 143 lines · 7676 B

1// nx_svc_budget_gate.nx -- REGRESSION PROOF for the self-retiring-service class (debt seq882, 2026-07-25). 2// 3// THE DEFECT IT LOCKS OUT: 44 sovereign services run `while served < budget { accept... } sys_exit(0)`. One 4// launched with a SMALL finite budget therefore RETIRES ITSELF -- and nx_hostctl's guard (nx_hostctl.nx:798) 5// reads the repeated exits as a crash-loop and BACKS OFF, so the surface goes genuinely DOWN while `status` 6// still prints UP. Measured live 2026-07-25: the gallery gateway, the wiki/hub/torrent/gen gateways, the main 7// OPAQUE login (:9091) AND the mgmt API itself were all launched with budget=5000. That is the root cause of 8// the operator-reported "gallery wont let me login" AND of the chronic mgmt/MCP transport flake. 9// nx_docportal_search_serve.nx:303 documents this SAME failure diagnosed on 2026-07-03 -- fixed there, never 10// swept. This gate exists so it can never silently return. 11// 12// WHY IT ASSERTS ON BYTES, NOT INTENT: "we fixed it" is author optimism, not evidence. Each tooth greps the 13// actual launch-command constant. T8 greps the LIVE DEPLOYED BINARY, not just source -- this session proved 14// that lie twice (a carve-out banked in memory as "landed in source" was NOT in source; a deployed gallery 15// thumbnailer predated its own fix by three weeks). Source-GREEN + binary-RED = NOT SHIPPED. T9/T10 are 16// MUTATION teeth: they fail if the exact bad literal reappears, so a revert cannot pass quietly. 17// 18// Named _svc_ not _daemon_ deliberately: md_promote_deny substring-blocks "daemon" so real daemons are forced 19// through the health-checked /api/deploy. This is a one-shot organ, so the honest name is the promotable one. 20// 21// Run from the nishihost CWD (the tools daemon fork-execs it there). license_tier: ORIGINAL No hw writes (Rule 26). 22import "nx_syscalls.nx" 23import "nx_gate_verdict.nx" 24 25const SBG_CAP: i64 = 1048576 26const SBG_MIN_BUDGET: i64 = 1000000 27 28func sbg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 29func sbg_puts(s: *u8) -> i64 { sys_write(1, s, sbg_slen(s)); return 0 } 30func sbg_putn(v: i64) -> i64 { 31 let t: *u8 = sys_mmap(32) 32 var m: i64 = v 33 var k: i64 = 0 34 if m == 0 { t[0] = 48 as u8; k = 1 } 35 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 36 let o: *u8 = sys_mmap(32) 37 var w: i64 = 0 38 var q: i64 = k - 1 39 while q >= 0 { o[w] = t[q]; w = w + 1; q = q - 1 } 40 sys_write(1, o, w) 41 return 0 42} 43 44// bounded whole-file read into a caller buffer; returns bytes read (0 = unreadable/empty) 45func sbg_slurp(path: *u8, buf: *u8, cap: i64) -> i64 { 46 let fd: i64 = sys_openat_rd(path) 47 if fd < 0 { return 0 } 48 var total: i64 = 0 49 var n: i64 = sys_read(fd, buf, cap) 50 while n > 0 { 51 total = total + n 52 if total >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + total) as *u8, cap - total) } 53 } 54 sys_close(fd) 55 return total 56} 57 58func sbg_contains(hay: *u8, hn: i64, needle: *u8) -> i64 { 59 let nn: i64 = sbg_slen(needle) 60 if nn == 0 { return 1 } 61 var i: i64 = 0 62 while i + nn <= hn { 63 var k: i64 = 0 64 var ok: i64 = 1 65 while k < nn { if hay[i+k] != needle[k] { ok = 0; k = nn } else { k = k + 1 } } 66 if ok == 1 { return 1 } 67 i = i + 1 68 } 69 return 0 70} 71 72// PRESENT tooth: the launch constant must carry an unbounded budget. 73// D001-migrated 2026-08-03: gv_check carries the canonical count; pass[0] persists ONLY to feed the 74// legacy `T=10 PASS=n` line (kept as the public signature per the migrate-on-touch recipe). 75func sbg_tooth(buf: *u8, n: i64, needle: *u8, label: *u8, pass: *i64, ctr: *i64) -> i64 { 76 let hit: i64 = sbg_contains(buf, n, needle) 77 if hit == 1 { pass[0] = pass[0] + 1 } 78 gv_check(label, hit, ctr) 79 return hit 80} 81 82// MUTATION tooth: the exact regression literal must be ABSENT. 83func sbg_tooth_absent(buf: *u8, n: i64, needle: *u8, label: *u8, pass: *i64, ctr: *i64) -> i64 { 84 let hit: i64 = sbg_contains(buf, n, needle) 85 var ok: i64 = 0 86 if hit == 0 { ok = 1; pass[0] = pass[0] + 1 } 87 gv_check(label, ok, ctr) 88 return hit 89} 90 91func main(argc: i64, argv: *i64) -> i64 { 92 let pass: *i64 = sys_mmap(16) as *i64 93 pass[0] = 0 94 let total: i64 = 10 95 let ctr: *i64 = gv_ctr() 96 97 sbg_puts("nx_svc_budget_gate -- no supervised service may retire itself (min budget " as *u8) 98 sbg_putn(SBG_MIN_BUDGET) 99 sbg_puts(")\n" as *u8) 100 101 let src: *u8 = sys_mmap(SBG_CAP) 102 let sn: i64 = sbg_slurp("buildroot/runtime/_hdl_build/nx_hostctl.nx" as *u8, src, SBG_CAP) 103 if sn == 0 { 104 sbg_puts("FAIL cannot read nx_hostctl.nx source (run from the nishihost CWD)\n" as *u8) 105 sbg_puts("nx_svc_budget_gate T=10 PASS=0 verdict=RED\n" as *u8) 106 return 1 107 } 108 109 sbg_tooth(src, sn, "galx_gw_store 1000000000 18090" as *u8, "T1 gallery gateway :18190 unbounded" as *u8, pass, ctr) 110 sbg_tooth(src, sn, "mgmt_snap.json 1000000000 >" as *u8, "T2 mgmt API :18098 unbounded (the control plane)" as *u8, pass, ctr) 111 // ⚠NEEDLE DRIFT FIXED 2026-08-03: these teeth grep EXACT launch strings, and the commands legitimately 112 // evolved (redirects > -> >>, login gained wiki-pages + hr-log args) while the needles did not -- so the 113 // gate screamed FALSE RED on four services whose budgets were 1000000000 all along (verified by reading 114 // HC_*_CMD). Same class as the cron-watch head-read: an instrument bound to an outdated format ages into 115 // a liar. Needles now pin budget + the arg that FOLLOWS it (login: the wiki-pages path; hub/torrent/gen: 116 // their >> log names), so a redirect-style change can't fake a budget regression either way. 117 sbg_tooth(src, sn, "opaque_store.log 1000000000 19456 2 1 /mnt" as *u8, "T3 OPAQUE login :9091 unbounded" as *u8, pass, ctr) 118 sbg_tooth(src, sn, "nx_wiki_gw.elf 18791 keys store 1000000000" as *u8, "T4 wiki gateway :18791 unbounded" as *u8, pass, ctr) 119 sbg_tooth(src, sn, "opaque_store.log 1000000000 65536 3 4 >>/tmp/hub_gw.log" as *u8, "T5 hub gateway :18792 unbounded" as *u8, pass, ctr) 120 sbg_tooth(src, sn, "opaque_store.log 1000000000 65536 3 4 >>/tmp/torrentgw.log" as *u8, "T6 torrent gateway :18793 unbounded" as *u8, pass, ctr) 121 sbg_tooth(src, sn, "opaque_store.log 1000000000 18795 0 19456 2 1 86400 >>/tmp/gen_gw.log" as *u8, "T7 gen gateway :18794 unbounded" as *u8, pass, ctr) 122 123 // T8 -- SOURCE-vs-DEPLOYED DRIFT. A fix that lives only in source is an unverified intention. 124 let live: *u8 = sys_mmap(SBG_CAP) 125 let ln: i64 = sbg_slurp("nx_hostctl" as *u8, live, SBG_CAP) 126 if ln == 0 { 127 gv_check("T8 LIVE DEPLOYED binary carries the fix (not just source)" as *u8, 0, ctr) 128 sbg_puts("FAIL T8 cannot read the LIVE nx_hostctl binary\n" as *u8) 129 } else { 130 sbg_tooth(live, ln, "galx_gw_store 1000000000 18090" as *u8, "T8 LIVE DEPLOYED binary carries the fix (not just source)" as *u8, pass, ctr) 131 } 132 133 // T9/T10 -- MUTATION teeth: the exact regression literals must never reappear. 134 sbg_tooth_absent(src, sn, "galx_gw_store 5000 18090" as *u8, "T9 mutation: gallery budget=5000 ABSENT from source" as *u8, pass, ctr) 135 sbg_tooth_absent(src, sn, "mgmt_snap.json 5000 >" as *u8, "T10 mutation: mgmt API budget=5000 ABSENT from source" as *u8, pass, ctr) 136 137 sbg_puts("nx_svc_budget_gate T=" as *u8) 138 sbg_putn(total) 139 sbg_puts(" PASS=" as *u8) 140 sbg_putn(pass[0]) 141 if pass[0] == total { sbg_puts(" verdict=GREEN\n" as *u8) } else { sbg_puts(" verdict=RED\n" as *u8) } 142 return gv_verdict("svc_budget" as *u8, ctr, "no supervised service may retire itself; needles pin budget 1000000000 in each live launch cmd" as *u8) 143}