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}