code wiki / (root) / nx_https_post_lib_gate.nx

nx_https_post_lib_gate.nx source

↩ module page · 212 lines · 13068 B

1// nx_https_post_lib_gate.nx -- THE GATE FOR URL-LEVEL HTTPS POST, 2026-09-03. 2// 3// SUBJECT: hp_should_follow in-process, and the PURE request builder the POST path composes 4// (nx_http_client_build_request_post_xhdr), against its GET sibling as the negative control. 5// 6// WHY IT GATES THE BUILDER AND NOT A LIVE POST. A gate that opens a socket is a gate that fails when the 7// network does, and its RED would indict this code for someone else's outage. The builder is a PURE 8// function -- buffers in, request bytes out -- so every claim that matters about method, body and framing 9// is decidable with no network at all. What the builder cannot tell us (that the connect glue wires the 10// right session) is stated as a limit rather than faked: hp_post_once is COMPOSITION of hf_open, which 11// nx_https_fetch_lib_gate already proves, plus one call. 12// 13// T1 AND T5 ARE THE PAIR THAT CARRIES THIS FILE. The defect that motivated the whole lib was a function 14// named post_json that emitted a GET. T1 asserts the first four bytes are literally POST. On its own that 15// is weak -- a builder that emitted POST for everything would pass it. T5 hands the SAME arguments to the 16// GET builder and requires the result NOT to start with POST, so T1 can only pass on a builder that 17// actually distinguishes the two methods. 18// 19// T3 IS THE BODY-LOSS TOOTH. The old defect did not merely use the wrong verb; it DROPPED THE BODY and 20// still reported success. T3 parses Content-Length back out of the generated request and requires it to 21// equal body_len, and T2 requires the body bytes to appear verbatim. A builder that framed a POST and 22// silently sent nothing would pass T1 and fail both of these. 23// 24// Teeth, in order: 25// T1 the generated request's method is POST. 26// T2 the body appears VERBATIM in the generated request. 27// T3 Content-Length parsed back from the request equals body_len (the body-loss tooth). 28// T4 the Content-Type given is present in the request. 29// T5 NEG-CONTROL: the GET builder on the same arguments does NOT emit POST (so T1 discriminates). 30// T6 an empty body still frames a valid POST carrying Content-Length 0. 31// T7 ANTI-VACUITY: the request grows by exactly the extra body bytes at fixed header width. 32// T8 a redirect status is NOT followed on POST -- following one rewrites the method to GET and drops 33// the body, which is the very defect this lib repairs. 34// T9 hp_should_follow is not a stub that refuses only redirects: 200 is refused too, so the function 35// means "this lib never follows", not "this lib has an opinion about 3xx". 36// MEASURED 9/9 GREEN 2026-09-03. 37// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 38import "nx_syscalls.nx" 39import "nx_gate_verdict.nx" 40import "nx_https_post_lib.nx" 41import "nx_http_client.nx" 42 43const HPG_CAP: i64 = 16384 44const HPG_ST_OK: i64 = 200 45const HPG_ST_301: i64 = 301 46const HPG_ST_302: i64 = 302 47const HPG_ST_307: i64 = 307 48 49func hpg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 50 51// find needle in buf[0..n); index just past it, or -1. Exits on a FLAG, never by clobbering the cursor. 52func hpg_find(buf: *u8, n: i64, needle: *u8) -> i64 { 53 let m: i64 = hpg_slen(needle) 54 if m == 0 { return 0 - 1 } 55 var i: i64 = 0 56 var hit: i64 = 0 - 1 57 while i + m <= n { 58 var j: i64 = 0 59 var same: i64 = 1 60 while j < m { 61 if buf[i + j] != needle[j] { same = 0; j = m } else { j = j + 1 } 62 } 63 if same == 1 { if hit < 0 { hit = i + m } } 64 i = i + 1 65 } 66 return hit 67} 68func hpg_int_after(buf: *u8, n: i64, key: *u8) -> i64 { 69 var p: i64 = hpg_find(buf, n, key) 70 if p < 0 { return 0 - 1 } 71 // skip one optional space after the colon 72 if p < n { if buf[p] == (32 as u8) { p = p + 1 } } 73 var v: i64 = 0 74 var got: i64 = 0 75 var go: i64 = 1 76 while go == 1 { 77 if p >= n { go = 0 } else { 78 let c: i64 = buf[p] as i64 79 if c < 48 { go = 0 } else { 80 if c > 57 { go = 0 } else { v = v * 10 + (c - 48); got = 1; p = p + 1 } 81 } 82 } 83 } 84 if got == 0 { return 0 - 1 } 85 return v 86} 87 88func main(argc: i64, argv: *i64) -> i64 { 89 let ctr: *i64 = gv_ctr() 90 gv_head("nx_https_post_lib gate -- a POST must carry its method AND its body, proven on the pure builder" as *u8) 91 92 let path: *u8 = "/api/tools/call" as *u8 93 let plen: i64 = hpg_slen(path) 94 let host: *u8 = "nishifamily.com" as *u8 95 let hlen: i64 = hpg_slen(host) 96 let ctype: *u8 = "application/json" as *u8 97 let ctlen: i64 = hpg_slen(ctype) 98 let body: *u8 = "{\x22jsonrpc\x22:\x222.0\x22,\x22id\x22:7}" as *u8 99 let blen: i64 = hpg_slen(body) 100 101 let cap: i64 = nx_http_client_request_cap(plen, hlen, 0, 0) + ctlen + blen + HPG_CAP 102 let req: *u8 = sys_mmap(cap) 103 let n: i64 = nx_http_client_build_request_post_xhdr(path, plen, host, hlen, 104 ctype, ctlen, body, blen, 105 0 as *u8, 0, 0 as *u8, 0, req) 106 107 // ---- T1 the method ---- 108 var t1: i64 = 0 109 if n > 4 { 110 if req[0] == (80 as u8) { if req[1] == (79 as u8) { if req[2] == (83 as u8) { if req[3] == (84 as u8) { t1 = 1 } } } } 111 } 112 gv_puts(" [T1] request bytes=" as *u8); gv_num(n) 113 gv_puts(" first4=" as *u8); gv_num(req[0] as i64); gv_puts("," as *u8); gv_num(req[1] as i64) 114 gv_puts("," as *u8); gv_num(req[2] as i64); gv_puts("," as *u8); gv_num(req[3] as i64); gv_puts("\n" as *u8) 115 gv_check("the-generated-request-method-is-POST" as *u8, t1, ctr) 116 117 // ---- T2 the body is present verbatim ---- 118 let bp: i64 = hpg_find(req, n, body) 119 gv_puts(" [T2] body found at=" as *u8); gv_num(bp); gv_puts(" body_len=" as *u8); gv_num(blen); gv_puts("\n" as *u8) 120 gv_check("the-body-appears-VERBATIM-in-the-generated-request" as *u8, (bp > 0) as i64, ctr) 121 122 // ---- T3 THE BODY-LOSS TOOTH ---- 123 let clen: i64 = hpg_int_after(req, n, "Content-Length:" as *u8) 124 gv_puts(" [T3] Content-Length=" as *u8); gv_num(clen); gv_puts(" want=" as *u8); gv_num(blen); gv_puts("\n" as *u8) 125 gv_check("Content-Length-equals-body_len (a POST that framed correctly and sent nothing would pass T1 and fail here)" as *u8, (clen == blen) as i64, ctr) 126 127 // ---- T4 the content type ---- 128 let ctp: i64 = hpg_find(req, n, ctype) 129 gv_puts(" [T4] content-type found at=" as *u8); gv_num(ctp); gv_puts("\n" as *u8) 130 gv_check("the-declared-Content-Type-is-present-in-the-request" as *u8, (ctp > 0) as i64, ctr) 131 132 // ---- T5 NEG-CONTROL: the GET builder must NOT emit POST ---- 133 let greq: *u8 = sys_mmap(cap) 134 let gn: i64 = nx_http_client_build_request(path, plen, host, hlen, greq) 135 var t5: i64 = 1 136 if gn > 4 { 137 if greq[0] == (80 as u8) { if greq[1] == (79 as u8) { if greq[2] == (83 as u8) { if greq[3] == (84 as u8) { t5 = 0 } } } } 138 } 139 gv_puts(" [T5] GET builder first4=" as *u8); gv_num(greq[0] as i64); gv_puts("," as *u8); gv_num(greq[1] as i64) 140 gv_puts("," as *u8); gv_num(greq[2] as i64); gv_puts("," as *u8); gv_num(greq[3] as i64); gv_puts("\n" as *u8) 141 gv_check("neg-control-the-GET-builder-does-NOT-emit-POST (without this a builder emitting POST for everything would pass T1)" as *u8, t5, ctr) 142 143 // ---- T6 empty body still frames a valid POST ---- 144 let ereq: *u8 = sys_mmap(cap) 145 let en: i64 = nx_http_client_build_request_post_xhdr(path, plen, host, hlen, 146 ctype, ctlen, 0 as *u8, 0, 147 0 as *u8, 0, 0 as *u8, 0, ereq) 148 let eclen: i64 = hpg_int_after(ereq, en, "Content-Length:" as *u8) 149 gv_puts(" [T6] empty-body request bytes=" as *u8); gv_num(en) 150 gv_puts(" Content-Length=" as *u8); gv_num(eclen); gv_puts("\n" as *u8) 151 var t6: i64 = 0 152 if en > 4 { if ereq[0] == (80 as u8) { if eclen == 0 { t6 = 1 } } } 153 gv_check("an-empty-body-still-frames-a-valid-POST-carrying-Content-Length-zero" as *u8, t6, ctr) 154 155 // ---- T7 ANTI-VACUITY: the request grows by exactly the extra body bytes ---- 156 // FIRST DRAFT OF THIS TOOTH WAS WRONG AND THE GATE CAUGHT ME, 2026-09-03. It compared the 157 // 24-byte-body request against the EMPTY-body one and demanded delta == 24. Measured delta was 25, 158 // because "Content-Length: 24" is one character longer than "Content-Length: 0" -- the request grows 159 // by the body PLUS the digit-width of the length header. The builder was right; my expectation had 160 // dropped a real term. 161 // THE FIX IS NOT A FUDGE CONSTANT. Adding +1 would encode today's body length into the tooth and 162 // break the moment someone edited the fixture. Instead the confounding dimension is HELD FIXED: 163 // the same buffer is framed at two lengths that have the SAME number of decimal digits (24 and 12), 164 // so the header width is identical in both and the delta is purely body bytes. 165 // => ASK WHICH DIMENSION YOU ARE VARYING AND WHICH YOU ARE HOLDING FIXED. A defect in the held 166 // dimension is invisible to every trial, and here it was hiding in a header I never thought about. 167 let half: i64 = blen / 2 168 let hreq: *u8 = sys_mmap(cap) 169 let hn: i64 = nx_http_client_build_request_post_xhdr(path, plen, host, hlen, 170 ctype, ctlen, body, half, 171 0 as *u8, 0, 0 as *u8, 0, hreq) 172 let hclen: i64 = hpg_int_after(hreq, hn, "Content-Length:" as *u8) 173 gv_puts(" [T7] full_len=" as *u8); gv_num(blen); gv_puts(" (bytes=" as *u8); gv_num(n) 174 gv_puts(") half_len=" as *u8); gv_num(half); gv_puts(" (bytes=" as *u8); gv_num(hn) 175 gv_puts(") delta=" as *u8); gv_num(n - hn) 176 gv_puts(" want=" as *u8); gv_num(blen - half); gv_puts("\n" as *u8) 177 // the fixture must actually have reached the same-digit-width condition, or the tooth is unsound 178 var t7: i64 = 0 179 if hclen == half { 180 if half >= 10 { 181 if blen <= 99 { if (n - hn) == (blen - half) { t7 = 1 } } 182 } 183 } 184 gv_check("ANTI-VACUITY-the-request-grows-by-exactly-the-extra-body-bytes-at-fixed-header-width (fixture asserts both lengths are two-digit first, so the Content-Length header cannot contribute to the delta)" as *u8, t7, ctr) 185 186 // ---- T8 a redirect is not followed on POST ---- 187 gv_puts(" [T8] should_follow 301=" as *u8); gv_num(hp_should_follow(HPG_ST_301)) 188 gv_puts(" 302=" as *u8); gv_num(hp_should_follow(HPG_ST_302)) 189 gv_puts(" 307=" as *u8); gv_num(hp_should_follow(HPG_ST_307)); gv_puts("\n" as *u8) 190 var t8: i64 = 0 191 if hp_should_follow(HPG_ST_301) == 0 { if hp_should_follow(HPG_ST_302) == 0 { if hp_should_follow(HPG_ST_307) == 0 { t8 = 1 } } } 192 gv_check("a-redirect-is-NOT-followed-on-POST (following one rewrites the method to GET and drops the body -- the defect this lib repairs)" as *u8, t8, ctr) 193 194 // ---- T9 it refuses uniformly, not just on 3xx ---- 195 gv_puts(" [T9] should_follow 200=" as *u8); gv_num(hp_should_follow(HPG_ST_OK)); gv_puts("\n" as *u8) 196 gv_check("hp_should_follow-refuses-uniformly (it means this lib never follows, not that it has an opinion about 3xx)" as *u8, (hp_should_follow(HPG_ST_OK) == 0) as i64, ctr) 197 198 // THE POLL LADDER (2026-09-06): the pure arithmetic the follow and re-issue loops run on 199 gv_check_eq("ladder-first-wait-is-sleep-over-16 (3000 -> 187: a ready artifact is seen in the first fifth of a second, not after a flat 3 s)" as *u8, hp_ladder_first(3000), 187, ctr) 200 gv_check_eq("ladder-first-wait-never-below-the-floor (a 100 ms sleep still polls no faster than 50 ms)" as *u8, hp_ladder_first(100), 50, ctr) 201 gv_check_eq("ladder-first-wait-never-above-sleep (a 20 ms sleep stays 20 ms: the floor does not overrule a caller's tighter budget)" as *u8, hp_ladder_first(20), 20, ctr) 202 gv_check_eq("ladder-doubles (187 -> 374)" as *u8, hp_ladder_next(187, 3000), 374, ctr) 203 gv_check_eq("ladder-caps-at-sleep (1496 -> 2992 -> 3000, never past the caller's interval)" as *u8, hp_ladder_next(hp_ladder_next(1496, 3000), 3000), 3000, ctr) 204 var lw: i64 = hp_ladder_first(3000) 205 var lsum: i64 = 0 206 var lk: i64 = 0 207 while lk < 5 { lsum = lsum + lw; lw = hp_ladder_next(lw, 3000); lk = lk + 1 } 208 gv_kv("ladder_first_five_waits_ms" as *u8, lsum) 209 gv_check("ladder-first-five-waits-cost-under-two-flat-intervals (5,797 ms against 15,000 ms flat: the fast path is cheap, the slow path unchanged)" as *u8, lsum < 2 * 3000, ctr) 210 gv_check_eq("ladder-reaches-the-flat-interval-by-the-fifth-wait (after that a slow backend pays exactly what it paid before)" as *u8, lw, 3000, ctr) 211 return gv_verdict("https_post_lib" as *u8, ctr, "URL-level POST proven on the pure builder: the method is POST, the body travels, Content-Length matches it, the GET builder is the control that makes the method claim mean something, no redirect is ever followed on a POST, and the poll ladder is held to its arithmetic" as *u8) 212}