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}