code wiki / _hdl_build / nx_routeguard_lib.nx

nx_routeguard_lib.nx source

↩ module page · 187 lines · 7576 B

1// nx_routeguard_lib.nx -- DEPLOY CONTRACT GUARD: does the candidate binary still serve every route 2// the live one does? 3// 4// THE DEFECT THIS EXISTS FOR (measured 2026-07-30, FIVE occurrences, one of them mine): 5// nx_mgmt_api has now lost /api/gate_run + /api/proc_kill from the LIVE binary five separate times 6// (21 routes -> 19). Mine happened because I built mgmt from a backdated tree and deployed a 528323-byte 7// artifact over a 575195-byte one -- a 47KB DECREASE that was visible at the time and that I did not 8// check. Every occurrence had the same shape: the source was fine, the BINARY silently lost surface, 9// and the deploy reported success. nx_route_diff already prints "route(s) vanished = deploy contract 10// regression" but is not wired into the deploy path, so nothing ever consulted it. 11// 12// WHY THIS GATE AND NOT LOAD-ADMISSION (self-correction, id=1785450386): my first instinct was to gate 13// deploys on host load the way /api/build is gated. That is WRONG and would deadlock the ecosystem -- 14// the repair for a saturated host IS a deploy, so a load-gated deploy refuses the fix BY THE CONDITION 15// IT REPAIRS. Route loss, by contrast, is ALWAYS wrong regardless of load, so this guard can never 16// block a repair. That property is the whole reason this is the right guard. 17// 18// DERIVED, NOT DECLARED: the expected route set is EXTRACTED FROM THE LIVE BINARY, never hardcoded. 19// A baked list would be the same declared-denominator defect already filed (id=1785446417) -- it would 20// go stale the moment a route is legitimately added, and it would fail OPEN. 21// 22// ASYMMETRIC BY DESIGN: candidate ADDING routes is fine (that is a normal feature deploy). Only 23// DISAPPEARANCE is a defect. So this is a SUPERSET check, not an equality check. 24// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). 25import "nx_syscalls.nx" 26 27const RG_BUF: i64 = 4194304 // per-binary read cap 28const RG_MAXR: i64 = 128 // max distinct routes tracked 29const RG_NAMEMAX: i64 = 64 30const RG_NEEDLE: i64 = 5 // strlen("/api/") 31 32func rg_isroute_ch(c: i64) -> i64 { 33 if c >= 97 { if c <= 122 { return 1 } } 34 if c >= 65 { if c <= 90 { return 1 } } 35 if c >= 48 { if c <= 57 { return 1 } } 36 if c == 95 { return 1 } 37 if c == 47 { return 1 } 38 if c == 46 { return 1 } 39 return 0 40} 41 42// read a whole file into a REUSED bounded buffer. returns bytes, or -1 if unopenable. 43func rg_read(path: *u8, buf: *u8, cap: i64) -> i64 { 44 let fd: i64 = sys_openat_rd(path) 45 if fd < 0 { return 0 - 1 } 46 var n: i64 = 0 47 var go: i64 = 1 48 while go == 1 { 49 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - n) 50 if r <= 0 { go = 0 } else { 51 n = n + r 52 if n >= cap { go = 0 } 53 } 54 } 55 sys_close(fd) 56 return n 57} 58 59// Does buf[0..n) contain the needle AS A WHOLE ROUTE TOKEN? 60// 61// BOUNDARY-CHECKED, and that is not pedantry -- it was a real hole caught by this organ's own gate. 62// A plain substring scan says /api/healthcheck SATISFIES /api/health, so RENAMING a route would pass 63// the guard silently: the old name vanishes from the surface while the check reports 0 missing. That 64// is the same class as the @priv lexer needing a trailing boundary so @privv could not match @priv. 65// A route is only "still served" if the literal ENDS where the live one ends. 66func rg_contains(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 { 67 if nl <= 0 { return 0 } 68 var i: i64 = 0 69 let last: i64 = n - nl 70 while i <= last { 71 var j: i64 = 0 72 var ok: i64 = 1 73 while j < nl { if buf[i + j] != needle[j] { ok = 0; j = nl } else { j = j + 1 } } 74 if ok == 1 { 75 let e: i64 = i + nl 76 var bounded: i64 = 1 77 if e < n { if rg_isroute_ch(buf[e] as i64) == 1 { bounded = 0 } } 78 if bounded == 1 { return 1 } 79 } 80 i = i + 1 81 } 82 return 0 83} 84 85// already collected? 86func rg_seen(names: *u8, lens: *i64, cnt: i64, p: *u8, l: i64) -> i64 { 87 var i: i64 = 0 88 while i < cnt { 89 if lens[i] == l { 90 let base: i64 = i * RG_NAMEMAX 91 var k: i64 = 0 92 var same: i64 = 1 93 while k < l { if names[base + k] != p[k] { same = 0; k = l } else { k = k + 1 } } 94 if same == 1 { return 1 } 95 } 96 i = i + 1 97 } 98 return 0 99} 100 101 102// Binary help text is weak route evidence. A sentence-ending period is a 103// redundant prose alias only when a separate exact base-route literal exists. 104// Quoted/NUL-delimited dotted routes and ambiguous cases remain guarded. 105func rg_prose_period_alias(buf:*u8,n:i64,start:i64,end:i64)->i64{ 106 if start<=0 || end<=start+RG_NEEDLE{return 0} 107 if buf[end-1]!=(46 as u8){return 0} 108 let before:i64=buf[start-1] as i64 109 if before!=32 && before!=9 && before!=10 && before!=13{return 0} 110 if end<n{ 111 let after:i64=buf[end] as i64 112 if after!=0 && after!=32 && after!=9 && after!=10 && after!=13{return 0} 113 } 114 let len:i64=end-start-1 115 var p:i64=0 116 while p+len<n{ 117 var left:i64=0 118 if p==0{left=1}else{if buf[p-1]==(0 as u8) || buf[p-1]==(34 as u8){left=1}} 119 if left==1{ 120 let right:i64=buf[p+len] as i64 121 if right==0 || right==34{ 122 var j:i64=0 123 while j<len && buf[p+j]==buf[start+j]{j=j+1} 124 if j==len{return 1} 125 } 126 } 127 p=p+1 128 } 129 return 0 130} 131 132// EXTRACT the route surface from a binary image: every distinct "/api/..." literal it carries. 133// Bounded by RG_MAXR; the caller REPORTS truncation rather than silently guarding a subset. 134func rg_extract(buf: *u8, n: i64, names: *u8, lens: *i64, trunc: *i64) -> i64 { 135 trunc[0] = 0 136 var cnt: i64 = 0 137 var i: i64 = 0 138 let last: i64 = n - RG_NEEDLE 139 while i <= last { 140 var hit: i64 = 0 141 if buf[i] == (47 as u8) { if buf[i+1] == (97 as u8) { if buf[i+2] == (112 as u8) { 142 if buf[i+3] == (105 as u8) { if buf[i+4] == (47 as u8) { hit = 1 } } } } } 143 if hit == 1 { 144 var end: i64 = i 145 var go: i64 = 1 146 while go == 1 { 147 if end >= n { go = 0 } else { 148 if rg_isroute_ch(buf[end] as i64) == 1 { end = end + 1 } else { go = 0 } 149 } 150 } 151 let l: i64 = end - i 152 if l > RG_NEEDLE && rg_prose_period_alias(buf,n,i,end)==0 { 153 if l < RG_NAMEMAX { 154 if rg_seen(names, lens, cnt, (buf as i64 + i) as *u8, l) == 0 { 155 if cnt < RG_MAXR { 156 let base: i64 = cnt * RG_NAMEMAX 157 var k: i64 = 0 158 while k < l { names[base + k] = buf[i + k]; k = k + 1 } 159 names[base + l] = 0 as u8 160 lens[cnt] = l 161 cnt = cnt + 1 162 } else { trunc[0] = trunc[0] + 1 } 163 } 164 } 165 } 166 i = end 167 } else { i = i + 1 } 168 } 169 return cnt 170} 171 172// THE VERDICT. Returns the number of routes present in LIVE but MISSING from CANDIDATE. 173// 0 = the candidate is a superset = safe to promote on this axis. 174// Missing route indices are written into miss[] (bounded by misscap). 175func rg_missing(lnames: *u8, llens: *i64, lcnt: i64, cbuf: *u8, cn: i64, miss: *i64, misscap: i64) -> i64 { 176 var m: i64 = 0 177 var i: i64 = 0 178 while i < lcnt { 179 let base: i64 = i * RG_NAMEMAX 180 if rg_contains(cbuf, cn, (lnames as i64 + base) as *u8, llens[i]) == 0 { 181 if m < misscap { miss[m] = i } 182 m = m + 1 183 } 184 i = i + 1 185 } 186 return m 187}