code wiki / _hdl_build / nx_api_contract.nx
nx_api_contract.nx source
↩ module page · 230 lines · 11974 B
1// nx_api_contract.nx -- API-CONTRACT REGRESSION GUARD (operator: "build the capabilities to prevent things
2// like this" -- a sibling mgmt-API redeploy silently DROPPED /api/promote 404, breaking organ deploys for
3// EVERY session; enforces CLAUDE rule-19 API contract stability). Data-driven: a canonical manifest of
4// critical routes (knowledge/registry/api_critical_routes.tsv) is the CONTRACT; the cron probe
5// (nx_api_contract_probe.sh, via sovereign nx_https_get) writes the live VERDICT to knowledge/status/
6// api_contract.log. THIS organ is the agent/MCP surface: it reports the contract + the last supervised
7// verdict so a session can check contract integrity BEFORE trusting a deploy.
8// nx_api_contract check [manifest] [verdictlog] (JSON: routes + last verdict + status)
9// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
10import "nx_syscalls.nx"
11const AC_MAGIC_4096: i64 = 4096
12
13const AC_CAP: i64 = 262144
14const AC_TAB: i64 = 9
15const AC_NL: i64 = 10
16const AC_HASH: i64 = 35
17const AC_STDERR: i64 = 2
18const AC_SPAN: i64 = 16
19const AC_EXIT_USAGE: i64 = 2
20
21func ac_werr(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(AC_STDERR, s, n); return 0 }
22func ac_vlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
23func ac_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 } return o }
24func ac_catn(d: *u8, o: i64, v: i64) -> i64 { let t: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 } var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { d[o] = t[k-1-i]; o = o + 1; i = i + 1 } return o }
25func ac_cat_esc(d: *u8, o: i64, q: *u8, s: i64, e: i64) -> i64 { var i: i64 = s; while i < e { var c: i64 = q[i] as i64; if c == 34 { c = 39 } if c == 92 { c = 47 } if c < 32 { c = 32 } d[o] = c as u8; o = o + 1; i = i + 1 } return o }
26func ac_read(path: *u8, buf: *u8, cap: i64) -> i64 {
27 let fd: i64 = sys_openat_rd(path)
28 if fd < 0 { return 0 - 1 }
29 var n: i64 = 0
30 var go: i64 = 1
31 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64) + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } }
32 sys_close(fd)
33 return n
34}
35func ac_starts(buf: *u8, n: i64, lit: *u8) -> i64 { var i: i64 = 0; while lit[i] != (0 as u8) { if i >= n { return 0 } if buf[i] != lit[i] { return 0 } i = i + 1 } return 1 }
36func ac_le(q: *u8, i: i64, n: i64) -> i64 { var e: i64 = i; var s: i64 = 1; while s == 1 { if e >= n { s = 0 } else { if q[e] == (AC_NL as u8) { s = 0 } else { e = e + 1 } } } return e }
37func ac_col(q: *u8, ls: i64, le: i64, c: i64, out: *i64) -> i64 {
38 var col: i64 = 0
39 var p: i64 = ls
40 while col < c {
41 var s: i64 = 1
42 while s == 1 { if p >= le { return 0 } if q[p] == (AC_TAB as u8) { s = 0 } else { p = p + 1 } }
43 p = p + 1
44 col = col + 1
45 }
46 var e: i64 = p
47 var s2: i64 = 1
48 while s2 == 1 { if e >= le { s2 = 0 } else { if q[e] == (AC_TAB as u8) { s2 = 0 } else { e = e + 1 } } }
49 out[0] = p
50 out[1] = e
51 return 1
52}
53func ac_is_word(c: i64) -> i64 { if c >= 97 { if c <= 122 { return 1 } } if c == 95 { return 1 } return 0 }
54// substring match where <needle> is a COMPLETE /api/[a-z_]+ token (next char not a word char) -- mirrors the
55// cron probe's `grep -oE '/api/[a-z_]+' | grep -Fxq` semantics, so a manifest prefix like /api/tools matches
56// "/api/tools/register" (next char '/') while /api/promote does NOT falsely match /api/promote_content alone.
57func ac_contains_token(hay: *u8, hn: i64, needle: *u8) -> i64 {
58 let nl: i64 = ac_vlen(needle)
59 if nl == 0 { return 0 }
60 var i: i64 = 0
61 while i + nl <= hn {
62 var m: i64 = 1
63 var j: i64 = 0
64 while j < nl { if hay[i+j] != needle[j] { m = 0; j = nl } else { j = j + 1 } }
65 if m == 1 {
66 var nxt: i64 = 0
67 if i + nl < hn { nxt = hay[i+nl] as i64 }
68 if ac_is_word(nxt) == 0 { return 1 }
69 }
70 i = i + 1
71 }
72 return 0
73}
74// fork+exec ./nx_https_get.elf <url> <connect-override> capturing stdout+stderr to outfile. Empty envp (the
75// fetch tool needs none). Same idiom as nx_deploy_lib dep_run_capture; runs on the NAS (tools-daemon cwd).
76func ac_run_capture(elf: *u8, a1: *u8, a2: *u8, outfile: *u8) -> i64 {
77 let pid: i64 = sys_fork()
78 if pid == 0 {
79 let fd: i64 = sys_openat_wr(outfile, 0x1a4)
80 if fd >= 0 { sys_dup3(fd, 1, 0); sys_dup3(fd, 2, 0) }
81 let argv: *i64 = sys_mmap(40) as *i64
82 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0
83 argv[0] = elf as i64; argv[1] = a1 as i64; argv[2] = a2 as i64; argv[3] = 0
84 sys_execve(elf, argv, envp)
85 sys_exit(127)
86 }
87 let st: *i64 = sys_mmap(16) as *i64
88 sys_wait4(pid, st, 0)
89 if (st[0] % 128) != 0 { return 0 - 1 }
90 return (st[0] >> 8) & 0xff
91}
92func ac_write_log(path: *u8, s: *u8, n: i64) -> i64 {
93 let fd: i64 = sys_openat_wr(path, 0x1a4)
94 if fd < 0 { return 0 }
95 sys_write(fd, s, n)
96 sys_close(fd)
97 return 1
98}
99// PROBE: the ON-DEMAND / INLINE live route-contract check (seq162 mandatory-inline half; read-only, ZERO
100// rollback authority = safe). FAIL-STATIC: an empty fetch or a response lacking the /api/health sentinel ->
101// VERDICT=UNKNOWN (never trust a failed sensor). Else diff the manifest routes vs the live /api and emit+log
102// GREEN/REGRESSION. A deployer runs this right after /api/deploy for a zero-lag verdict (vs the 10-min cron).
103func ac_do_probe(mpath: *u8, vpath: *u8) -> i64 {
104 let cap: *u8 = sys_mmap(AC_CAP)
105 ac_run_capture("./nx_https_get.elf" as *u8, "https://nishifamily.com/api" as *u8, "127.0.0.1:8443" as *u8, "/tmp/nx_ac_probe.out" as *u8)
106 let rn: i64 = ac_read("/tmp/nx_ac_probe.out" as *u8, cap, AC_CAP)
107 let tsb: *i64 = sys_mmap(16) as *i64
108 sys_clock_gettime_real(tsb)
109 let out: *u8 = sys_mmap(AC_CAP)
110 var o: i64 = 0
111 var trust: i64 = 1
112 if rn <= 0 { trust = 0 }
113 if trust == 1 { if ac_contains_token(cap, rn, "/api/health" as *u8) == 0 { trust = 0 } }
114 if trust == 0 {
115 let ub: *u8 = sys_mmap(AC_MAGIC_4096)
116 var uo: i64 = ac_cat(ub, 0, "VERDICT=UNKNOWN api-contract probe-fetch-failed (empty or no /api/health sentinel; fail-static, no diff) ts=" as *u8)
117 uo = ac_catn(ub, uo, tsb[0]); uo = ac_cat(ub, uo, "\n" as *u8)
118 ac_write_log(vpath, ub, uo)
119 o = ac_cat(out, 0, "{\"verb\":\"probe\",\"verdict\":\"UNKNOWN\",\"reason\":\"probe-fetch-failed-fail-static\"}\n" as *u8)
120 sys_write(1, out, o); sys_exit(0); return 0
121 }
122 let man: *u8 = sys_mmap(AC_CAP)
123 let mn: i64 = ac_read(mpath, man, AC_CAP)
124 let c0: *i64 = sys_mmap(AC_SPAN) as *i64
125 var total: i64 = 0
126 var missing: i64 = 0
127 o = ac_cat(out, o, "{\"verb\":\"probe\",\"missing\":[" as *u8)
128 var i: i64 = 0
129 while i < mn {
130 let le: i64 = ac_le(man, i, mn)
131 if le > i { if (man[i] as i64) != AC_HASH {
132 if ac_col(man, i, le, 0, c0) == 1 {
133 let rp: *u8 = sys_mmap(128)
134 var k: i64 = 0
135 var p: i64 = c0[0]
136 while p < c0[1] { rp[k] = man[p]; k = k + 1; p = p + 1 }
137 rp[k] = 0 as u8
138 total = total + 1
139 if ac_contains_token(cap, rn, rp) == 0 {
140 if missing > 0 { o = ac_cat(out, o, "," as *u8) }
141 o = ac_cat(out, o, "\"" as *u8)
142 o = ac_cat_esc(out, o, man, c0[0], c0[1])
143 o = ac_cat(out, o, "\"" as *u8)
144 missing = missing + 1
145 }
146 }
147 } }
148 i = le + 1
149 }
150 o = ac_cat(out, o, "],\"routes_total\":" as *u8)
151 o = ac_catn(out, o, total)
152 o = ac_cat(out, o, ",\"missing_count\":" as *u8)
153 o = ac_catn(out, o, missing)
154 o = ac_cat(out, o, ",\"verdict\":\"" as *u8)
155 let lb: *u8 = sys_mmap(AC_MAGIC_4096)
156 var lo: i64 = 0
157 if missing == 0 {
158 o = ac_cat(out, o, "GREEN\"}\n" as *u8)
159 lo = ac_cat(lb, 0, "VERDICT=GREEN api-contract " as *u8)
160 lo = ac_catn(lb, lo, total); lo = ac_cat(lb, lo, "/" as *u8); lo = ac_catn(lb, lo, total)
161 lo = ac_cat(lb, lo, " critical routes present (on-demand probe) ts=" as *u8)
162 } else {
163 o = ac_cat(out, o, "REGRESSION\"}\n" as *u8)
164 lo = ac_cat(lb, 0, "VERDICT=REGRESSION api-contract " as *u8)
165 lo = ac_catn(lb, lo, total - missing); lo = ac_cat(lb, lo, "/" as *u8); lo = ac_catn(lb, lo, total)
166 lo = ac_cat(lb, lo, " present MISSING=" as *u8); lo = ac_catn(lb, lo, missing)
167 lo = ac_cat(lb, lo, " (on-demand probe) ts=" as *u8)
168 }
169 lo = ac_catn(lb, lo, tsb[0]); lo = ac_cat(lb, lo, "\n" as *u8)
170 ac_write_log(vpath, lb, lo)
171 sys_write(1, out, o); sys_exit(0); return 0
172}
173func main(argc: i64, argv: *i64) -> i64 {
174 var verb: *u8 = "check" as *u8
175 if argc > 1 { verb = argv[1] as *u8 }
176 if ac_starts(verb, ac_vlen(verb), "probe" as *u8) == 1 {
177 var mp: *u8 = "knowledge/registry/api_critical_routes.tsv" as *u8
178 var vp: *u8 = "knowledge/status/api_contract.log" as *u8
179 if argc > 2 { mp = argv[2] as *u8 }
180 if argc > 3 { vp = argv[3] as *u8 }
181 return ac_do_probe(mp, vp)
182 }
183 if ac_starts(verb, ac_vlen(verb), "check" as *u8) == 0 { ac_werr("usage: nx_api_contract check|probe [manifest] [verdictlog]\n" as *u8); sys_exit(AC_EXIT_USAGE); return AC_EXIT_USAGE }
184 var mpath: *u8 = "knowledge/registry/api_critical_routes.tsv" as *u8
185 var vpath: *u8 = "knowledge/status/api_contract.log" as *u8
186 if argc > 2 { mpath = argv[2] as *u8 }
187 if argc > 3 { vpath = argv[3] as *u8 }
188 let man: *u8 = sys_mmap(AC_CAP)
189 let mn: i64 = ac_read(mpath, man, AC_CAP)
190 let vbuf: *u8 = sys_mmap(AC_CAP)
191 let vn: i64 = ac_read(vpath, vbuf, AC_CAP)
192 let out: *u8 = sys_mmap(AC_CAP)
193 var o: i64 = 0
194 o = ac_cat(out, o, "{\"verb\":\"check\",\"contract\":[" as *u8)
195 let c0: *i64 = sys_mmap(AC_SPAN) as *i64
196 let c1: *i64 = sys_mmap(AC_SPAN) as *i64
197 var routes: i64 = 0
198 var i: i64 = 0
199 while i < mn {
200 let le: i64 = ac_le(man, i, mn)
201 if le > i { if (man[i] as i64) != AC_HASH {
202 if ac_col(man, i, le, 0, c0) == 1 {
203 if routes > 0 { o = ac_cat(out, o, "," as *u8) }
204 o = ac_cat(out, o, "{\"route\":\"" as *u8)
205 o = ac_cat_esc(out, o, man, c0[0], c0[1])
206 o = ac_cat(out, o, "\",\"why\":\"" as *u8)
207 if ac_col(man, i, le, 1, c1) == 1 { o = ac_cat_esc(out, o, man, c1[0], c1[1]) }
208 o = ac_cat(out, o, "\"}" as *u8)
209 routes = routes + 1
210 }
211 } }
212 i = le + 1
213 }
214 o = ac_cat(out, o, "],\"routes_count\":" as *u8)
215 o = ac_catn(out, o, routes)
216 // last supervised verdict (from the cron probe)
217 o = ac_cat(out, o, ",\"last_verdict\":\"" as *u8)
218 var green: i64 = 0
219 if ac_starts(vbuf, vn, "VERDICT=GREEN" as *u8) == 1 { green = 1; o = ac_cat(out, o, "GREEN" as *u8) } else { if vn > 0 { o = ac_cat(out, o, "REGRESSION" as *u8) } else { o = ac_cat(out, o, "UNKNOWN" as *u8) } }
220 o = ac_cat(out, o, "\",\"verdict_line\":\"" as *u8)
221 // the verdict line, up to newline, escaped
222 var vle: i64 = 0
223 if vn > 0 { vle = ac_le(vbuf, 0, vn); o = ac_cat_esc(out, o, vbuf, 0, vle) }
224 o = ac_cat(out, o, "\",\"status\":\"" as *u8)
225 if green == 1 { o = ac_cat(out, o, "GREEN" as *u8) } else { if vn > 0 { o = ac_cat(out, o, "REGRESSION" as *u8) } else { o = ac_cat(out, o, "UNMONITORED" as *u8) } }
226 o = ac_cat(out, o, "\",\"envelope\":\"critical-route manifest + verdict log each read bounded at AC_CAP 256KiB; a larger file reads TRUNCATED and a route could be judged missing -- declared per the scale-law (F846), never silent\"}\n" as *u8)
227 sys_write(1, out, o)
228 sys_exit(0)
229 return 0
230}