code wiki / _hdl_build / nx_cms_draft_gate.nx
nx_cms_draft_gate.nx source
↩ module page · 365 lines · 15562 B
1// nx_cms_draft_gate.nx -- CMS C9 GATE (re-runnable, evidence-driven): draft-preview mode over real
2// HTTP against the real admin daemon. Proves the TEAM-AUTHORED _pe_draft lifecycle at the wire:
3// publish/discard with no draft are REFUSED 409 (the table's absent transitions), a saved draft
4// never touches the public page, preview is session-only and sanitized, promote goes live atomically,
5// discard leaves the publish intact. Appends "CMSGATE row=nx_cms_draft ... verdict=PASS|FAIL" to
6// knowledge/status/cms_gate.log (the Warden board's C9 evidence row). Exit 0 iff all rows pass.
7// license_tier: ORIGINAL
8import "nx_cms_store.nx"
9import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
10import "nx_sha256.nx"
11import "nx_syscalls.nx"
12import "_hdl_build/nx_kill_portable.nx"
13
14const CD_PORT: i64 = 8081
15const CD_SITE: *u8 = "/tmp/_cms_draft_site"
16
17func cd_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
18func cd_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
19func cd_cat(dst: *u8, off: i64, s: *u8) -> i64 {
20 var o: i64 = off
21 var k: i64 = 0
22 while s[k] != (0 as u8) { dst[o] = s[k]; o = o + 1; k = k + 1 }
23 return o
24}
25func cd_catn(dst: *u8, off: i64, s: *u8, n: i64) -> i64 {
26 var o: i64 = off
27 var k: i64 = 0
28 while k < n { dst[o] = s[k]; o = o + 1; k = k + 1 }
29 return o
30}
31func cd_index(hay: *u8, n: i64, needle: *u8) -> i64 {
32 let nl: i64 = cd_len(needle)
33 var i: i64 = 0
34 while i + nl <= n {
35 var q: i64 = 0
36 var ok: i64 = 1
37 while q < nl { if (hay[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nl } q = q + 1 }
38 if ok == 1 { return i }
39 i = i + 1
40 }
41 return 0 - 1
42}
43func cd_has(hay: *u8, n: i64, needle: *u8) -> i64 { if cd_index(hay, n, needle) >= 0 { return 1 } return 0 }
44
45func cd_http(req: *u8, rl: i64, resp: *u8, cap: i64) -> i64 {
46 let fd: i64 = sys_socket(2, 1, 0)
47 if fd < 0 { return 0 - 1 }
48 let addr: *u8 = sys_mmap(16)
49 addr[0] = 2 as u8; addr[1] = 0 as u8
50 addr[2] = ((CD_PORT >> 8) & 0xff) as u8
51 addr[3] = (CD_PORT & 0xff) as u8
52 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8
53 var zi: i64 = 8
54 while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 }
55 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 }
56 var off: i64 = 0
57 var go: i64 = 1
58 while go == 1 {
59 go = 0
60 if off < rl {
61 let w: i64 = sys_write(fd, (req + off) as *u8, rl - off)
62 if w > 0 { off = off + w; go = 1 }
63 }
64 }
65 sys_set_socket_timeout(fd, 10)
66 var total: i64 = 0
67 go = 1
68 while go == 1 {
69 go = 0
70 if total < cap {
71 let r: i64 = sys_read(fd, (resp + total) as *u8, cap - total)
72 if r > 0 { total = total + r; go = 1 }
73 }
74 }
75 sys_close(fd)
76 return total
77}
78
79func cd_post(req: *u8, path: *u8, cookie: *u8, body: *u8) -> i64 {
80 var o: i64 = cd_cat(req, 0, "POST " as *u8)
81 o = cd_cat(req, o, path)
82 o = cd_cat(req, o, " HTTP/1.1\r\nHost: gate\r\nContent-Type: application/x-www-form-urlencoded\r\n" as *u8)
83 if (cookie[0] as i64) != 0 {
84 o = cd_cat(req, o, "Cookie: nsess=" as *u8)
85 o = cd_catn(req, o, cookie, 32)
86 o = cd_cat(req, o, "\r\n" as *u8)
87 }
88 o = cd_cat(req, o, "Content-Length: " as *u8)
89 let bl: i64 = cd_len(body)
90 let t: *u8 = sys_mmap(24)
91 var m: i64 = bl
92 var k: i64 = 0
93 if m == 0 { t[0] = 48 as u8; k = 1 }
94 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
95 var i: i64 = 0
96 while i < k { req[o] = t[k-1-i]; o = o + 1; i = i + 1 }
97 o = cd_cat(req, o, "\r\n\r\n" as *u8)
98 o = cd_cat(req, o, body)
99 return o
100}
101func cd_get(req: *u8, path: *u8, cookie: *u8) -> i64 {
102 var o: i64 = cd_cat(req, 0, "GET " as *u8)
103 o = cd_cat(req, o, path)
104 o = cd_cat(req, o, " HTTP/1.1\r\nHost: gate\r\n" as *u8)
105 if (cookie[0] as i64) != 0 {
106 o = cd_cat(req, o, "Cookie: nsess=" as *u8)
107 o = cd_catn(req, o, cookie, 32)
108 o = cd_cat(req, o, "\r\n" as *u8)
109 }
110 o = cd_cat(req, o, "\r\n" as *u8)
111 return o
112}
113
114func cd_row(id: i64, ok: i64, what: *u8) -> i64 {
115 cd_w("DRAFTROW " as *u8)
116 let d: *u8 = sys_mmap(8); d[0] = (48 + id/10) as u8; d[1] = (48 + id%10) as u8; d[2] = 32 as u8; d[3] = 0 as u8
117 cd_w(d)
118 if ok == 1 { cd_w("PASS " as *u8) }
119 if ok == 0 { cd_w("FAIL " as *u8) }
120 cd_w(what); cd_w("\n" as *u8)
121 return ok
122}
123
124func cd_mkdir(path: *u8, mode: i64) -> i64 {
125 let nb: *i64 = sys_mmap(8) as *i64
126 nb[0] = 258 // x86_64 mkdirat (runtime-computed escape hatch)
127 return __syscall(nb[0], 0 - 100, path, mode, 0, 0, 0)
128}
129
130func main() -> i64 {
131 // ---- fresh gate site (stale draft state from prior runs is set aside, never trusted) ----
132 cd_mkdir(CD_SITE, 0x1ed)
133 cst_copy("knowledge/cms/andelinwest/template.html" as *u8, "/tmp/_cms_draft_site/template.html" as *u8)
134 cst_copy("knowledge/cms/andelinwest/content.txt" as *u8, "/tmp/_cms_draft_site/content.txt" as *u8)
135 sys_renameat("/tmp/_cms_draft_site/draft.txt" as *u8, "/tmp/_cms_draft_site/draft.stale" as *u8)
136 let dig: *u8 = sys_mmap(32)
137 sha256_digest("draft-gate-pw-1" as *u8, 15, dig)
138 let hexs: *u8 = sys_mmap(80)
139 let hxc: *u8 = "0123456789abcdef" as *u8
140 var i: i64 = 0
141 while i < 32 {
142 hexs[i*2] = hxc[((dig[i] as i64) >> 4) & 15]
143 hexs[i*2+1] = hxc[(dig[i] as i64) & 15]
144 i = i + 1
145 }
146 let pf: i64 = sys_openat_wr("/tmp/_cms_draft_site/admin_pw.sha256" as *u8, 0x1a4)
147 sys_write(pf, hexs, 64)
148 sys_close(pf)
149
150 // ---- rebuild the admin sovereignly (prove-all: /tmp contested) ----
151 let bpid: i64 = sys_fork()
152 if bpid == 0 {
153 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
154 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) }
155 let bargv: *i64 = sys_mmap(32) as *i64
156 bargv[0] = "_offc/nx_sov_build_run.elf" as *u8 as i64
157 bargv[1] = "nx_cms_admin" as *u8 as i64
158 bargv[2] = 0
159 let benvp: *i64 = sys_mmap(16) as *i64
160 benvp[0] = 0
161 sys_execve("_offc/nx_sov_build_run.elf" as *u8, bargv, benvp); sys_exit(127)
162 }
163 let bst: *i64 = sys_mmap(16) as *i64
164 sys_wait4(bpid, bst, 0)
165 // CALLER-GAP FIX 2026-08-01 (seq1267 class): the runner emits buildroot/_build/<t>.sov.elf under
166 // the no-/tmp doctrine while this probed /tmp. Proven by control: _build copy 208962B, /tmp absent.
167 var bchk: i64 = sys_openat_rd("buildroot/_build/nx_cms_admin.sov.elf" as *u8)
168 var badm: *u8 = "buildroot/_build/nx_cms_admin.sov.elf" as *u8
169 if bchk < 0 { bchk = sys_openat_rd("/tmp/nx_cms_admin.sov.elf" as *u8); badm = "/tmp/nx_cms_admin.sov.elf" as *u8 }
170 if bchk < 0 { cd_w("CMSGATE row=nx_cms_draft ADMIN-BUILD-MISSING (probed buildroot/_build AND /tmp)\n" as *u8); sys_exit(1) }
171 sys_close(bchk)
172
173 // ---- boot ----
174 let pid: i64 = sys_fork()
175 if pid == 0 {
176 let lg: i64 = sys_openat_wr("/tmp/_cms_admin_draft_gate.log" as *u8, 0x1a4)
177 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0) }
178 let argvv: *i64 = sys_mmap(8*6) as *i64
179 argvv[0] = badm as i64
180 argvv[1] = "8081" as *u8 as i64
181 argvv[2] = CD_SITE as i64
182 argvv[3] = "99" as *u8 as i64
183 argvv[4] = 0
184 let envp: *i64 = sys_mmap(16) as *i64
185 envp[0] = 0
186 sys_execve(badm, argvv, envp)
187 sys_exit(127)
188 }
189
190 let req: *u8 = sys_mmap(262144)
191 let resp: *u8 = sys_mmap(524288)
192 let nocookie: *u8 = sys_mmap(8)
193 nocookie[0] = 0 as u8
194 let sess: *u8 = sys_mmap(64)
195 sess[0] = 0 as u8
196 let csrf: *u8 = sys_mmap(64)
197 csrf[0] = 0 as u8
198
199 // D0: boot-poll + seed serves
200 var rn: i64 = 0 - 1
201 var tries: i64 = 0
202 while tries < 50 {
203 let rl0: i64 = cd_get(req, "/" as *u8, nocookie)
204 rn = cd_http(req, rl0, resp, 524287)
205 if rn > 0 { tries = 50 }
206 if rn <= 0 { sys_sleep_ms(100); tries = tries + 1 }
207 }
208 var pass: i64 = 0
209 var rows: i64 = 0
210 var ok: i64 = 0
211 if rn > 0 { if cd_has(resp, rn, "200 OK" as *u8) == 1 { if cd_has(resp, rn, "Andelin West Law" as *u8) == 1 { ok = 1 } } }
212 rows = rows + 1; pass = pass + cd_row(0, ok, "seed render serves GET /" as *u8)
213
214 // D1: login -> session
215 var rl: i64 = cd_post(req, "/admin/login" as *u8, nocookie, "pw=draft-gate-pw-1" as *u8)
216 rn = cd_http(req, rl, resp, 524287)
217 ok = 0
218 if rn > 0 { if cd_has(resp, rn, "303" as *u8) == 1 {
219 let sp: i64 = cd_index(resp, rn, "nsess=" as *u8)
220 if sp >= 0 {
221 var m2: i64 = 0
222 while m2 < 32 { sess[m2] = resp[sp+6+m2]; m2 = m2 + 1 }
223 sess[32] = 0 as u8
224 ok = 1
225 }
226 } }
227 rows = rows + 1; pass = pass + cd_row(1, ok, "login issues session" as *u8)
228
229 // D2: editor shows the draft controls + csrf
230 rl = cd_get(req, "/admin" as *u8, sess)
231 rn = cd_http(req, rl, resp, 524287)
232 ok = 0
233 if rn > 0 { if cd_has(resp, rn, "Publish pending draft" as *u8) == 1 { if cd_has(resp, rn, "Save as draft" as *u8) == 1 {
234 let cp: i64 = cd_index(resp, rn, "name=\"csrf\" value=\"" as *u8)
235 if cp >= 0 {
236 var m3: i64 = 0
237 while m3 < 32 { csrf[m3] = resp[cp+19+m3]; m3 = m3 + 1 }
238 csrf[32] = 0 as u8
239 ok = 1
240 }
241 } } }
242 rows = rows + 1; pass = pass + cd_row(2, ok, "editor shows draft controls + csrf" as *u8)
243
244 // D3: publish with NO draft -> 409 (the team table's absent transition, refused at the wire)
245 let bodyp: *u8 = sys_mmap(256)
246 var bp: i64 = cd_cat(bodyp, 0, "csrf=" as *u8)
247 bp = cd_catn(bodyp, bp, csrf, 32)
248 bodyp[bp] = 0 as u8
249 rl = cd_post(req, "/admin/publish" as *u8, sess, bodyp)
250 rn = cd_http(req, rl, resp, 524287)
251 ok = 0
252 if rn > 0 { if cd_has(resp, rn, "409" as *u8) == 1 { ok = 1 } }
253 rows = rows + 1; pass = pass + cd_row(3, ok, "publish with no draft refused 409" as *u8)
254
255 // D4: discard with NO draft -> 409
256 rl = cd_post(req, "/admin/discard" as *u8, sess, bodyp)
257 rn = cd_http(req, rl, resp, 524287)
258 ok = 0
259 if rn > 0 { if cd_has(resp, rn, "409" as *u8) == 1 { ok = 1 } }
260 rows = rows + 1; pass = pass + cd_row(4, ok, "discard with no draft refused 409" as *u8)
261
262 // D5: save mode=draft (with injected script) -> 303 AND public page UNCHANGED
263 let body5: *u8 = sys_mmap(4096)
264 var b5: i64 = cd_cat(body5, 0, "csrf=" as *u8)
265 b5 = cd_catn(body5, b5, csrf, 32)
266 b5 = cd_cat(body5, b5, "&mode=draft&hero_title=Draft+Headline+Pending¬e_html=%3Cscript%3Ealert%282%29%3C%2Fscript%3E%3Ci%3EDraft+note.%3C%2Fi%3E" as *u8)
267 body5[b5] = 0 as u8
268 rl = cd_post(req, "/admin/save" as *u8, sess, body5)
269 rn = cd_http(req, rl, resp, 524287)
270 ok = 0
271 if rn > 0 { if cd_has(resp, rn, "303" as *u8) == 1 {
272 let rl5: i64 = cd_get(req, "/" as *u8, nocookie)
273 let rn5: i64 = cd_http(req, rl5, resp, 524287)
274 if rn5 > 0 { if cd_has(resp, rn5, "Draft Headline Pending" as *u8) == 0 { if cd_has(resp, rn5, "Andelin West Law" as *u8) == 1 { ok = 1 } } }
275 } }
276 rows = rows + 1; pass = pass + cd_row(5, ok, "draft saved 303 + public page UNTOUCHED" as *u8)
277
278 // D6: authed preview shows the draft, sanitized
279 rl = cd_get(req, "/admin/preview" as *u8, sess)
280 rn = cd_http(req, rl, resp, 524287)
281 ok = 0
282 if rn > 0 { if cd_has(resp, rn, "200 OK" as *u8) == 1 { if cd_has(resp, rn, "Draft Headline Pending" as *u8) == 1 { if cd_has(resp, rn, "<i>Draft note.</i>" as *u8) == 1 { if cd_has(resp, rn, "<script" as *u8) == 0 { ok = 1 } } } } }
283 rows = rows + 1; pass = pass + cd_row(6, ok, "authed preview shows draft, XSS sanitized" as *u8)
284
285 // D7: unauthed preview refused (no draft leak before publish)
286 rl = cd_get(req, "/admin/preview" as *u8, nocookie)
287 rn = cd_http(req, rl, resp, 524287)
288 ok = 0
289 if rn > 0 { if cd_has(resp, rn, "403" as *u8) == 1 { if cd_has(resp, rn, "Draft Headline Pending" as *u8) == 0 { ok = 1 } } }
290 rows = rows + 1; pass = pass + cd_row(7, ok, "unauthed preview refused 403" as *u8)
291
292 // D8: publish the draft -> live
293 rl = cd_post(req, "/admin/publish" as *u8, sess, bodyp)
294 rn = cd_http(req, rl, resp, 524287)
295 ok = 0
296 if rn > 0 { if cd_has(resp, rn, "303" as *u8) == 1 {
297 let rl8: i64 = cd_get(req, "/" as *u8, nocookie)
298 let rn8: i64 = cd_http(req, rl8, resp, 524287)
299 if rn8 > 0 { if cd_has(resp, rn8, "Draft Headline Pending" as *u8) == 1 { ok = 1 } }
300 } }
301 rows = rows + 1; pass = pass + cd_row(8, ok, "publish promotes draft to the live page" as *u8)
302
303 // D9: second draft then discard -> publish intact, preview gone
304 let body9: *u8 = sys_mmap(4096)
305 var b9: i64 = cd_cat(body9, 0, "csrf=" as *u8)
306 b9 = cd_catn(body9, b9, csrf, 32)
307 b9 = cd_cat(body9, b9, "&mode=draft&hero_title=Second+Draft+Marker" as *u8)
308 body9[b9] = 0 as u8
309 rl = cd_post(req, "/admin/save" as *u8, sess, body9)
310 rn = cd_http(req, rl, resp, 524287)
311 ok = 0
312 if rn > 0 { if cd_has(resp, rn, "303" as *u8) == 1 {
313 rl = cd_post(req, "/admin/discard" as *u8, sess, bodyp)
314 rn = cd_http(req, rl, resp, 524287)
315 if rn > 0 { if cd_has(resp, rn, "303" as *u8) == 1 {
316 let rl9: i64 = cd_get(req, "/" as *u8, nocookie)
317 let rn9: i64 = cd_http(req, rl9, resp, 524287)
318 var pubok: i64 = 0
319 if rn9 > 0 { if cd_has(resp, rn9, "Second Draft Marker" as *u8) == 0 { if cd_has(resp, rn9, "Draft Headline Pending" as *u8) == 1 { pubok = 1 } } }
320 let rlA: i64 = cd_get(req, "/admin/preview" as *u8, sess)
321 let rnA: i64 = cd_http(req, rlA, resp, 524287)
322 if rnA > 0 { if cd_has(resp, rnA, "404" as *u8) == 1 { if pubok == 1 { ok = 1 } } }
323 } }
324 } }
325 rows = rows + 1; pass = pass + cd_row(9, ok, "discard sets draft aside; publish intact; preview 404" as *u8)
326
327 // ---- teardown ----
328 nxk_kill(pid, 9)
329 let stbuf: *i64 = sys_mmap(16) as *i64
330 sys_wait4(pid, stbuf, 0)
331
332 var verdict: *u8 = "FAIL" as *u8
333 if pass == rows { verdict = "PASS" as *u8 }
334 let line: *u8 = sys_mmap(512)
335 var lo: i64 = cd_cat(line, 0, "CMSGATE row=nx_cms_draft rows=" as *u8)
336 let t2: *u8 = sys_mmap(24)
337 var mm: i64 = rows
338 var kk: i64 = 0
339 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
340 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
341 var ii: i64 = 0
342 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
343 lo = cd_cat(line, lo, " pass=" as *u8)
344 mm = pass; kk = 0
345 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
346 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
347 ii = 0
348 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
349 lo = cd_cat(line, lo, " verdict=" as *u8)
350 lo = cd_cat(line, lo, verdict)
351 lo = cd_cat(line, lo, " epoch=" as *u8)
352 mm = sys_now_realtime_sec(); kk = 0
353 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
354 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
355 ii = 0
356 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
357 line[lo] = 10 as u8
358 lo = lo + 1
359 sys_write(1, line, lo)
360 let gf: i64 = sys_openat_append("knowledge/status/cms_gate.log" as *u8, 0x1a4)
361 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) }
362 if pass == rows { sys_exit(0) }
363 sys_exit(1)
364 return 1
365}