code wiki / _hdl_build / nx_search_ship_lib.nx
nx_search_ship_lib.nx source
↩ module page · 273 lines · 11964 B
1// nx_search_ship_lib.nx -- THE PURE HALF OF THE SEARCH SHIP LOOP (2026-09-14): every parser the loop reads a
2// receipt or a probe with, in a lib so nx_search_ship_gate can drive them on planted fixtures without forking a
3// build, a deploy or the edge. The program (nx_search_ship) owns the forks and the exit codes; nothing here
4// touches a file, a socket or a process. Prefix sx_ (ss_ is the seg store's, os_ is nx_organ_ship's).
5// license_tier: ORIGINAL
6import "nx_syscalls.nx"
7
8const SX_PIPE: i64 = 124
9const SX_QUOTE: i64 = 34
10const SX_LF: i64 = 10
11const SX_SPACE: i64 = 32
12const SX_HASH: i64 = 35
13const SX_MINUS: i64 = 45
14const SX_D0: i64 = 48
15const SX_D9: i64 = 57
16const SX_PHASE_SLOTS: i64 = 8 // recv, prep, s1, l0dec, auth, div, total, cand
17
18func sx_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
19func sx_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var oo: i64 = o; while s[i] != (0 as u8) { d[oo] = s[i]; oo = oo + 1; i = i + 1 } d[oo] = 0 as u8; return oo }
20func sx_catn(d: *u8, o: i64, v: i64) -> i64 {
21 var oo: i64 = o
22 var x: i64 = v
23 if x < 0 { d[oo] = SX_MINUS as u8; oo = oo + 1; x = 0 - x }
24 if x == 0 { d[oo] = SX_D0 as u8; oo = oo + 1; d[oo] = 0 as u8; return oo }
25 var pw: i64 = 1
26 while x / pw >= 10 { pw = pw * 10 }
27 while pw > 0 { d[oo] = (SX_D0 + (x / pw) % 10) as u8; oo = oo + 1; pw = pw / 10 }
28 d[oo] = 0 as u8
29 return oo
30}
31func sx_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 } return 0 }
32func sx_starts(s: *u8, p: *u8) -> i64 { var i: i64 = 0; while p[i] != (0 as u8) { if s[i] != p[i] { return 0 } i = i + 1 } return 1 }
33// first occurrence of needle in hay[0..n), -1 absent
34func sx_find(hay: *u8, n: i64, needle: *u8) -> i64 {
35 let nl: i64 = sx_slen(needle)
36 if nl == 0 { return 0 - 1 }
37 var i: i64 = 0
38 while i + nl <= n {
39 var j: i64 = 0
40 var hit: i64 = 1
41 while j < nl { if hay[i + j] != needle[j] { hit = 0; j = nl } else { j = j + 1 } }
42 if hit == 1 { return i }
43 i = i + 1
44 }
45 return 0 - 1
46}
47// decimal (optional leading minus) immediately after the FIRST needle; -1 when absent or no digit follows.
48// -1 is therefore ambiguous with a literal -1 in the text: every field this loop reads is a count or a
49// duration, never negative, so the caller reads -1 as ABSENT.
50func sx_int_after(hay: *u8, n: i64, needle: *u8) -> i64 {
51 let p: i64 = sx_find(hay, n, needle)
52 if p < 0 { return 0 - 1 }
53 var i: i64 = p + sx_slen(needle)
54 var neg: i64 = 0
55 if i < n { if hay[i] == (SX_MINUS as u8) { neg = 1; i = i + 1 } }
56 var v: i64 = 0
57 var any: i64 = 0
58 var go: i64 = 1
59 while go == 1 {
60 if i >= n { go = 0 } else {
61 let c: i64 = hay[i] as i64
62 if c >= SX_D0 { if c <= SX_D9 { v = v * 10 + (c - SX_D0); any = 1; i = i + 1 } else { go = 0 } } else { go = 0 }
63 }
64 }
65 if any == 0 { return 0 - 1 }
66 if neg == 1 { return 0 - v }
67 return v
68}
69// the quoted string after key (key includes its colon, e.g. "\"sha256\":"), NUL-terminated into dst; returns its length
70func sx_json_str(hay: *u8, n: i64, key: *u8, dst: *u8, cap: i64) -> i64 {
71 dst[0] = 0 as u8
72 let p: i64 = sx_find(hay, n, key)
73 if p < 0 { return 0 }
74 var i: i64 = p + sx_slen(key)
75 var q: i64 = 0 - 1
76 var go: i64 = 1
77 while go == 1 { if i >= n { go = 0 } else { if hay[i] == (SX_QUOTE as u8) { q = i; go = 0 } else { i = i + 1 } } }
78 if q < 0 { return 0 }
79 var k: i64 = 0
80 var j: i64 = q + 1
81 go = 1
82 while go == 1 { if j >= n { go = 0 } else { if hay[j] == (SX_QUOTE as u8) { go = 0 } else { if k < cap - 1 { dst[k] = hay[j]; k = k + 1 } j = j + 1 } } }
83 dst[k] = 0 as u8
84 return k
85}
86// every line that STARTS with pfx, concatenated (each with its own LF) into dst[0..cap); returns the line count.
87// The referee A/B compares the two concatenations byte-for-byte: 17 queries, one line each, in file order.
88func sx_lines_with(hay: *u8, n: i64, pfx: *u8, dst: *u8, cap: i64, dlen: *i64) -> i64 {
89 var cnt: i64 = 0
90 var o: i64 = 0
91 var i: i64 = 0
92 while i < n {
93 var e: i64 = i
94 while e < n { if hay[e] == (SX_LF as u8) { e = n + 1 } else { e = e + 1 } }
95 var eol: i64 = e
96 if e == n + 1 { eol = 0 - 1; var f: i64 = i; while f < n { if hay[f] == (SX_LF as u8) { eol = f; f = n } else { f = f + 1 } } }
97 if eol < 0 { eol = n }
98 if sx_starts((hay as i64 + i) as *u8, pfx) == 1 {
99 var k: i64 = i
100 while k < eol { if o < cap - 2 { dst[o] = hay[k]; o = o + 1 } k = k + 1 }
101 dst[o] = SX_LF as u8; o = o + 1
102 cnt = cnt + 1
103 }
104 i = eol + 1
105 }
106 dst[o] = 0 as u8
107 dlen[0] = o
108 return cnt
109}
110// byte-for-byte equality of two buffers
111func sx_bytes_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
112 if an != bn { return 0 }
113 var i: i64 = 0
114 while i < an { if a[i] != b[i] { return 0 } i = i + 1 }
115 return 1
116}
117// the FIRST line at which two line-buffers differ (1-based), 0 when identical
118func sx_first_diff_line(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
119 var i: i64 = 0
120 var line: i64 = 1
121 var n: i64 = an
122 if bn < n { n = bn }
123 while i < n {
124 if a[i] != b[i] { return line }
125 if a[i] == (SX_LF as u8) { line = line + 1 }
126 i = i + 1
127 }
128 if an != bn { return line }
129 return 0
130}
131// the phase timers out of one /api/search capture: out[0]=recv ms (the fetcher's first line), then from the
132// phase_us block prep, s1, l0dec, auth, div, total (microseconds) and cand. -1 = field absent. Returns 1 when the
133// phase_us block was found at all, else 0 (a 503, a warm-window refusal or a body without the field).
134func sx_phase(hay: *u8, n: i64, out: *i64) -> i64 {
135 var z: i64 = 0
136 while z < SX_PHASE_SLOTS { out[z] = 0 - 1; z = z + 1 }
137 out[0] = sx_int_after(hay, n, "recv=" as *u8)
138 let p: i64 = sx_find(hay, n, "\"phase_us\":{" as *u8)
139 if p < 0 { return 0 }
140 let h2: *u8 = (hay as i64 + p) as *u8
141 let n2: i64 = n - p
142 out[1] = sx_int_after(h2, n2, "\"prep\":" as *u8)
143 out[2] = sx_int_after(h2, n2, "\"s1\":" as *u8)
144 out[3] = sx_int_after(h2, n2, "\"l0dec\":" as *u8)
145 out[4] = sx_int_after(h2, n2, "\"auth\":" as *u8)
146 out[5] = sx_int_after(h2, n2, "\"div\":" as *u8)
147 out[6] = sx_int_after(h2, n2, "\"total\":" as *u8)
148 out[7] = sx_int_after(h2, n2, "\"cand\":" as *u8)
149 return 1
150}
151// the consumer bar (ms) of the journey named `id` in a uat_journeys.conf buffer (id|url|connect|latency_ms_max|must);
152// dflt when the row or its field is absent
153func sx_journey_bar(cf: *u8, n: i64, id: *u8, dflt: i64) -> i64 {
154 let key: *u8 = sys_mmap(128)
155 var ko: i64 = sx_cat(key, 0, id)
156 ko = sx_cat(key, ko, "|" as *u8)
157 let p: i64 = sx_find(cf, n, key)
158 sys_munmap(key, 128)
159 if p < 0 { return dflt }
160 var i: i64 = p
161 var pipes: i64 = 0
162 var go: i64 = 1
163 while go == 1 {
164 if i >= n { go = 0 } else {
165 if cf[i] == (SX_PIPE as u8) { pipes = pipes + 1; i = i + 1; if pipes == 3 { go = 0 } } else { i = i + 1 }
166 }
167 }
168 if pipes < 3 { return dflt }
169 // the digits start right after the third pipe (an empty needle would read as ABSENT -- caught by the gate's T5)
170 var v: i64 = 0
171 var any: i64 = 0
172 var g2: i64 = 1
173 while g2 == 1 {
174 if i >= n { g2 = 0 } else {
175 let c: i64 = cf[i] as i64
176 if c >= SX_D0 { if c <= SX_D9 { v = v * 10 + (c - SX_D0); any = 1; i = i + 1 } else { g2 = 0 } } else { g2 = 0 }
177 }
178 }
179 if any == 0 { return dflt }
180 return v
181}
182// space -> %20 (the only byte a conf query carries that a URL cannot); returns the new offset
183func sx_urlenc(d: *u8, o: i64, s: *u8) -> i64 {
184 var oo: i64 = o
185 var i: i64 = 0
186 while s[i] != (0 as u8) {
187 if s[i] == (SX_SPACE as u8) { d[oo] = 37 as u8; d[oo + 1] = 50 as u8; d[oo + 2] = SX_D0 as u8; oo = oo + 3 } else { d[oo] = s[i]; oo = oo + 1 }
188 i = i + 1
189 }
190 d[oo] = 0 as u8
191 return oo
192}
193// probe queries out of a conf buffer: one per line, '#' comments and blank lines skipped, each copied into
194// slots of `w` bytes at `dst` (NUL-terminated), at most `maxn`; returns the count
195func sx_probes(cf: *u8, n: i64, dst: *u8, w: i64, maxn: i64) -> i64 {
196 var cnt: i64 = 0
197 var i: i64 = 0
198 while i < n {
199 var e: i64 = i
200 var go: i64 = 1
201 while go == 1 { if e >= n { go = 0 } else { if cf[e] == (SX_LF as u8) { go = 0 } else { e = e + 1 } } }
202 if e > i { if cf[i] != (SX_HASH as u8) { if cnt < maxn {
203 let slot: *u8 = (dst as i64 + cnt * w) as *u8
204 var k: i64 = 0
205 var j: i64 = i
206 while j < e { if k < w - 1 { if cf[j] != (13 as u8) { slot[k] = cf[j]; k = k + 1 } } j = j + 1 }
207 slot[k] = 0 as u8
208 if k > 0 { cnt = cnt + 1 }
209 } } }
210 i = e + 1
211 }
212 return cnt
213}
214// HTML SERP WITNESS (2026-09-14). The JSON probes exercise only the /api/search branch of the daemon's router; the
215// HTML /search branch is a different code path (dad_is_search) and it BROKE through the buffered edge on 2026-09-14
216// while every JSON probe read GREEN. A page witness needs BOTH the status line and the marker the consumer journey
217// itself demands (uat_journeys.conf must_contain): a 200 without the marker is a shell without an answer, and a
218// marker under a refusal page is not an answer either. 1 only when both are present in the capture.
219func sx_html_ok(out: *u8, n: i64, marker: *u8) -> i64 {
220 if sx_find(out, n, "HTTP/1.1 200" as *u8) < 0 { return 0 }
221 if sx_find(out, n, marker) < 0 { return 0 }
222 return 1
223}
224// One column of one journey row (uat_journeys.conf: id|url|connect|latency_ms_max|must_contain), CR stripped and
225// NUL-terminated into out. Returns the bytes copied; 0 when no row carries that id or the column is empty. The id
226// must be followed by a pipe, so a shorter id never matches a longer id's prefix.
227const SX_JF_ID: i64 = 0
228const SX_JF_URL: i64 = 1
229const SX_JF_CONNECT: i64 = 2
230const SX_JF_BAR: i64 = 3
231const SX_JF_MUST: i64 = 4
232const SX_JF_CR: i64 = 13
233func sx_journey_field(cf: *u8, n: i64, id: *u8, col: i64, out: *u8, cap: i64) -> i64 {
234 let idn: i64 = sx_slen(id)
235 var i: i64 = 0
236 while i < n {
237 var e: i64 = i
238 var eol: i64 = 0
239 while eol == 0 { if e >= n { eol = 1 } else { if cf[e] == (SX_LF as u8) { eol = 1 } else { e = e + 1 } } }
240 var m: i64 = 1
241 if e - i < idn + 1 { m = 0 } else {
242 var k: i64 = 0
243 while k < idn { if cf[i + k] != id[k] { m = 0 } k = k + 1 }
244 if cf[i + idn] != (SX_PIPE as u8) { m = 0 }
245 }
246 if m == 1 {
247 var c: i64 = 0
248 var j: i64 = i
249 var o: i64 = 0
250 while j < e {
251 if cf[j] == (SX_PIPE as u8) { c = c + 1 } else { if c == col { if cf[j] != (SX_JF_CR as u8) { if o < cap - 1 { out[o] = cf[j]; o = o + 1 } } } }
252 j = j + 1
253 }
254 out[o] = 0 as u8
255 return o
256 }
257 i = e + 1
258 }
259 out[0] = 0 as u8
260 return 0
261}
262// AGENT DECISION (W2, 2026-09-14). The worker adopts on its own ONLY the class whose safety is mechanical: the
263// candidate differs from live, the ruler lost nothing, and the referee is byte-identical on every judged query --
264// a pure speed change. A candidate identical to live is NOCHANGE; anything whose ranking moved, or that lost a
265// printable run, is HELD as a private candidate for the root (RACI: workers never adopt a ranking change).
266const SX_AGENT_NOCHANGE: i64 = 0
267const SX_AGENT_ADOPT: i64 = 1
268const SX_AGENT_HOLD: i64 = 2
269func sx_agent_decide(same_as_live: i64, referee_identical: i64, lost: i64) -> i64 {
270 if same_as_live == 1 { return SX_AGENT_NOCHANGE }
271 if referee_identical == 1 { if lost == 0 { return SX_AGENT_ADOPT } }
272 return SX_AGENT_HOLD
273}