nx_wiki_main.nx source
↩ module page · 522 lines · 25753 B
1// nx_wiki_main.nx -- wiki daemon entry point.
2//
3// Forks the nx_audit_server_routed::main 140-line accept-loop
4// pattern. Reuses every existing HTTP / health / log primitive;
5// zero new daemon primitives introduced (per duplicate-primitives
6// audit). Per the small-sharp+composable standard: this file is
7// the wiki's site-specific main() that wires the hub primitives +
8// the wiki route dispatcher into a runnable daemon.
9//
10// COMPOSES:
11// nx_http_server bind / listen / accept / read / send
12// nx_kv_store session storage (caller-allocated)
13// nx_hash_facade password hash compare
14// nx_log_jsonl per-request structured log
15// nx_syscalls (sys_read_file) read admin-hash file at startup
16// nx_syscalls (sys_now_ms) wall-clock for session expiry
17// hub/nx_admin_login_flow admin-login flow init
18// wiki/nx_wiki_routes route dispatcher
19//
20// Status: V1 SEED. 2026-05-27.
21//
22// WINNER-TIER: BASELINE-C provisional (forks audit-server's shipped
23// keepalive + per-conn-budget shape; throughput parity
24// expected; paired bench pending wiki workload fixture)
25// INCUMBENTS: nginx (single-process), Caddy, Apache HTTPD MPM,
26// substrate's own nx_audit_server_routed
27// NUMBERS: V1 ships the daemon; paired req/sec vs audit-server
28// pending real load
29// GAP: nginx multi-worker + master process management;
30// V1 ships single-process with supervisor-restart on
31// request-budget exhaustion (matches audit-server)
32// PLAN: M-next: multi-worker fork pattern when wiki workload
33// demands > 1 process's throughput
34// EXEMPTION REASON: n/a; provisional pending measurement
35//
36// V1 HONEST SCOPE LIMITS (per NISHI_CODE_HYGIENE_STANDARD M6):
37// - Bind: loopback-only (127.0.0.1:51850). Operator wires a
38// reverse-proxy / nginx-front-end to expose at nishifamily.com/wiki.
39// V2 supports listening on non-loopback addresses + TLS via
40// future nx_tls_shim.
41// - Admin hash: loaded from file at NX_WIKI_MAIN_ADMIN_HASH_PATH
42// ("/tmp/nishi_wiki_admin_hash" V1; operator setup script writes
43// 8-byte big-endian i64 hash). Missing file -> daemon refuses
44// to start with explicit verdict (no silent default; not pretend).
45// - Session storage: in-memory KV (lost on restart V1; persistent
46// via nx_blob_store backing in V2).
47// - Random for session tokens: caller-supplied buffer rotated
48// per-request from sys_getrandom (V1) -> future nx_csprng (V2).
49
50import "nx_syscalls.nx"
51import "nx_http_server.nx"
52import "nx_kv_store.nx"
53import "nx_hash_facade.nx"
54import "nx_log_jsonl.nx"
55import "nx_http_header_find.nx"
56import "hub/nx_admin_login_flow.nx"
57import "hub/nx_search_handler_flow.nx"
58import "wiki/nx_wiki_routes.nx"
59import "wiki/nx_wiki_index_builder.nx"
60import "wiki/nx_wiki_page_save.nx"
61import "wiki/nx_wiki_content_loader.nx"
62import "wiki/nx_wiki_archive_router.nx"
63import "wiki/nx_artifact_store.nx"
64import "wiki/nx_pipeline_walker.nx"
65import "wiki/nx_pipeline_recursive_walker.nx"
66import "wiki/nx_pipeline_graph_builder.nx"
67import "hub/nx_dep_graph.nx"
68import "nx_search_inverted.nx"
69
70// ===== Sealed verdict surface =================================================
71const NX_WIKI_MAIN_OK: i64 = 0
72const NX_WIKI_MAIN_BIND_FAILED: i64 = 1300
73const NX_WIKI_MAIN_NO_ADMIN_HASH: i64 = 1301
74const NX_WIKI_MAIN_KV_INIT_FAILED: i64 = 1302
75const NX_WIKI_MAIN_CONFIG_INIT_FAILED: i64 = 1303
76const NX_WIKI_MAIN_RANDOM_FAILED: i64 = 1304
77const NX_WIKI_MAIN_REQUEST_BUDGET_HIT: i64 = 1305
78
79// ===== Named constants (M7 hygiene compliance) =================================================
80//
81// All sizing + paths + identifiers named; no magic literals in body.
82
83const NX_WIKI_MAIN_BACKLOG: i64 = 64
84const NX_WIKI_MAIN_REALM: *u8 = "Nishi Wiki Admin" as *u8
85const NX_WIKI_MAIN_REALM_N: i64 = 16
86const NX_WIKI_MAIN_ADMIN_USERNAME: *u8 = "elderwesto" as *u8
87const NX_WIKI_MAIN_ADMIN_USERNAME_N: i64 = 10
88const NX_WIKI_MAIN_ADMIN_HASH_PATH: *u8 = "/tmp/nishi_wiki_admin_hash" as *u8
89
90// Linux x86_64 syscall numbers we need beyond nx_syscalls.nx defaults.
91const NX_WIKI_MAIN_SYS_GETRANDOM: i64 = 318 // x86_64
92
93// Session storage sizing (caller-allocated per nx_kv_store contract).
94const NX_WIKI_MAIN_KV_DATA_CAP: i64 = 1048576 // 1 MiB session data
95const NX_WIKI_MAIN_KV_INDEX_CAP: i64 = 65536 // 64 KiB index (~4000 sessions)
96
97// CSPRNG buffer (16 bytes per session-token mint).
98const NX_WIKI_MAIN_RANDOM_BYTES: i64 = 16
99
100// Per-request log structure name.
101const NX_WIKI_MAIN_LOG_COMPONENT: *u8 = "nx_wiki_main" as *u8
102const NX_WIKI_MAIN_LOG_COMPONENT_N: i64 = 12
103
104// ===== Load admin password hash from file =================================================
105//
106// File contents: 8 bytes big-endian i64 (the result of operator's
107// nx_hash_facade_compute_bytes("M0nkey2#") at setup). V1 reads the
108// file via sys_read_file_x86_64; V2 may add fcntl-locked atomic
109// read.
110//
111// Returns:
112// >=0 -> the hash value as i64
113// < 0 -> negated NX_WIKI_MAIN_NO_ADMIN_HASH verdict
114
115func nx_wiki_main_load_admin_hash(path: *u8) -> i64 {
116 let len_out: *i64 = (sys_mmap(8)) as *i64
117 len_out[0] = 0
118 let body: *u8 = sys_read_file(path, len_out)
119 if (body as i64) == 0 { return 0 - NX_WIKI_MAIN_NO_ADMIN_HASH }
120 if len_out[0] < 8 { return 0 - NX_WIKI_MAIN_NO_ADMIN_HASH }
121 // Big-endian i64 read.
122 var hash: i64 = 0
123 var i: i64 = 0
124 while i < 8 {
125 hash = (hash << 8) | (body[i] as i64)
126 i = i + 1
127 }
128 return hash
129}
130
131// ===== Fill random_16 buffer via getrandom syscall =================================================
132//
133// Calls Linux x86_64 SYS_GETRANDOM (318). V1 blocks if entropy pool
134// uninitialized; V2 can switch to GRND_NONBLOCK + fallback. Returns
135// NX_WIKI_MAIN_OK on success.
136
137func nx_wiki_main_fill_random(out_16: *u8) -> i64 {
138 if (out_16 as i64) == 0 { return 0 - NX_WIKI_MAIN_RANDOM_FAILED }
139 let rc: i64 = __syscall(NX_WIKI_MAIN_SYS_GETRANDOM,
140 out_16 as i64,
141 NX_WIKI_MAIN_RANDOM_BYTES,
142 0, 0, 0, 0)
143 if rc != NX_WIKI_MAIN_RANDOM_BYTES { return 0 - NX_WIKI_MAIN_RANDOM_FAILED }
144 return NX_WIKI_MAIN_OK
145}
146
147// ===== Per-request log emit =================================================
148//
149// SYSTEM-class fields only per privacy-by-default (no IP / UA / user
150// data). Status + method + path + duration.
151
152func nx_wiki_main_log_request(ts_ms: i64,
153 method_kind: i64,
154 path: *u8, path_n: i64,
155 status: i64,
156 duration_ms: i64) -> i64 {
157 let log_buf: *u8 = sys_mmap(2048)
158 let log_off: *i64 = (sys_mmap(8)) as *i64
159 log_off[0] = 0
160 let scratch: *u8 = sys_mmap(64)
161
162 // Build msg = method-name + " " + path (capped at 256).
163 let msg_buf: *u8 = sys_mmap(512)
164 var msg_off: i64 = 0
165 let mname: *u8 = nxar_method_name(method_kind)
166 let mname_n: i64 = nxar_method_name_len(method_kind)
167 var i: i64 = 0
168 while i < mname_n {
169 msg_buf[msg_off + i] = mname[i]
170 i = i + 1
171 }
172 msg_off = msg_off + mname_n
173 msg_buf[msg_off] = 0x20 as u8
174 msg_off = msg_off + 1
175 var cap_path: i64 = path_n
176 if cap_path > 256 { cap_path = 256 }
177 var j: i64 = 0
178 while j < cap_path {
179 msg_buf[msg_off + j] = path[j]
180 j = j + 1
181 }
182 msg_off = msg_off + cap_path
183
184 var level: i64 = NXL_INFO
185 if status >= 400 { if status <= 499 { level = NXL_WARN } }
186 if status >= 500 { if status <= 599 { level = NXL_ERROR } }
187
188 nx_log_emit_line_with_int(log_buf, log_off, 2048,
189 ts_ms, level,
190 NX_WIKI_MAIN_LOG_COMPONENT,
191 NX_WIKI_MAIN_LOG_COMPONENT_N,
192 msg_buf, msg_off,
193 "status" as *u8, 6,
194 status, scratch)
195 sys_write(1, log_buf, log_off[0])
196 return NX_WIKI_MAIN_OK
197}
198
199// Method-name helpers (mirror nx_audit_server_routed conventions).
200func nxar_method_name(k: i64) -> *u8 {
201 if k == NX_WIKI_ROUTE_METHOD_GET { return "GET" as *u8 }
202 if k == NX_WIKI_ROUTE_METHOD_POST { return "POST" as *u8 }
203 if k == NX_WIKI_ROUTE_METHOD_OPTIONS { return "OPTIONS" as *u8 }
204 return "?" as *u8
205}
206func nxar_method_name_len(k: i64) -> i64 {
207 if k == NX_WIKI_ROUTE_METHOD_GET { return 3 }
208 if k == NX_WIKI_ROUTE_METHOD_POST { return 4 }
209 if k == NX_WIKI_ROUTE_METHOD_OPTIONS { return 7 }
210 return 1
211}
212
213// ===== Main daemon loop =================================================
214//
215// Mirror of nx_audit_server_routed::main but for the wiki:
216// 1. Bind 127.0.0.1:51850
217// 2. Load admin hash (refuse to start if missing)
218// 3. Init session KV store (1 MiB data + 64 KiB index)
219// 4. Init NxAdminLoginConfig
220// 5. Accept loop with per-conn keepalive budget (100) +
221// per-process request budget (1M); supervisor restarts on exhaustion
222// 6. Per request: parse -> dispatch via nx_wiki_route_dispatch ->
223// send -> log
224
225func main() -> i64 {
226 // ----- 1. Bind -----
227 let addr: *u8 = sys_mmap(16)
228 // DEPLOY (2026-06-16): bind ALL interfaces (was loopback-only) so the daemon is reachable for
229 // LAN verification before the sites-daemon reverse-proxy is wired; 0.0.0.0 still includes
230 // 127.0.0.1 so the eventual loopback proxy path is unaffected. :51850 is not publicly forwarded.
231 let a_rc: i64 = nx_http_server_addr_any(addr, NX_WIKI_ROUTE_PORT)
232 if a_rc != 16 { return NX_WIKI_MAIN_BIND_FAILED }
233
234 let lv: *i64 = (sys_mmap(8)) as *i64
235 let lfd: i64 = nx_http_server_listen(addr, NX_WIKI_MAIN_BACKLOG, lv)
236 if lfd < 0 { return NX_WIKI_MAIN_BIND_FAILED }
237
238 // ----- 2. Load admin hash (NX_WIKI_MAIN_ADMIN_HASH_PATH) -----
239 let admin_hash: i64 = nx_wiki_main_load_admin_hash(NX_WIKI_MAIN_ADMIN_HASH_PATH)
240 if admin_hash < 0 {
241 // Refuse to start without explicit operator-supplied hash file.
242 // NO silent default; NO pretend functionality.
243 return NX_WIKI_MAIN_NO_ADMIN_HASH
244 }
245
246 // ----- 3. Init session KV store -----
247 let sessions: *NxKvStore = (sys_mmap(NX_KV_STORE_BYTES)) as *NxKvStore
248 let kv_data: *u8 = sys_mmap(NX_WIKI_MAIN_KV_DATA_CAP)
249 let kv_index: *u8 = sys_mmap(NX_WIKI_MAIN_KV_INDEX_CAP)
250 let kv_rc: i64 = nx_kv_store_init(sessions,
251 kv_data, NX_WIKI_MAIN_KV_DATA_CAP,
252 kv_index, NX_WIKI_MAIN_KV_INDEX_CAP)
253 if kv_rc != NXKV_OK { return NX_WIKI_MAIN_KV_INIT_FAILED }
254
255 // ----- 4. Init NxAdminLoginConfig -----
256 let admin_cfg: *NxAdminLoginConfig = (sys_mmap(96)) as *NxAdminLoginConfig
257 let cfg_rc: i64 = nx_admin_login_config_init(admin_cfg,
258 NX_WIKI_MAIN_REALM,
259 NX_WIKI_MAIN_REALM_N,
260 NX_WIKI_MAIN_ADMIN_USERNAME,
261 NX_WIKI_MAIN_ADMIN_USERNAME_N,
262 admin_hash,
263 sessions,
264 NX_ALOGIN_DEFAULT_SESSION_MAX_AGE_S)
265 if cfg_rc != NX_ALOGIN_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
266
267 // ----- 5. Doc names buffer (V1 empty; V2 populated by index_builder) -----
268 let doc_names: *u8 = sys_mmap(4096)
269 // V1: zero entries. Wikilinks all render as broken until
270 // nx_wiki_index_builder.nx ships + populates this buffer.
271 let doc_names_count: i64 = 0
272
273 // ----- 6. Random buffer (refreshed per request) -----
274 let random_16: *u8 = sys_mmap(NX_WIKI_MAIN_RANDOM_BYTES)
275
276 // ----- 6b. Search flow (V1 search arc; LIVE for /wiki/search) -----
277 // Counter cap = 100_000 docs (well below 4M cap; right-sized for V1
278 // wiki where index is empty until nx_wiki_index_builder ships).
279 // docs_cap = 100 (default per NxSearchOnsiteCtx); postings = 10_000.
280 let search_flow: *NxSearchFlow = (sys_mmap(256)) as *NxSearchFlow
281 let sf_rc: i64 = nx_search_flow_init(search_flow,
282 100000, // counter_cap (max rowid + 1)
283 100, // docs_cap (top-N results)
284 10000) // postings_cap per term
285 if sf_rc != NX_SHF_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
286
287 // ----- 6c. Wiki doc store + index builder (V+2 auto-discover LIVE) -----
288 // Allocate NxWikiDocStore + NxWikiIndexBuilder; populate by RECURSIVELY
289 // walking the silicon content dir (V+2: nx_wcl_load_discover_tree
290 // replaced the hardcoded V1 seed list -- every *.md top-level AND in a
291 // subdir becomes a page + search hit with no code edit). The hardcoded
292 // §1 manifest remains as the loader's graceful fallback.
293 let doc_store: *NxWikiDocStore = (sys_mmap(256)) as *NxWikiDocStore
294 let store_rc: i64 = nx_wiki_doc_store_init(doc_store,
295 100, // docs_cap
296 65536, // titles_pool_cap
297 65536, // urls_pool_cap
298 4194304) // bodies_pool_cap (4 MB)
299 if store_rc != NX_WIB_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
300
301 let builder: *NxWikiIndexBuilder = (sys_mmap(64)) as *NxWikiIndexBuilder
302 let builder_rc: i64 = nx_wiki_index_builder_init(builder, doc_store, 100)
303 if builder_rc != NX_WIB_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
304
305 // Docs root: WSL2-accessible path to the nishi-silicon repo on the
306 // dev host (NX_WCL_SILICON_ROOT in nx_wiki_content_loader). Production
307 // deploys override via env (queued env-config wiring per Cardinal 17
308 // configuration hierarchy).
309 //
310 // FOLLOW-ON: honor nx_nishi_page_validator results (skip
311 // non-conformant docs with a structured log line) inside the walk.
312
313 // V+2 AUTO-DISCOVER: recursively walk the silicon content dir
314 // (top-level AND subdir charters like hdl/*.md) via the sovereign
315 // getdents64 work-stack in nx_wiki_content_loader. Every *.md becomes
316 // /wiki/<path-aware-slug> + a search hit with NO code edit -- drop a
317 // new charter into the dir (or a subdir) and it appears on next start.
318 //
319 // Per Cardinal 14 (graceful degradation): nx_wcl_load_discover_tree
320 // internally FALLS BACK to the hardcoded §1 manifest if the root dir
321 // can't be walked (e.g. a deploy host without the repo mounted), so
322 // the daemon always starts with a populated index. Per the desync
323 // landmine the function counts via the doc-store delta (ground truth),
324 // not a returned status code.
325 let n_loaded: i64 = nx_wcl_load_discover_tree(builder)
326 // graceful: a per-file miss is skipped inside the loader; n_loaded is
327 // the count actually indexed (>=0). An empty index is still a valid
328 // state for the search engine (empty intersection = 0 results).
329
330 // Finalize even if some reads failed -- empty index is a valid
331 // state per nx_search_onsite_engine (empty intersection = 0 results).
332 let finalize_rc: i64 = nx_wiki_index_builder_finalize(builder)
333 if finalize_rc != NX_WIB_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
334
335 // R1 EDIT OVERLAY: prefer a seg_store-persisted version of a page
336 // (key wikicur:<slug> under knowledge/store/wikipage-) over the
337 // filesystem .md seed, so admin edits survive restart (cardinal 13
338 // additive-data: history lives in the store, not the container fs).
339 // Per cardinal 14 a page whose persisted bytes do not fit is skipped;
340 // the daemon always starts with a populated, serveable index.
341 let n_overlaid: i64 = nx_wiki_page_overlay_store(doc_store)
342
343 // Hand the finalized index + store to the search arc.
344 let search_idx: *NxInvIndex = nx_wiki_index_builder_get_index(builder)
345 if (search_idx as i64) == 0 { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
346
347 // ----- 6d. Archive store (V1 archive subsystem; cite-time snapshots) -----
348 let archive_store: *NxArchiveStore = (sys_mmap(256)) as *NxArchiveStore
349 let arch_rc: i64 = nx_archive_store_init(archive_store, 100)
350 if arch_rc != NX_ARCH_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
351
352 // V1 seed snapshots demonstrating cite-time-snapshot pattern.
353 // V2 adds live fetch via nx_http_client + robots.txt + crawl_policy.
354 // V2 also adds wiki-build-pass walker that scans nishi-silicon
355 // docs for external citations and auto-archives each.
356 nx_archive_store_add(archive_store,
357 "https://en.wikipedia.org/wiki/Inverted_index" as *u8, 44,
358 "2026-05-27T00:00:00Z" as *u8, 20,
359 "Snapshot of Wikipedia: Inverted index. Cited in NISHI_SEARCH_CHARTER as foundational data structure (Salton 1971). V1 seed snapshot; V2 swaps for live fetch via nx_http_client per ARCHIVE_INTEGRATION_CHARTER section 5." as *u8,
360 221,
361 NX_ARCH_CT_PLAIN)
362 nx_archive_store_add(archive_store,
363 "https://en.wikipedia.org/wiki/BM25" as *u8, 34,
364 "2026-05-27T00:00:00Z" as *u8, 20,
365 "Snapshot of Wikipedia: BM25. Cited in NISHI_SEARCH_CHARTER section 5.2 as V2 ranker target (Robertson and Zaragoza 2009). V1 seed snapshot." as *u8,
366 145,
367 NX_ARCH_CT_PLAIN)
368 nx_archive_store_add(archive_store,
369 "https://spdx.org/licenses/" as *u8, 26,
370 "2026-05-27T00:00:00Z" as *u8, 20,
371 "Snapshot of SPDX License List. Cited in NISHI_PAGE_FORMAT_V1 section 2.8 as the canonical source for nishi-license meta tag values. V1 seed snapshot." as *u8,
372 157,
373 NX_ARCH_CT_PLAIN)
374
375 // ----- 6e. Pipeline artifact store (V2.0 P-1..P-5 + V2.5 LIVE) -----
376 // V2.5: recursive walker via sovereign nx_dir_list + getdents64
377 // discovers ALL .nx / .md / .sh artifacts under operator-supplied
378 // roots. Replaces V2.0's hardcoded 24-path list.
379 // V3+ swap: per-file last-commit-hash via nx_git_walk (queued).
380 let artifact_store: *NxArtifactStore = (sys_mmap(512)) as *NxArtifactStore
381 let art_rc: i64 = nx_artifact_store_init(artifact_store, 1000) // V2.5 cap; expected ~300-500 artifacts
382 if art_rc != NX_ART_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
383
384 // V2.5: recursive walker roots (covers ALL substrate artifacts).
385 // The hardcoded 24-path list from V2.0 is replaced by recursive
386 // discovery under these roots.
387 let pipeline_roots: *i64 = (sys_mmap(8 * 8)) as *i64
388 var rr: i64 = 0
389 pipeline_roots[rr] = ("/mnt/c/Users/elder/nishi-core/nxc2/runtime" as *u8) as i64; rr = rr + 1
390 pipeline_roots[rr] = ("/mnt/c/Users/elder/nishi-core/nxc2/docs" as *u8) as i64; rr = rr + 1
391 pipeline_roots[rr] = ("/mnt/c/Users/elder/nishi-core/nxc2/bench" as *u8) as i64; rr = rr + 1
392 pipeline_roots[rr] = ("/mnt/c/Users/elder/nishi-silicon" as *u8) as i64; rr = rr + 1
393
394 let walk_counters: *i64 = (sys_mmap(3 * 8)) as *i64
395 let walk_rc: i64 = nx_pipeline_recursive_walk_roots(artifact_store,
396 pipeline_roots, rr,
397 walk_counters)
398 // Per Cardinal 14: walker tolerates per-file errors; STORE_FULL
399 // is the only fatal verdict (and per V2.5 cap 1000, unlikely)
400 if walk_rc == 0 - NX_PREC_STORE_FULL { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
401 // walk_counters[0]=files walked; [1]=dirs visited; [2]=files skipped
402
403 // V2.0 fallback: keep the hardcoded list around as a typed array
404 // for the doc-store seed list (separate from pipeline). pp=0 here
405 // (legacy variable referenced in older code paths; preserved for
406 // compatibility with any path-list iteration below).
407 let pipeline_paths: *i64 = (sys_mmap(8)) as *i64
408 var pp: i64 = 0
409
410 // V2.0 hardcoded 24-path list deleted (replaced by V2.5 recursive
411 // walker above). Per Cardinal 25 (build intelligence; never strip):
412 // the deletion is justified because the recursive walker is the
413 // strict superset (discovers the 24 paths AND all others).
414
415 // ----- 6f. Dep graph + Tarjan SCC (V2.0 P-8 + P-8.5 LIVE) -----
416 // Builds directed graph from extracted .nx imports; runs Tarjan SCC;
417 // surfaces at /wiki/pipeline (cycles + per-artifact rev-deps).
418 let dep_graph: *NxDepGraph = (sys_mmap(256)) as *NxDepGraph
419 let dg_rc: i64 = nx_dep_graph_init(dep_graph, 1000, 10000)
420 if dg_rc != NX_DEPG_OK { return NX_WIKI_MAIN_CONFIG_INIT_FAILED }
421
422 let dg_counters: *i64 = (sys_mmap(5 * 8)) as *i64
423 let dg_build_rc: i64 = nx_pipeline_build_dep_graph(artifact_store, dep_graph,
424 dg_counters)
425 // Per Cardinal 14: dep graph build is non-fatal; daemon starts even if
426 // some imports unresolved
427 // dg_counters[0]=edges added; [1]=imports unresolved; [2]=ambiguous;
428 // [3]=artifacts re-read; [4]=read failures
429
430 // ----- 7. Per-process counters -----
431 var n_requests: i64 = 0
432 var n_errors: i64 = 0
433 var n_served: i64 = 0
434 let t_start: i64 = sys_now_ms()
435
436 // ----- 8. Accept loop -----
437 while n_served < NX_WIKI_ROUTE_REQUEST_BUDGET {
438 let av: *i64 = (sys_mmap(8)) as *i64
439 let cfd: i64 = nx_http_server_accept_one(lfd, av)
440 if cfd < 0 {
441 n_errors = n_errors + 1
442 n_served = n_served + 1
443 if n_served >= NX_WIKI_ROUTE_REQUEST_BUDGET { return NX_WIKI_MAIN_REQUEST_BUDGET_HIT }
444 }
445 if cfd >= 0 {
446 // Keepalive: process up to NX_WIKI_ROUTE_PER_CONN_BUDGET
447 // requests per TCP connection.
448 var reqs_on_conn: i64 = 0
449 var conn_alive: i64 = 1
450 while conn_alive == 1 {
451 if reqs_on_conn >= NX_WIKI_ROUTE_PER_CONN_BUDGET { conn_alive = 0 }
452 if n_served >= NX_WIKI_ROUTE_REQUEST_BUDGET { conn_alive = 0 }
453 if conn_alive == 1 {
454 let req_buf: *u8 = sys_mmap(NX_WIKI_ROUTE_REQ_CAP)
455 let om: *i64 = (sys_mmap(8)) as *i64
456 let opo: *i64 = (sys_mmap(8)) as *i64
457 let opl: *i64 = (sys_mmap(8)) as *i64
458 let ocl: *i64 = (sys_mmap(8)) as *i64
459 let obo: *i64 = (sys_mmap(8)) as *i64
460 let orn: *i64 = (sys_mmap(8)) as *i64
461
462 let rrc: i64 = nx_http_server_read_request(cfd,
463 req_buf,
464 NX_WIKI_ROUTE_REQ_CAP,
465 om, opo, opl,
466 ocl, obo, orn)
467 if rrc != NXS_OK { conn_alive = 0 }
468 if rrc == NXS_OK {
469 let path_ptr: *u8 = (req_buf as i64 + opo[0]) as *u8
470 let path_n: i64 = opl[0]
471 let resp_buf: *u8 = sys_mmap(NX_WIKI_ROUTE_RESP_CAP)
472 let resp_n: *i64 = (sys_mmap(8)) as *i64
473 resp_n[0] = 0
474
475 let now_ms: i64 = sys_now_ms()
476 let now_s: i64 = now_ms / 1000
477
478 // Refresh random_16 from CSPRNG (best-effort;
479 // failure means new sessions won't be minted
480 // safely but existing routes still work).
481 nx_wiki_main_fill_random(random_16)
482
483 let dispatch_rc: i64 = nx_wiki_route_dispatch(
484 req_buf, obo[0],
485 path_ptr, path_n,
486 om[0],
487 admin_cfg,
488 now_s,
489 search_flow, search_idx, doc_store, archive_store, artifact_store,
490 resp_buf, NX_WIKI_ROUTE_RESP_CAP,
491 resp_n)
492
493 if dispatch_rc != NX_WIKI_ROUTE_OK {
494 n_errors = n_errors + 1
495 }
496
497 // Send response (no-keepalive close per audit-server pattern).
498 let send_rc: i64 = nx_http_server_send_response_nokeep_close(
499 cfd, resp_buf, resp_n[0])
500 if send_rc != NXS_OK { conn_alive = 0 }
501 n_requests = n_requests + 1
502
503 // Log request.
504 let req_end_ms: i64 = sys_now_ms()
505 let req_dur: i64 = req_end_ms - now_ms
506 var log_status: i64 = 200
507 if dispatch_rc != NX_WIKI_ROUTE_OK { log_status = 500 }
508 nx_wiki_main_log_request(now_ms, om[0],
509 path_ptr, path_n,
510 log_status, req_dur)
511 }
512 n_served = n_served + 1
513 reqs_on_conn = reqs_on_conn + 1
514 }
515 }
516 sys_close(cfd)
517 }
518 }
519
520 sys_close(lfd)
521 return NX_WIKI_MAIN_REQUEST_BUDGET_HIT
522}