nx_daemon_budget_gate.nx source
↩ module page · 131 lines · 6471 B
1// nx_daemon_budget_gate.nx -- REGRESSION PROOF for the self-retiring-daemon class (debt seq882, 2026-07-25).
2//
3// THE DEFECT IT LOCKS OUT: 44 sovereign daemons run `while served < budget { accept... } sys_exit(0)`. A
4// supervised daemon launched with a SMALL finite budget therefore RETIRES ITSELF -- and nx_hostctl's guard
5// (nx_hostctl.nx:798) reads the repeated exits as a crash-loop and BACKS OFF, so the surface goes genuinely
6// DOWN while `status` still prints UP. Measured live: the gallery gateway, the wiki/hub/torrent/gen gateways,
7// the main OPAQUE login daemon AND the mgmt API itself were all launched with budget=5000. That is the root
8// cause of 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 being diagnosed on 2026-07-03 -- fixed there
10// and never swept. This gate exists so it can never silently return.
11//
12// WHY ASSERT ON BYTES, NOT ON INTENT: an author-optimism check ("we fixed it") is not evidence. Each tooth
13// greps the actual launch-command constant. T8 greps the LIVE DEPLOYED BINARY, not just source -- this session
14// proved that class of lie twice (a carve-out banked in memory as "landed in source" was NOT in source, and a
15// deployed gallery thumbnailer predated its own fix by three weeks). Source-GREEN + binary-RED = NOT SHIPPED.
16// T9 is the MUTATION tooth: it fails if the exact bad literal ever reappears, so a revert cannot pass quietly.
17//
18// Run from the nishihost CWD (the tools daemon fork-execs it there). license_tier: ORIGINAL No hw writes (Rule 26).
19import "nx_syscalls.nx"
20
21const DBG_CAP: i64 = 1048576
22const DBG_MIN_BUDGET: i64 = 1000000
23
24func dbg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
25func dbg_puts(s: *u8) -> i64 { sys_write(1, s, dbg_slen(s)); return 0 }
26func dbg_putn(v: i64) -> i64 {
27 let t: *u8 = sys_mmap(32)
28 var m: i64 = v
29 var k: i64 = 0
30 if m == 0 { t[0] = 48 as u8; k = 1 }
31 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
32 let o: *u8 = sys_mmap(32)
33 var w: i64 = 0
34 var q: i64 = k - 1
35 while q >= 0 { o[w] = t[q]; w = w + 1; q = q - 1 }
36 sys_write(1, o, w)
37 return 0
38}
39
40// bounded whole-file read into a caller buffer; returns bytes read (0 = unreadable/empty)
41func dbg_slurp(path: *u8, buf: *u8, cap: i64) -> i64 {
42 let fd: i64 = sys_openat_rd(path)
43 if fd < 0 { return 0 }
44 var total: i64 = 0
45 var n: i64 = sys_read(fd, buf, cap)
46 while n > 0 {
47 total = total + n
48 if total >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + total) as *u8, cap - total) }
49 }
50 sys_close(fd)
51 return total
52}
53
54func dbg_contains(hay: *u8, hn: i64, needle: *u8) -> i64 {
55 let nn: i64 = dbg_slen(needle)
56 if nn == 0 { return 1 }
57 var i: i64 = 0
58 while i + nn <= hn {
59 var k: i64 = 0
60 var ok: i64 = 1
61 while k < nn { if hay[i+k] != needle[k] { ok = 0; k = nn } else { k = k + 1 } }
62 if ok == 1 { return 1 }
63 i = i + 1
64 }
65 return 0
66}
67
68// PRESENT tooth: the launch constant must carry an unbounded budget.
69func dbg_tooth(buf: *u8, n: i64, needle: *u8, label: *u8, pass: *i64) -> i64 {
70 let hit: i64 = dbg_contains(buf, n, needle)
71 if hit == 1 { pass[0] = pass[0] + 1; dbg_puts("PASS " as *u8) } else { dbg_puts("FAIL " as *u8) }
72 dbg_puts(label)
73 dbg_puts("\n" as *u8)
74 return hit
75}
76
77// MUTATION tooth: the exact regression literal must be ABSENT.
78func dbg_tooth_absent(buf: *u8, n: i64, needle: *u8, label: *u8, pass: *i64) -> i64 {
79 let hit: i64 = dbg_contains(buf, n, needle)
80 if hit == 0 { pass[0] = pass[0] + 1; dbg_puts("PASS " as *u8) } else { dbg_puts("FAIL " as *u8) }
81 dbg_puts(label)
82 dbg_puts("\n" as *u8)
83 return hit
84}
85
86func main(argc: i64, argv: *i64) -> i64 {
87 let pass: *i64 = sys_mmap(16) as *i64
88 pass[0] = 0
89 let total: i64 = 10
90
91 dbg_puts("nx_daemon_budget_gate -- no supervised daemon may retire itself (min budget " as *u8)
92 dbg_putn(DBG_MIN_BUDGET)
93 dbg_puts(")\n" as *u8)
94
95 let src: *u8 = sys_mmap(DBG_CAP)
96 let sn: i64 = dbg_slurp("buildroot/runtime/_hdl_build/nx_hostctl.nx" as *u8, src, DBG_CAP)
97 if sn == 0 {
98 dbg_puts("FAIL cannot read nx_hostctl.nx source (run from the nishihost CWD)\n" as *u8)
99 dbg_puts("nx_daemon_budget_gate T=10 PASS=0 verdict=RED\n" as *u8)
100 return 1
101 }
102
103 dbg_tooth(src, sn, "galx_gw_store 1000000000 18090" as *u8, "T1 gallery gateway :18190 unbounded" as *u8, pass)
104 dbg_tooth(src, sn, "mgmt_snap.json 1000000000 >" as *u8, "T2 mgmt API :18098 unbounded (the control plane)" as *u8, pass)
105 dbg_tooth(src, sn, "opaque_store.log 1000000000 19456 2 1 >/tmp/login.log" as *u8, "T3 OPAQUE login :9091 unbounded" as *u8, pass)
106 dbg_tooth(src, sn, "nx_wiki_gw.elf 18791 keys store 1000000000" as *u8, "T4 wiki gateway :18791 unbounded" as *u8, pass)
107 dbg_tooth(src, sn, "opaque_store.log 1000000000 65536 3 4 >/tmp/hub_gw.log" as *u8, "T5 hub gateway :18792 unbounded" as *u8, pass)
108 dbg_tooth(src, sn, "opaque_store.log 1000000000 65536 3 4 >/tmp/torrentgw.log" as *u8, "T6 torrent gateway :18793 unbounded" as *u8, pass)
109 dbg_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)
110
111 // T8 -- SOURCE-vs-DEPLOYED DRIFT. A fix that lives only in source is an unverified intention.
112 let live: *u8 = sys_mmap(DBG_CAP)
113 let ln: i64 = dbg_slurp("nx_hostctl" as *u8, live, DBG_CAP)
114 if ln == 0 {
115 dbg_puts("FAIL T8 cannot read the LIVE nx_hostctl binary\n" as *u8)
116 } else {
117 dbg_tooth(live, ln, "galx_gw_store 1000000000 18090" as *u8, "T8 LIVE DEPLOYED binary carries the fix (not just source)" as *u8, pass)
118 }
119
120 // T9/T10 -- MUTATION teeth: the exact regression literals must never reappear.
121 dbg_tooth_absent(src, sn, "galx_gw_store 5000 18090" as *u8, "T9 mutation: gallery budget=5000 is ABSENT from source" as *u8, pass)
122 dbg_tooth_absent(src, sn, "mgmt_snap.json 5000 >" as *u8, "T10 mutation: mgmt API budget=5000 is ABSENT from source" as *u8, pass)
123
124 dbg_puts("nx_daemon_budget_gate T=" as *u8)
125 dbg_putn(total)
126 dbg_puts(" PASS=" as *u8)
127 dbg_putn(pass[0])
128 if pass[0] == total { dbg_puts(" verdict=GREEN\n" as *u8) } else { dbg_puts(" verdict=RED\n" as *u8) }
129 if pass[0] == total { return 0 }
130 return 1
131}