code wiki / _hdl_build / nx_routeguard.nx

nx_routeguard.nx source

↩ module page · 109 lines · 5154 B

1// nx_routeguard.nx -- THIN ORGAN: run the deploy-contract check between two binaries. 2// 3// nx_routeguard <live-artifact> <candidate-artifact> 4// 5// Answers ONE question: does the candidate still serve every /api/ route the live one does? 6// exit 0 GREEN (superset -- safe on this axis) | exit 3 RED (routes vanished) | exit 2 REFUSED. 7// 8// USE IT BEFORE EVERY mgmt DEPLOY. The 2026-07-30 regression (5th occurrence) happened because a 9// 528323-byte artifact was promoted over a 575195-byte one and nothing compared their surfaces -- 10// the deploy reported success while /api/gate_run and /api/proc_kill silently vanished, and the 11// sovereign process-kill verb was gone exactly when a runaway process needed killing. 12// 13// REFUSES RATHER THAN BLESSES: if the LIVE image yields zero routes, this exits 2, because 14// "0 missing" computed from an empty expectation reads as SAFE while guaranteeing nothing. A guard 15// that cannot see its subject must say so, not pass it. 16// license_tier: ORIGINAL Read-only -- it never promotes, deletes or writes. (Rule 26) expect_exit: 0 17import "nx_routeguard_lib.nx" 18 19const RGO_OUT: i64 = 65536 20 21func rgo_cat(o: *u8, at: i64, s: *u8) -> i64 { 22 var a: i64 = at 23 var i: i64 = 0 24 while s[i] != (0 as u8) { o[a] = s[i]; a = a + 1; i = i + 1 } 25 return a 26} 27func rgo_num(o: *u8, at: i64, v: i64) -> i64 { 28 if v == 0 { o[at] = 48 as u8; return at + 1 } 29 let t: *u8 = sys_mmap(32) 30 var n: i64 = 0 31 var x: i64 = v 32 while x > 0 { t[n] = ((x % 10) + 48) as u8; x = x / 10; n = n + 1 } 33 var a: i64 = at 34 while n > 0 { n = n - 1; o[a] = t[n]; a = a + 1 } 35 return a 36} 37 38func main(argc: i64, argv: *i64) -> i64 { 39 let o: *u8 = sys_mmap(RGO_OUT) 40 var b: i64 = 0 41 if argc < 3 { 42 b = rgo_cat(o, b, "usage: nx_routeguard <live-artifact> <candidate-artifact>\n does the candidate still serve every /api/ route the live one does?\n exit 0 GREEN | 3 RED (routes vanished) | 2 REFUSED (cannot see the subject)\n" as *u8) 43 sys_write(1, o, b) 44 return 1 45 } 46 let livep: *u8 = argv[1] as *u8 47 let candp: *u8 = argv[2] as *u8 48 49 let lbuf: *u8 = sys_mmap(RG_BUF) 50 let cbuf: *u8 = sys_mmap(RG_BUF) 51 let ln: i64 = rg_read(livep, lbuf, RG_BUF) 52 let cn: i64 = rg_read(candp, cbuf, RG_BUF) 53 if ln <= 0 { 54 b = rgo_cat(o, b, "ROUTEGUARD REFUSED -- cannot read LIVE artifact: " as *u8) 55 b = rgo_cat(o, b, livep); b = rgo_cat(o, b, "\n" as *u8) 56 sys_write(2, o, b); return 2 57 } 58 if cn <= 0 { 59 b = rgo_cat(o, b, "ROUTEGUARD REFUSED -- cannot read CANDIDATE artifact: " as *u8) 60 b = rgo_cat(o, b, candp); b = rgo_cat(o, b, "\n" as *u8) 61 sys_write(2, o, b); return 2 62 } 63 64 let names: *u8 = sys_mmap(RG_MAXR * RG_NAMEMAX) 65 let lens: *i64 = sys_mmap(RG_MAXR * 8) as *i64 66 let trunc: *i64 = sys_mmap(16) as *i64 67 let miss: *i64 = sys_mmap(RG_MAXR * 8) as *i64 68 let lcnt: i64 = rg_extract(lbuf, ln, names, lens, trunc) 69 if lcnt == 0 { 70 b = rgo_cat(o, b, "ROUTEGUARD REFUSED -- the LIVE artifact yields ZERO /api/ routes, so there is no\n contract to check. Reporting '0 missing' from an empty expectation would read as SAFE\n while guaranteeing nothing. Point this at the real live binary.\n" as *u8) 71 sys_write(2, o, b); return 2 72 } 73 let m: i64 = rg_missing(names, lens, lcnt, cbuf, cn, miss, RG_MAXR) 74 75 b = rgo_cat(o, b, "=== ROUTEGUARD -- deploy contract (route superset) ===\n live=" as *u8) 76 b = rgo_cat(o, b, livep) 77 b = rgo_cat(o, b, " bytes="); b = rgo_num(o, b, ln) 78 b = rgo_cat(o, b, "\n cand=" as *u8) 79 b = rgo_cat(o, b, candp) 80 b = rgo_cat(o, b, " bytes="); b = rgo_num(o, b, cn) 81 b = rgo_cat(o, b, "\n routes_live=" as *u8); b = rgo_num(o, b, lcnt) 82 b = rgo_cat(o, b, " routes_missing=" as *u8); b = rgo_num(o, b, m) 83 // NO SILENT CAPS: a truncated extraction guards a SUBSET, and the caller must know. 84 b = rgo_cat(o, b, " extract_truncated=" as *u8); b = rgo_num(o, b, trunc[0]) 85 b = rgo_cat(o, b, "\n" as *u8) 86 if m == 0 { 87 b = rgo_cat(o, b, " GREEN -- the candidate is a route SUPERSET; safe to promote on this axis.\n (Adding routes is fine; only DISAPPEARANCE is a deploy-contract regression.)\n" as *u8) 88 sys_write(1, o, b) 89 return 0 90 } 91 b = rgo_cat(o, b, "\n RED -- DEPLOY CONTRACT REGRESSION. These routes are served LIVE and are ABSENT\n from the candidate. Promoting it would delete working API surface:\n" as *u8) 92 var i: i64 = 0 93 var shown: i64 = 0 94 while i < m { 95 if shown < RG_MAXR { 96 if i < RG_MAXR { 97 let idx: i64 = miss[i] 98 b = rgo_cat(o, b, " VANISHED " as *u8) 99 b = rgo_cat(o, b, (names as i64 + idx * RG_NAMEMAX) as *u8) 100 b = rgo_cat(o, b, "\n" as *u8) 101 shown = shown + 1 102 } 103 } 104 i = i + 1 105 } 106 b = rgo_cat(o, b, " Do NOT promote. Rebuild the candidate from a source tree that still carries these\n routes, then re-run. If the loss is DELIBERATE, say so explicitly at the deploy call.\n" as *u8) 107 sys_write(1, o, b) 108 return 3 109}