nx_wiki_search_wiring.nx source
↩ module page · 471 lines · 20931 B
1// nx_wiki_search_wiring.nx -- WIKI-SPECIFIC THIN WIRING for search.
2//
3// All search logic lives in HUB primitives; this file is glue:
4// 1. Extract query string from URL (after '?')
5// 2. Run the hub flow with wiki-specific theme
6// 3. Resolve each result rowid via wiki doc store
7// 4. HTTP-wrap with /wiki/search-style 200 response
8//
9// Per operator cardinal 2026-05-27: "make sure you make the onsite and
10// offsite search reusable and not just tied to the wiki" -- this file
11// is structurally enforced THIN per NISHI_SMALL_SHARP_COMPOSABLE_STANDARD
12// §4.1 M3 + the hub<->site separation in nx_search_handler_flow.
13//
14// COMPOSES (only hub + wiki primitives):
15// hub/nx_search_handler_flow (5-step pipeline)
16// hub/nx_search_render_html (theme + inputs structs)
17// hub/nx_search_query_parser (NxSearchQuery accessors)
18// nx_search_onsite_engine (NxSearchResults accessors)
19// nx_search_inverted (NxInvIndex)
20// wiki/nx_wiki_search_render (theme constants)
21//
22// COMPOSED BY:
23// wiki/nx_wiki_routes (dispatches /wiki/search here)
24
25import "nx_syscalls.nx"
26import "hub/nx_search_handler_flow.nx"
27import "hub/nx_search_render_html.nx"
28import "hub/nx_search_query_parser.nx"
29import "nx_search_onsite_engine.nx"
30import "nx_search_inverted.nx"
31import "wiki/nx_wiki_search_render.nx"
32import "wiki/nx_wiki_index_builder.nx"
33import "wiki/nx_wiki_bm25_search.nx"
34
35// ===== Sealed verdict surface (codes 2200-2209) =================================================
36const NX_WSW_OK: i64 = 0
37const NX_WSW_BAD_INPUT: i64 = 2200
38const NX_WSW_QUERY_TOO_LONG: i64 = 2201
39const NX_WSW_RESOLVER_OVERFLOW: i64 = 2202
40const NX_WSW_FLOW_FAIL: i64 = 2203
41
42// ===== Named sizing constants (M7) =================================================
43const NX_WSW_MAX_URL_LEN: i64 = 4096
44const NX_WSW_RESOLVER_BUF_CAP: i64 = 16384 // 16 KB scratch for resolved doc data
45const NX_WSW_PLACEHOLDER_TITLE_CAP: i64 = 64
46const NX_WSW_PLACEHOLDER_URL_CAP: i64 = 128
47const NX_WSW_PLACEHOLDER_BODY_CAP: i64 = 512
48const NX_WSW_RANK_CAP: i64 = 256 // BM25 ranked-result scratch cap
49const NX_WSW_RENDER_CAP: i64 = 200 // hub render hard cap (NX_SRH_MAX_RESULTS_RENDER)
50
51// ===== V1 resolver (placeholder; HONEST gap-marker) =================================================
52//
53// V1 SCOPE per NISHI_CODE_HYGIENE M6 (anti-pretend-stub):
54// The wiki doc store + on-disk doc loader is queued via
55// nx_wiki_index_builder (charter §6 module 6). Until that ships:
56//
57// This resolver fills the inputs with EXPLICITLY-LABELED placeholder
58// strings -- title="(Doc loader pending)", url="/wiki/", body="..."
59// The rendered response is well-formed HTML showing the SEARCH FLOW
60// is wired end-to-end + the gap is visible to the operator (not
61// hidden behind a 500 or a 501).
62//
63// When nx_wiki_index_builder ships, this function is REPLACED with
64// a real walker that opens the doc-store kv (per nx_kv_store) and
65// reads title/url/body from disk.
66
67func nx_wsw_resolve_docs_placeholder(flow: *NxSearchFlow,
68 scratch_buf: *u8, scratch_cap: i64) -> i64 {
69 if flow.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
70 if (scratch_buf as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
71 if scratch_cap < NX_WSW_RESOLVER_BUF_CAP { return 0 - NX_WSW_RESOLVER_OVERFLOW }
72
73 let r: *NxSearchResults = nx_search_flow_results(flow)
74 if (r as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
75
76 let title_lit: *u8 = "(Doc loader pending)" as *u8
77 let title_lit_n: i64 = 20
78 let url_prefix: *u8 = "/wiki/doc-" as *u8
79 let url_prefix_n: i64 = 10
80 let body_lit: *u8 = "Search arc wired end-to-end via hub/nx_search_handler_flow. Doc loader (nx_wiki_index_builder) queued per NISHI_SEARCH_CHARTER section 6 module 6 -- when that ships, this resolver is replaced with real wiki doc-store reads. The 501 has been closed; the 200 honestly shows the gap." as *u8
81 let body_lit_n: i64 = 297
82
83 var i: i64 = 0
84 var off: i64 = 0
85 while i < r.count {
86 if i >= flow.docs_cap { return 0 - NX_WSW_RESOLVER_OVERFLOW }
87 // Title (shared literal)
88 flow.title_ptrs[i] = title_lit as i64
89 flow.title_lens[i] = title_lit_n
90 // URL: "/wiki/doc-<rowid>"
91 if off + url_prefix_n + 16 > scratch_cap { return 0 - NX_WSW_RESOLVER_OVERFLOW }
92 let url_start: i64 = off
93 var k: i64 = 0
94 while k < url_prefix_n { scratch_buf[off] = url_prefix[k]; off = off + 1; k = k + 1 }
95 let rowid: i64 = nx_search_results_rowid_at(r, i)
96 let rowid_n: i64 = nx_shf_int_to_dec(rowid, (scratch_buf as i64 + off) as *u8, 16)
97 off = off + rowid_n
98 flow.url_ptrs[i] = (scratch_buf as i64 + url_start)
99 flow.url_lens[i] = off - url_start
100 // Body (shared literal)
101 flow.body_ptrs[i] = body_lit as i64
102 flow.body_lens[i] = body_lit_n
103 i = i + 1
104 }
105 return NX_WSW_OK
106}
107
108// ===== V1 REAL resolver -- walks NxWikiDocStore (closes placeholder gap) =================================================
109//
110// Replaces nx_wsw_resolve_docs_placeholder when caller passes a
111// populated NxWikiDocStore (typically from nx_wiki_index_builder).
112//
113// For each result rowid, looks up (title, url, body) from the store
114// and stamps into flow.inputs parallel arrays. Per Cardinal #25
115// (Build Intelligence, Never Strip Features) -- this is the real fn;
116// placeholder remains for tests / pre-builder scenarios.
117
118func nx_wsw_resolve_docs_from_store(flow: *NxSearchFlow,
119 store: *NxWikiDocStore) -> i64 {
120 if flow.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
121 if store.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
122
123 let r: *NxSearchResults = nx_search_flow_results(flow)
124 if (r as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
125
126 var i: i64 = 0
127 while i < r.count {
128 if i >= flow.docs_cap { return 0 - NX_WSW_RESOLVER_OVERFLOW }
129
130 let rowid: i64 = nx_search_results_rowid_at(r, i)
131 if rowid < 0 { return 0 - NX_WSW_BAD_INPUT }
132
133 let t_ptr: *i64 = (sys_mmap(8)) as *i64
134 let t_n: *i64 = (sys_mmap(8)) as *i64
135 let u_ptr: *i64 = (sys_mmap(8)) as *i64
136 let u_n: *i64 = (sys_mmap(8)) as *i64
137 let b_ptr: *i64 = (sys_mmap(8)) as *i64
138 let b_n: *i64 = (sys_mmap(8)) as *i64
139 t_ptr[0] = 0; t_n[0] = 0; u_ptr[0] = 0; u_n[0] = 0; b_ptr[0] = 0; b_n[0] = 0
140
141 let rc_l: i64 = nx_wiki_doc_store_lookup(store, rowid,
142 t_ptr, t_n, u_ptr, u_n, b_ptr, b_n)
143 if rc_l != NX_WIB_OK {
144 // Defensive at boundary (Cardinal 12): doc store missing
145 // a rowid the index reported is INDEX/STORE drift; render
146 // as "(missing)" rather than abort.
147 flow.title_ptrs[i] = 0
148 flow.title_lens[i] = 0
149 flow.url_ptrs[i] = 0
150 flow.url_lens[i] = 0
151 flow.body_ptrs[i] = 0
152 flow.body_lens[i] = 0
153 }
154 if rc_l == NX_WIB_OK {
155 flow.title_ptrs[i] = t_ptr[0]
156 flow.title_lens[i] = t_n[0]
157 flow.url_ptrs[i] = u_ptr[0]
158 flow.url_lens[i] = u_n[0]
159 flow.body_ptrs[i] = b_ptr[0]
160 flow.body_lens[i] = b_n[0]
161 }
162 i = i + 1
163 }
164 return NX_WSW_OK
165}
166
167// ===== Extract query string from URL (find '?', return offset + len) =================================================
168
169func nx_wsw_extract_query_string(url: *u8, url_n: i64,
170 out_off: *i64, out_len: *i64) -> i64 {
171 if (out_off as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
172 if (out_len as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
173 if url_n < 0 { return 0 - NX_WSW_BAD_INPUT }
174 if url_n > NX_WSW_MAX_URL_LEN { return 0 - NX_WSW_QUERY_TOO_LONG }
175 out_off[0] = 0
176 out_len[0] = 0
177 var i: i64 = 0
178 while i < url_n {
179 if url[i] == (0x3F as u8) { // '?'
180 out_off[0] = i + 1
181 out_len[0] = url_n - i - 1
182 return NX_WSW_OK
183 }
184 i = i + 1
185 }
186 // No '?': empty query string, well-defined.
187 return NX_WSW_OK
188}
189
190// ===== Top-level handler V2: REAL doc-store resolver =================================================
191//
192// Called from nx_wiki_route_dispatch when nx_wiki_index_builder has
193// been run at startup + a populated NxWikiDocStore is available.
194// Returns real search results backed by the live wiki doc store.
195
196func nx_wiki_search_handle_v2(flow: *NxSearchFlow,
197 idx: *NxInvIndex,
198 store: *NxWikiDocStore,
199 url: *u8, url_n: i64,
200 resp_buf: *u8, resp_cap: i64,
201 out_resp_n: *i64) -> i64 {
202 if flow.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
203 if (resp_buf as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
204
205 // Extract query string.
206 let q_off: *i64 = (sys_mmap(8)) as *i64
207 let q_len: *i64 = (sys_mmap(8)) as *i64
208 q_off[0] = 0; q_len[0] = 0
209 let rc_xq: i64 = nx_wsw_extract_query_string(url, url_n, q_off, q_len)
210 if rc_xq != NX_WSW_OK { return rc_xq }
211 let q_ptr: *u8 = (url as i64 + q_off[0]) as *u8
212 let q_n: i64 = q_len[0]
213
214 // Steps 1-2.
215 let rc_p: i64 = nx_search_flow_parse(flow, q_ptr, q_n)
216 if rc_p != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
217 if (idx as i64) != 0 {
218 let rc_e: i64 = nx_search_flow_execute_onsite(flow, idx)
219 if rc_e != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
220 }
221 if (idx as i64) == 0 {
222 flow.results.count = 0
223 flow.results.elapsed_us = 0
224 }
225
226 // Step 3: REAL resolver if store available; else placeholder.
227 if (store as i64) != 0 {
228 if store.valid == 1 {
229 let rc_res: i64 = nx_wsw_resolve_docs_from_store(flow, store)
230 if rc_res != NX_WSW_OK { return rc_res }
231 }
232 }
233 if (store as i64) == 0 {
234 let scratch: *u8 = (sys_mmap(NX_WSW_RESOLVER_BUF_CAP)) as *u8
235 let rc_res: i64 = nx_wsw_resolve_docs_placeholder(flow, scratch, NX_WSW_RESOLVER_BUF_CAP)
236 if rc_res != NX_WSW_OK { return rc_res }
237 }
238 let rc_fin: i64 = nx_search_flow_finalize_inputs(flow)
239 if rc_fin != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
240
241 // Step 4: render.
242 let theme: *NxSearchRenderTheme = (sys_mmap(128)) as *NxSearchRenderTheme
243 let rc_t: i64 = nx_wiki_search_theme_init(theme)
244 if rc_t != NX_SRH_OK { return 0 - NX_WSW_FLOW_FAIL }
245 let body_n: *i64 = (sys_mmap(8)) as *i64
246 body_n[0] = 0
247 let rc_r: i64 = nx_search_flow_render(flow, theme, body_n)
248 if rc_r != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
249
250 // Step 5: HTTP wrap.
251 let rc_w: i64 = nx_search_flow_wrap_http(flow, body_n[0], resp_buf, resp_cap, out_resp_n)
252 if rc_w != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
253
254 return NX_WSW_OK
255}
256
257// ===== Top-level handler V1: PLACEHOLDER resolver (kept for compat) =================================================
258//
259// Called from nx_wiki_route_dispatch when no doc store has been built.
260// Now thin wrapper: dispatches to V2 with store=0.
261
262func nx_wiki_search_handle(flow: *NxSearchFlow,
263 idx: *NxInvIndex,
264 url: *u8, url_n: i64,
265 resp_buf: *u8, resp_cap: i64,
266 out_resp_n: *i64) -> i64 {
267 return nx_wiki_search_handle_v2(flow, idx, 0 as *NxWikiDocStore,
268 url, url_n, resp_buf, resp_cap, out_resp_n)
269}
270
271// ===== V3 handler: BM25-RANKED + ARCHIVE-UNIFIED search =================================================
272//
273// THE wiki R3 route. Upgrades ranking from the onsite engine's presence/AND
274// (count-of-matched-terms) to real Okapi BM25 (term-frequency saturated,
275// length-normalized, IDF-weighted) AND unifies the no-link-rot archive: content
276// that lives only in the content-addressed archive (seg_store prefix
277// knowledge/store/wikiarchive-) is folded in via its term index and ranked in
278// the SAME corpus. Sorted most-relevant-first.
279//
280// Pipeline:
281// 1. parse query string -> NxSearchQuery (hub primitive, unchanged)
282// 2. lift the parsed terms into the (**u8, *i64) shape nx_wiki_bm25_rank wants
283// 3. nx_wiki_bm25_rank over [doc store] ++ [archive blobs that hit a term]
284// 4. stamp the ranked rowids/scores into flow.results; resolve each ranked row
285// into flow.inputs (DOC rows from the doc store; ARCHIVE rows from the
286// ranked blob body + a synthesized "(archived)" title/url)
287// 5. render (existing wiki theme) + HTTP-wrap (existing hub steps)
288//
289// archive_prefix may be 0 (doc-store-only ranking, still BM25). When the live
290// wiki passes WAR_PREFIX (knowledge/store/wikiarchive-), the archive is searched.
291
292func nx_wiki_search_handle_bm25(flow: *NxSearchFlow,
293 store: *NxWikiDocStore,
294 archive_prefix: *u8,
295 url: *u8, url_n: i64,
296 resp_buf: *u8, resp_cap: i64,
297 out_resp_n: *i64) -> i64 {
298 if flow.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
299 if (resp_buf as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
300 if (store as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
301 if store.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
302
303 // Step 1: extract + parse the query string.
304 let q_off: *i64 = (sys_mmap(8)) as *i64
305 let q_len: *i64 = (sys_mmap(8)) as *i64
306 q_off[0] = 0; q_len[0] = 0
307 let rc_xq: i64 = nx_wsw_extract_query_string(url, url_n, q_off, q_len)
308 if rc_xq != NX_WSW_OK { return rc_xq }
309 let q_ptr: *u8 = (url as i64 + q_off[0]) as *u8
310 let q_n: i64 = q_len[0]
311
312 let rc_p: i64 = nx_search_flow_parse(flow, q_ptr, q_n)
313 if rc_p != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
314
315 // Step 2: lift parsed terms into BM25 term arrays.
316 let nterms: i64 = nx_search_query_count(flow.q)
317 let qterms: *i64 = (sys_mmap(8 * 64)) as *i64
318 let qlens: *i64 = (sys_mmap(8 * 64)) as *i64
319 var nt: i64 = 0
320 var ti: i64 = 0
321 while ti < nterms {
322 if nt < 32 {
323 let tp: *i64 = (sys_mmap(8)) as *i64
324 let tl: *i64 = (sys_mmap(8)) as *i64
325 tp[0] = 0; tl[0] = 0
326 let rc_t: i64 = nx_search_query_term_at(flow.q, ti, tp, tl)
327 if rc_t == NX_SQP_OK {
328 if tl[0] > 0 {
329 qterms[nt] = tp[0]
330 qlens[nt] = tl[0]
331 nt = nt + 1
332 }
333 }
334 }
335 ti = ti + 1
336 }
337
338 // Step 3: BM25 rank over doc store + archive.
339 let ranked: *NxWikiRanked = (sys_mmap(128)) as *NxWikiRanked
340 let rc_ri: i64 = nx_wiki_ranked_init(ranked, NX_WSW_RANK_CAP)
341 if rc_ri != 0 { return 0 - NX_WSW_FLOW_FAIL }
342 let max_results: i64 = nx_search_query_max_results(flow.q)
343 let rc_rank: i64 = nx_wiki_bm25_rank(store, archive_prefix,
344 qterms as **u8, qlens, nt,
345 ranked, max_results)
346 if rc_rank != 0 { return 0 - NX_WSW_FLOW_FAIL }
347
348 // Step 4: stamp ranked results + resolve each row into render inputs.
349 var n: i64 = nx_wiki_ranked_count(ranked)
350 if n > flow.docs_cap { n = flow.docs_cap }
351 if n > NX_WSW_RENDER_CAP { n = NX_WSW_RENDER_CAP } // hub render cap (200)
352 flow.results.count = n
353 flow.results.n_terms = nt
354 flow.results.elapsed_us = 0
355
356 let arch_title: *u8 = "(archived page)" as *u8
357 let arch_title_n: i64 = 15
358 let arch_url: *u8 = "/wiki/archive/" as *u8
359 let arch_url_n: i64 = 14
360
361 var i: i64 = 0
362 while i < n {
363 flow.results.rowids[i] = nx_wiki_ranked_rowid_at(ranked, i)
364 flow.results.scores[i] = nx_wiki_ranked_score_at(ranked, i)
365 let kind: i64 = nx_wiki_ranked_kind_at(ranked, i)
366 if kind == NX_WBS_SRC_DOC {
367 // resolve title/url/body from the live doc store by rowid
368 let t_ptr: *i64 = (sys_mmap(8)) as *i64
369 let t_n: *i64 = (sys_mmap(8)) as *i64
370 let u_ptr: *i64 = (sys_mmap(8)) as *i64
371 let u_n: *i64 = (sys_mmap(8)) as *i64
372 let b_ptr: *i64 = (sys_mmap(8)) as *i64
373 let b_n: *i64 = (sys_mmap(8)) as *i64
374 t_ptr[0] = 0; t_n[0] = 0; u_ptr[0] = 0; u_n[0] = 0; b_ptr[0] = 0; b_n[0] = 0
375 let rc_l: i64 = nx_wiki_doc_store_lookup(store, flow.results.rowids[i],
376 t_ptr, t_n, u_ptr, u_n, b_ptr, b_n)
377 if rc_l == NX_WIB_OK {
378 flow.title_ptrs[i] = t_ptr[0]; flow.title_lens[i] = t_n[0]
379 flow.url_ptrs[i] = u_ptr[0]; flow.url_lens[i] = u_n[0]
380 flow.body_ptrs[i] = b_ptr[0]; flow.body_lens[i] = b_n[0]
381 }
382 if rc_l != NX_WIB_OK {
383 flow.title_ptrs[i] = 0; flow.title_lens[i] = 0
384 flow.url_ptrs[i] = 0; flow.url_lens[i] = 0
385 flow.body_ptrs[i] = 0; flow.body_lens[i] = 0
386 }
387 }
388 if kind == NX_WBS_SRC_ARCHIVE {
389 // archive row: body is the ranked blob body; title/url synthesized
390 // (the no-rot archive is content-addressed; archive router serves it)
391 flow.title_ptrs[i] = arch_title as i64; flow.title_lens[i] = arch_title_n
392 flow.url_ptrs[i] = arch_url as i64; flow.url_lens[i] = arch_url_n
393 flow.body_ptrs[i] = ranked.body_ptrs[i]; flow.body_lens[i] = ranked.body_lens[i]
394 }
395 i = i + 1
396 }
397
398 let rc_fin: i64 = nx_search_flow_finalize_inputs(flow)
399 if rc_fin != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
400
401 // Step 5: render (wiki theme) + HTTP wrap.
402 let theme: *NxSearchRenderTheme = (sys_mmap(128)) as *NxSearchRenderTheme
403 let rc_t: i64 = nx_wiki_search_theme_init(theme)
404 if rc_t != NX_SRH_OK { return 0 - NX_WSW_FLOW_FAIL }
405 let body_n: *i64 = (sys_mmap(8)) as *i64
406 body_n[0] = 0
407 let rc_r: i64 = nx_search_flow_render(flow, theme, body_n)
408 if rc_r != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
409 let rc_w: i64 = nx_search_flow_wrap_http(flow, body_n[0], resp_buf, resp_cap, out_resp_n)
410 if rc_w != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
411
412 return NX_WSW_OK
413}
414
415// ===== LEGACY V1 (internal; unused after V2 unification) =================================================
416
417func nx_wiki_search_handle_legacy(flow: *NxSearchFlow,
418 idx: *NxInvIndex,
419 url: *u8, url_n: i64,
420 resp_buf: *u8, resp_cap: i64,
421 out_resp_n: *i64) -> i64 {
422 if flow.valid != 1 { return 0 - NX_WSW_BAD_INPUT }
423 if (resp_buf as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
424 if (out_resp_n as i64) == 0 { return 0 - NX_WSW_BAD_INPUT }
425
426 // Extract query string from URL.
427 let q_off: *i64 = (sys_mmap(8)) as *i64
428 let q_len: *i64 = (sys_mmap(8)) as *i64
429 q_off[0] = 0; q_len[0] = 0
430 let rc_xq: i64 = nx_wsw_extract_query_string(url, url_n, q_off, q_len)
431 if rc_xq != NX_WSW_OK { return rc_xq }
432 let q_ptr: *u8 = (url as i64 + q_off[0]) as *u8
433 let q_n: i64 = q_len[0]
434
435 // Step 1: parse.
436 let rc_p: i64 = nx_search_flow_parse(flow, q_ptr, q_n)
437 if rc_p != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
438
439 // Step 2: execute onsite engine (only if idx is provided; if no
440 // index built yet, results stay at 0 -- empty-state renders).
441 if (idx as i64) != 0 {
442 let rc_e: i64 = nx_search_flow_execute_onsite(flow, idx)
443 if rc_e != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
444 }
445 if (idx as i64) == 0 {
446 flow.results.count = 0
447 flow.results.elapsed_us = 0
448 }
449
450 // Step 3: resolve docs (V1 placeholder; replaced when index_builder ships).
451 let scratch: *u8 = (sys_mmap(NX_WSW_RESOLVER_BUF_CAP)) as *u8
452 let rc_res: i64 = nx_wsw_resolve_docs_placeholder(flow, scratch, NX_WSW_RESOLVER_BUF_CAP)
453 if rc_res != NX_WSW_OK { return rc_res }
454 let rc_fin: i64 = nx_search_flow_finalize_inputs(flow)
455 if rc_fin != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
456
457 // Step 4: render HTML with wiki theme.
458 let theme: *NxSearchRenderTheme = (sys_mmap(128)) as *NxSearchRenderTheme
459 let rc_t: i64 = nx_wiki_search_theme_init(theme)
460 if rc_t != NX_SRH_OK { return 0 - NX_WSW_FLOW_FAIL }
461 let body_n: *i64 = (sys_mmap(8)) as *i64
462 body_n[0] = 0
463 let rc_r: i64 = nx_search_flow_render(flow, theme, body_n)
464 if rc_r != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
465
466 // Step 5: HTTP wrap.
467 let rc_w: i64 = nx_search_flow_wrap_http(flow, body_n[0], resp_buf, resp_cap, out_resp_n)
468 if rc_w != NX_SHF_OK { return 0 - NX_WSW_FLOW_FAIL }
469
470 return NX_WSW_OK
471}