code wiki / wiki / nx_wiki_routes.nx

nx_wiki_routes.nx source

↩ module page · 690 lines · 34380 B

1// nx_wiki_routes.nx -- wiki HTTP route dispatcher (main daemon entry). 2// 3// Forks the nx_audit_server_routed.nx pattern (687-line template). 4// Reuses every existing HTTP / health / metrics / per-conn-budget 5// primitive per the duplicate-primitives audit; zero new HTTP / 6// daemon primitives introduced. 7// 8// COMPOSES (per NISHI_SMALL_SHARP_COMPOSABLE_STANDARD ยง4.1 M7): 9// nx_http_server bind / listen / accept / read / send 10// nx_http_health /health + /ready endpoints 11// nx_http_metrics /metrics (Prometheus text) 12// nx_log_jsonl per-request structured log emit 13// wiki/nx_wiki_admin_wiring /wiki/admin/* handler 14// wiki/nx_wiki_doc_render /wiki/<doc-name> handler 15// hub/nx_admin_login_flow (transitively via admin_wiring) 16// 17// COMPOSED BY: 18// wiki/nx_wiki_main.nx daemon main() entry (future commit) 19// 20// Status: V1 SEED. 2026-05-27. 21// 22// WINNER-TIER: BASELINE-C provisional (forks the audit-server's 23// shipped patterns; throughput + latency parity with 24// the audit server expected since it's the same 25// keepalive + per-conn-budget shape; paired bench 26// pending real wiki workload fixture) 27// INCUMBENTS: nginx (route dispatch), Caddy, Apache HTTPD, 28// substrate's own nx_audit_server_routed (closest 29// reference; same family) 30// NUMBERS: V1 ships the route table + dispatcher; paired 31// throughput bench vs nginx + audit-server pending 32// wiki-doc-set fixture 33// GAP: nginx has rewrite rules + load balancing + reverse 34// proxy + cache; V1 ships JUST wiki-specific routes 35// (small-sharp per the standard; nginx-class features 36// add as needed when wiki workload demands) 37// PLAN: M-next: paired throughput vs audit-server (same 38// hardware); add nx_http_metrics integration for 39// wiki-specific counters 40// EXEMPTION REASON: n/a; provisional pending measurement 41// 42// Route table (V1): 43// GET /wiki/ index page (link list of known docs) 44// GET /wiki/<doc-name> render doc (nx_wiki_doc_render) 45// GET /wiki/admin admin login (nx_wiki_admin_wiring) 46// POST /wiki/admin admin form submit (same) 47// GET /wiki/admin/logout logout (same) 48// GET /wiki/admin/dashboard protected dashboard (501 V1; queued) 49// GET /wiki/search search handler (501 V1; queued) 50// GET /wiki/archive/* archive router (501 V1; queued) 51// GET /health reuse nx_http_health 52// GET /ready reuse nx_http_health 53// GET /metrics reuse nx_http_metrics 54// * 404 Not Found 55// 56// HONEST V1 SCOPE per NISHI_CODE_HYGIENE_STANDARD M6 (anti-pretend-stub): 57// /wiki/admin/dashboard, /wiki/search, /wiki/archive/* return 58// HTTP 501 Not Implemented with body "Handler queued per 59// <module-name>; commits pending" -- explicit + auditable; the 60// route exists but tells the operator unambiguously that the 61// handler is V1-scope-deferred. NOT pretend functionality. 62 63import "nx_syscalls.nx" 64import "nx_http_server.nx" 65import "nx_http_health_emit.nx" // the /health EMITTER (2 call sites below). Was importing 66 // nx_http_health.nx, which since the 2026-07-21 dedupe is the URL PROBER. 67import "nx_http_metrics.nx" 68import "nx_log_jsonl.nx" 69import "nx_http_header_find.nx" 70import "wiki/nx_wiki_admin_wiring.nx" 71import "wiki/nx_wiki_doc_render.nx" 72import "wiki/nx_wiki_search_wiring.nx" 73import "wiki/nx_wiki_index_builder.nx" 74import "wiki/nx_wiki_doc_handler.nx" 75import "wiki/nx_wiki_edit_handler.nx" 76import "wiki/nx_wiki_status.nx" 77import "wiki/nx_wiki_article.nx" 78import "wiki/nx_wiki_queue.nx" 79import "wiki/nx_wiki_dashboard.nx" 80import "wiki/nx_wiki_archive_router.nx" 81import "wiki/nx_pipeline_route.nx" 82import "wiki/nx_artifact_store.nx" 83// wiki R4: static link-graph view (GET /wiki/graph). Composes the doc store + 84// the shared forward-link scanner (via nx_wiki_backlinks, pulled transitively) 85// into a JS-free <svg>. nx_wiki_graph imports nx_wiki_index_builder bare; the 86// resolver canonicalizes so it dedupes with wiki/nx_wiki_index_builder above. 87import "wiki/nx_wiki_graph.nx" 88import "hub/nx_search_handler_flow.nx" 89import "nx_search_inverted.nx" 90 91// ===== Sealed verdict surface ================================================= 92const NX_WIKI_ROUTE_OK: i64 = 0 93const NX_WIKI_ROUTE_BAD_INPUT: i64 = 1200 94const NX_WIKI_ROUTE_BUF_OVERFLOW: i64 = 1201 95const NX_WIKI_ROUTE_HANDLER_FAILED: i64 = 1202 96const NX_WIKI_ROUTE_NOT_IMPLEMENTED: i64 = 1203 97const NX_WIKI_ROUTE_UNKNOWN: i64 = 1204 98 99// ===== Daemon-wide sizing constants (M7) ================================================= 100const NX_WIKI_ROUTE_PORT: i64 = 51850 101const NX_WIKI_ROUTE_REQ_CAP: i64 = 16384 102const NX_WIKI_ROUTE_RESP_CAP: i64 = 1048576 // 1 MiB (docs can be large) 103const NX_WIKI_ROUTE_REQUEST_BUDGET: i64 = 1000000 104const NX_WIKI_ROUTE_PER_CONN_BUDGET: i64 = 100 // keepalive cap per audit-server pattern 105 106// HTTP method-kind (per nx_http_io convention) 107const NX_WIKI_ROUTE_METHOD_GET: i64 = 1 108const NX_WIKI_ROUTE_METHOD_POST: i64 = 2 109const NX_WIKI_ROUTE_METHOD_OPTIONS: i64 = 3 110 111// ===== Path-prefix matching helpers (forks audit-server pattern) ================================================= 112// 113// Returns 1 if path[0..prefix_n] == prefix AND the next byte is 114// `/` or end-of-path. Used for /wiki/admin/* and /wiki/archive/*. 115 116func nx_wiki_route_starts_with(path: *u8, path_n: i64, 117 prefix: *u8, prefix_n: i64) -> i64 { 118 if path_n < prefix_n { return 0 } 119 var i: i64 = 0 120 while i < prefix_n { 121 if i >= NX_WIKI_ROUTE_REQ_CAP { return 0 } // M3 bounded 122 if path[i] != prefix[i] { return 0 } 123 i = i + 1 124 } 125 if path_n == prefix_n { return 1 } 126 if path[prefix_n] == 0x2f as u8 { return 1 } // '/' 127 return 0 128} 129 130// Returns 1 if path ends with suffix (e.g. "/edit"). Used to route 131// /wiki/<slug>/edit to the R1 edit handler before the generic doc catch. 132func nx_wiki_route_ends_with(path: *u8, path_n: i64, 133 suffix: *u8, suffix_n: i64) -> i64 { 134 if path_n < suffix_n { return 0 } 135 let base: i64 = path_n - suffix_n 136 var i: i64 = 0 137 while i < suffix_n { 138 if i >= NX_WIKI_ROUTE_REQ_CAP { return 0 } 139 if path[base + i] != suffix[i] { return 0 } 140 i = i + 1 141 } 142 return 1 143} 144 145func nx_wiki_route_eq(path: *u8, path_n: i64, 146 target: *u8, target_n: i64) -> i64 { 147 if path_n != target_n { return 0 } 148 var i: i64 = 0 149 while i < path_n { 150 if i >= NX_WIKI_ROUTE_REQ_CAP { return 0 } // M3 bounded 151 if path[i] != target[i] { return 0 } 152 i = i + 1 153 } 154 return 1 155} 156 157// ===== Response builders ================================================= 158// 159// Each returns NX_WIKI_ROUTE_OK + final resp byte count via out-param. 160 161// Append helpers (M5 bounds + M8 propagation). 162func nx_wiki_route_append(out: *u8, off: *i64, cap: i64, 163 src: *u8, src_n: i64) -> i64 { 164 if off[0] + src_n > cap { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 165 var i: i64 = 0 166 while i < src_n { 167 if i >= cap { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } // M3 168 out[off[0] + i] = src[i] 169 i = i + 1 170 } 171 off[0] = off[0] + src_n 172 return NX_WIKI_ROUTE_OK 173} 174 175func nx_wiki_route_append_z(out: *u8, off: *i64, cap: i64, src: *u8) -> i64 { 176 if (src as i64) == 0 { return 0 - NX_WIKI_ROUTE_BAD_INPUT } 177 var n: i64 = 0 178 while n < cap { 179 if src[n] == (0 as u8) { return nx_wiki_route_append(out, off, cap, src, n) } 180 n = n + 1 181 } 182 return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW 183} 184 185// ===== / (wiki index) ================================================= 186// 187// V1 emits a static HTML page with links to the route catalog. V2 188// will read the doc index (nx_wiki_index_builder, queued) + emit 189// dynamic doc-list. 190 191func nx_wiki_route_serve_index(out: *u8, out_cap: i64, out_n: *i64) -> i64 { 192 out_n[0] = 0 193 let body: *u8 = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Nishi Wiki</title><style>body{font-family:system-ui,sans-serif;max-width:680px;margin:2em auto;padding:0 1em;line-height:1.5}h1{margin-bottom:.2em}ul{padding-left:1.2em}a{color:#27c;text-decoration:none}a:hover{text-decoration:underline}.status{color:#888;font-size:.9em}</style></head><body><h1>Nishi Sovereign Substrate Wiki</h1><p>Living-doc platform per NISHI_PRODUCT_OPS_PLATFORM.md. Routes:</p><ul><li><a href=\"/wiki/admin\">/wiki/admin</a> &mdash; admin login (elderwesto)</li><li><a href=\"/wiki/search\">/wiki/search</a> <span class=\"status\">(queued: nx_wiki_search_handler)</span></li><li><a href=\"/wiki/admin/dashboard\">/wiki/admin/dashboard</a> <span class=\"status\">(queued: nx_wiki_dashboard)</span></li><li><a href=\"/wiki/archive/\">/wiki/archive/*</a> <span class=\"status\">(queued: nx_wiki_archive_router)</span></li><li><a href=\"/health\">/health</a></li><li><a href=\"/ready\">/ready</a></li><li><a href=\"/metrics\">/metrics</a></li></ul><p class=\"status\">V1 build: doc render + admin login shipped; search / dashboard / archive subsystems shipping per ZERO_TO_ADVANCED.md per loop-discipline cardinal (one focused commit at a time).</p></body></html>" as *u8 194 let body_n: i64 = 1098 // hand-counted; V2 dynamic-len helper 195 196 let header: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8 197 let off: *i64 = (sys_mmap(8)) as *i64 198 off[0] = 0 199 let rc1: i64 = nx_wiki_route_append_z(out, off, out_cap, header) 200 if rc1 != NX_WIKI_ROUTE_OK { return rc1 } 201 202 // Append body_n decimal. 203 let scratch: *u8 = sys_mmap(16) 204 var n: i64 = body_n 205 var k: i64 = 0 206 while n > 0 { 207 if k >= 16 { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 208 scratch[k] = (0x30 + (n - (n / 10) * 10)) as u8 209 n = n / 10 210 k = k + 1 211 } 212 var ri: i64 = k - 1 213 while ri >= 0 { 214 if off[0] >= out_cap { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 215 out[off[0]] = scratch[ri] 216 off[0] = off[0] + 1 217 ri = ri - 1 218 } 219 let crlf2: *u8 = "\r\n\r\n" as *u8 220 let rc2: i64 = nx_wiki_route_append(out, off, out_cap, crlf2, 4) 221 if rc2 != NX_WIKI_ROUTE_OK { return rc2 } 222 223 // Body 224 let rc3: i64 = nx_wiki_route_append(out, off, out_cap, body, body_n) 225 if rc3 != NX_WIKI_ROUTE_OK { return rc3 } 226 227 out_n[0] = off[0] 228 return NX_WIKI_ROUTE_OK 229} 230 231// ===== /wiki/graph (wiki R4: static link-graph view) ========================= 232// 233// Builds the link graph over the doc store (nodes = pages; edges = resolvable 234// forward [[wikilinks]]) and renders it as a self-contained, JS-FREE <svg> 235// inside minimal page chrome. Static SVG = sovereign + renders identically 236// everywhere (no <script>, no event handlers). Returns HTTP 200 + HTML. 237// Graceful (Cardinal 14): with no/empty store the SVG is an empty ring, still a 238// well-formed 200 (never a 501 for this live route). 239const NX_WIKI_GRAPH_PAGE_CAP: i64 = 786432 // 768 KB SVG+chrome scratch 240 241func nx_wiki_route_serve_graph(doc_store: *NxWikiDocStore, 242 out: *u8, out_cap: i64, out_n: *i64) -> i64 { 243 out_n[0] = 0 244 245 // Build the graph + render the SVG into a scratch buffer. 246 let svg: *u8 = sys_mmap(NX_WIKI_GRAPH_PAGE_CAP) 247 let svg_used: *i64 = (sys_mmap(8)) as *i64 248 svg_used[0] = 0 249 var svg_n: i64 = 0 250 if (doc_store as i64) != 0 { 251 if doc_store.valid == 1 { 252 let g: *NxWikiGraph = (sys_mmap(256)) as *NxWikiGraph 253 let rc_gb: i64 = nx_wiki_graph_build(doc_store, g) 254 if rc_gb == NX_WGRAPH_OK { 255 let rc_sv: i64 = nx_wiki_graph_render_svg(g, doc_store, svg, 256 NX_WIKI_GRAPH_PAGE_CAP, svg_used) 257 if rc_sv == NX_WGRAPH_OK { svg_n = svg_used[0] } 258 } 259 } 260 } 261 262 // Page body = chrome (sticky nav reused from the doc page) + the SVG figure. 263 // Built into a body buffer so we can emit an exact Content-Length. 264 let body: *u8 = sys_mmap(NX_WIKI_GRAPH_PAGE_CAP) 265 var bo: i64 = 0 266 let pre: *u8 = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Link graph | Nishi Wiki</title>\n<style>body{margin:0;font-family:system-ui,sans-serif;background:#fff;color:#1a202c}header.nx-top{position:sticky;top:0;border-bottom:1px solid #e2e8f0;background:#fff}.nx-nav{max-width:1080px;margin:0 auto;display:flex;align-items:center;gap:1.1em;padding:.65em 1.2em}.nx-brand{font-weight:800;color:#1a202c;text-decoration:none}.nx-brand span{color:#2c7a7b}.nx-nav a{color:#5a6573;text-decoration:none;font-weight:600;font-size:.92em}main{max-width:1080px;margin:0 auto;padding:1.6em 1.2em}h1{font-size:1.6em}figure{margin:0}svg.nx-graph{width:100%;height:auto;border:1px solid #e2e8f0;border-radius:10px;background:#fcfdff}</style>\n</head>\n<body>\n<header class=\"nx-top\"><nav class=\"nx-nav\"><a class=\"nx-brand\" href=\"/wiki/\">Nishi<span>Wiki</span></a><a href=\"/wiki/\">Home</a><a href=\"/wiki/graph\">Graph</a><a href=\"/wiki/dashboard\">Dashboard</a></nav></header>\n<main><h1>Wiki link graph</h1><p>Pages are nodes; resolvable <code>[[wikilinks]]</code> are edges. Static, JavaScript-free SVG.</p><figure>" as *u8 267 var pi: i64 = 0 268 while pre[pi] != (0 as u8) { body[bo + pi] = pre[pi]; pi = pi + 1 } 269 bo = bo + pi 270 // SVG (or a tiny empty-state note when the graph could not render). 271 if svg_n > 0 { 272 var si: i64 = 0 273 while si < svg_n { 274 if si >= NX_WIKI_GRAPH_PAGE_CAP { si = svg_n } 275 if si < svg_n { body[bo + si] = svg[si]; si = si + 1 } 276 } 277 bo = bo + svg_n 278 } 279 if svg_n == 0 { 280 let empt: *u8 = "<p>No pages indexed yet.</p>" as *u8 281 var ei: i64 = 0 282 while empt[ei] != (0 as u8) { body[bo + ei] = empt[ei]; ei = ei + 1 } 283 bo = bo + ei 284 } 285 let post: *u8 = "</figure></main>\n</body>\n</html>\n" as *u8 286 var qi: i64 = 0 287 while post[qi] != (0 as u8) { body[bo + qi] = post[qi]; qi = qi + 1 } 288 bo = bo + qi 289 290 // HTTP/1.1 200 + Content-Type + dynamic Content-Length + CRLFCRLF + body. 291 let off: *i64 = (sys_mmap(8)) as *i64 292 off[0] = 0 293 let header: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8 294 let rc1: i64 = nx_wiki_route_append_z(out, off, out_cap, header) 295 if rc1 != NX_WIKI_ROUTE_OK { return rc1 } 296 // body length decimal 297 let scratch: *u8 = sys_mmap(16) 298 var n: i64 = bo 299 var k: i64 = 0 300 if n == 0 { scratch[0] = 0x30 as u8; k = 1 } 301 while n > 0 { 302 if k >= 16 { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 303 scratch[k] = (0x30 + (n - (n / 10) * 10)) as u8 304 n = n / 10 305 k = k + 1 306 } 307 var ri: i64 = k - 1 308 while ri >= 0 { 309 if off[0] >= out_cap { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 310 out[off[0]] = scratch[ri] 311 off[0] = off[0] + 1 312 ri = ri - 1 313 } 314 let crlf2: *u8 = "\r\n\r\n" as *u8 315 let rc2: i64 = nx_wiki_route_append(out, off, out_cap, crlf2, 4) 316 if rc2 != NX_WIKI_ROUTE_OK { return rc2 } 317 let rc3: i64 = nx_wiki_route_append(out, off, out_cap, body, bo) 318 if rc3 != NX_WIKI_ROUTE_OK { return rc3 } 319 320 out_n[0] = off[0] 321 return NX_WIKI_ROUTE_OK 322} 323 324// ===== Honest 501 for queued handlers ================================================= 325// 326// Per NISHI_CODE_HYGIENE_STANDARD M6 anti-pretend-stub: the route 327// exists + returns a clear "handler queued; module name + plan" 328// response. NOT pretend functionality; explicit + auditable. 329 330func nx_wiki_route_serve_501(out: *u8, out_cap: i64, out_n: *i64, 331 handler_name: *u8, handler_name_n: i64) -> i64 { 332 out_n[0] = 0 333 let header: *u8 = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8 334 let body_pre: *u8 = "<!DOCTYPE html><html><body><h1>501 Not Implemented</h1><p>This route's handler is queued for a future commit per loop-discipline. Module name: <code>" as *u8 335 let body_pre_n: i64 = 175 336 let body_post: *u8 = "</code></p><p>The wiki ships handlers one focused commit at a time per the cardinal cluster. See <code>NISHI_PRODUCT_OPS_PLATFORM.md</code> for the full build plan.</p></body></html>" as *u8 337 let body_post_n: i64 = 196 338 let body_total: i64 = body_pre_n + handler_name_n + body_post_n 339 340 let off: *i64 = (sys_mmap(8)) as *i64 341 off[0] = 0 342 let rc1: i64 = nx_wiki_route_append_z(out, off, out_cap, header) 343 if rc1 != NX_WIKI_ROUTE_OK { return rc1 } 344 345 // Length decimal 346 let scratch: *u8 = sys_mmap(16) 347 var n: i64 = body_total 348 var k: i64 = 0 349 while n > 0 { 350 if k >= 16 { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 351 scratch[k] = (0x30 + (n - (n / 10) * 10)) as u8 352 n = n / 10 353 k = k + 1 354 } 355 var ri: i64 = k - 1 356 while ri >= 0 { 357 if off[0] >= out_cap { return 0 - NX_WIKI_ROUTE_BUF_OVERFLOW } 358 out[off[0]] = scratch[ri] 359 off[0] = off[0] + 1 360 ri = ri - 1 361 } 362 let crlf2: *u8 = "\r\n\r\n" as *u8 363 let rc2: i64 = nx_wiki_route_append(out, off, out_cap, crlf2, 4) 364 if rc2 != NX_WIKI_ROUTE_OK { return rc2 } 365 366 let rc3: i64 = nx_wiki_route_append(out, off, out_cap, body_pre, body_pre_n) 367 if rc3 != NX_WIKI_ROUTE_OK { return rc3 } 368 let rc4: i64 = nx_wiki_route_append(out, off, out_cap, handler_name, handler_name_n) 369 if rc4 != NX_WIKI_ROUTE_OK { return rc4 } 370 let rc5: i64 = nx_wiki_route_append(out, off, out_cap, body_post, body_post_n) 371 if rc5 != NX_WIKI_ROUTE_OK { return rc5 } 372 373 out_n[0] = off[0] 374 return NX_WIKI_ROUTE_OK 375} 376 377// ===== Plain 404 ================================================= 378 379func nx_wiki_route_serve_404(out: *u8, out_cap: i64, out_n: *i64) -> i64 { 380 let resp: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 56\r\n\r\n<!DOCTYPE html><html><body><h1>404</h1><p>Wiki: not found.</p></body></html>" as *u8 381 let resp_n: i64 = 167 382 out_n[0] = 0 383 let off: *i64 = (sys_mmap(8)) as *i64 384 off[0] = 0 385 let rc: i64 = nx_wiki_route_append(out, off, out_cap, resp, resp_n) 386 if rc != NX_WIKI_ROUTE_OK { return rc } 387 out_n[0] = off[0] 388 return NX_WIKI_ROUTE_OK 389} 390 391// ===== 405 Method Not Allowed ================================================= 392 393func nx_wiki_route_serve_405(out: *u8, out_cap: i64, out_n: *i64) -> i64 { 394 let resp: *u8 = "HTTP/1.1 405 Method Not Allowed\r\nContent-Type: text/plain\r\nContent-Length: 19\r\n\r\nMethod not allowed\n" as *u8 395 let resp_n: i64 = 117 396 out_n[0] = 0 397 let off: *i64 = (sys_mmap(8)) as *i64 398 off[0] = 0 399 let rc: i64 = nx_wiki_route_append(out, off, out_cap, resp, resp_n) 400 if rc != NX_WIKI_ROUTE_OK { return rc } 401 out_n[0] = off[0] 402 return NX_WIKI_ROUTE_OK 403} 404 405// ===== Top-level dispatcher ================================================= 406// 407// Caller (the daemon main loop) hands in the parsed HTTP request + 408// pre-allocated response buffer + auxiliary state (admin config, 409// random bytes, now_s). Dispatcher routes by method + path; calls 410// the appropriate handler; returns NX_WIKI_ROUTE_OK + final resp 411// byte count via out_resp_n. 412 413// SITES-LIVE 2026-05-27 SIGNATURE TRIM (15 args; native ELF parser 414// IR cap is 16 args/call). Dropped per V-MODAUTH-6 + simplification: 415// - random_16: cookie-session minting removed; no longer needed 416// - doc_names_{buf,cap,count}: wikilink resolution V+1 feature; not 417// load-bearing for any V1 route currently. Wikilinks render as 418// broken until V+1 wikilink-resolver wired through new arg. 419func nx_wiki_route_dispatch(http_req_buf: *u8, http_headers_end: i64, 420 url_path: *u8, url_path_n: i64, 421 method_kind: i64, 422 admin_cfg: *NxAuthContext, 423 now_s: i64, 424 search_flow: *NxSearchFlow, 425 search_idx: *NxInvIndex, 426 doc_store: *NxWikiDocStore, 427 archive_store: *NxArchiveStore, 428 artifact_store: *NxArtifactStore, 429 resp_buf: *u8, resp_cap: i64, 430 out_resp_n: *i64) -> i64 { 431 if (url_path as i64) == 0 { return 0 - NX_WIKI_ROUTE_BAD_INPUT } 432 if (resp_buf as i64) == 0 { return 0 - NX_WIKI_ROUTE_BAD_INPUT } 433 if (out_resp_n as i64) == 0 { return 0 - NX_WIKI_ROUTE_BAD_INPUT } 434 out_resp_n[0] = 0 435 436 // /health 437 if nx_wiki_route_eq(url_path, url_path_n, "/health" as *u8, 7) == 1 { 438 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 439 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 440 } 441 let scratch: *u8 = sys_mmap(64) 442 let rc: i64 = nx_http_emit_health(resp_buf, resp_cap, out_resp_n, 443 NXHL_STATUS_PASS, 0, 1, scratch) 444 if rc != 0 { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 445 return NX_WIKI_ROUTE_OK 446 } 447 448 // /ready 449 if nx_wiki_route_eq(url_path, url_path_n, "/ready" as *u8, 6) == 1 { 450 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 451 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 452 } 453 let scratch: *u8 = sys_mmap(64) 454 let rc: i64 = nx_http_emit_health(resp_buf, resp_cap, out_resp_n, 455 NXHL_STATUS_PASS, 0, 1, scratch) 456 if rc != 0 { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 457 return NX_WIKI_ROUTE_OK 458 } 459 460 // /wiki/admin (and subpaths) 461 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki/admin" as *u8, 11) == 1 { 462 // GET and POST both go to the wiring; wiring dispatches by method internally 463 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 464 if method_kind != NX_WIKI_ROUTE_METHOD_POST { 465 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 466 } 467 } 468 // /wiki/admin/dashboard -- LIVE (V1 product-ops dashboard). 469 // Same handler as /wiki/dashboard; admin route reserved for 470 // V2 dashboard variants with privileged stats (request counters, 471 // error rates, telemetry) that only authenticated admins see. 472 if nx_wiki_route_eq(url_path, url_path_n, 473 "/wiki/admin/dashboard" as *u8, 21) == 1 { 474 let rc: i64 = nx_wiki_dashboard_handle(doc_store, 475 resp_buf, resp_cap, out_resp_n) 476 if rc != NX_WDB_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 477 return NX_WIKI_ROUTE_OK 478 } 479 let rc: i64 = nx_wiki_admin_handle(admin_cfg, 480 http_req_buf, http_headers_end, 481 url_path, url_path_n, 482 method_kind, 483 now_s, 484 resp_buf, resp_cap, out_resp_n) 485 if rc != NX_WAW_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 486 return NX_WIKI_ROUTE_OK 487 } 488 489 // /wiki/search -- LIVE (wiki R3: BM25-RANKED + ARCHIVE-UNIFIED). 490 // Wired via wiki/nx_wiki_search_wiring.nx (THIN per-site adapter). 491 // Ranking upgraded from the onsite engine's presence/AND to real Okapi 492 // BM25 (nx_wiki_bm25_search -> nx_bm25), and search now covers BOTH the 493 // live doc store AND the no-link-rot archive (seg_store term index over 494 // knowledge/store/wikiarchive-) -- the wiki+archive unified toward the 495 // Library. A populated doc_store uses the BM25 path; if the store is not 496 // yet built, falls back to the onsite-engine V2 path (still well-formed 497 // HTTP 200, never a 501 for a live route). 498 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki/search" as *u8, 12) == 1 { 499 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 500 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 501 } 502 if (search_flow as i64) == 0 { 503 return nx_wiki_route_serve_501(resp_buf, resp_cap, out_resp_n, 504 "search_flow not initialized at startup" as *u8, 38) 505 } 506 var has_store: i64 = 0 507 if (doc_store as i64) != 0 { if doc_store.valid == 1 { if doc_store.doc_count > 0 { has_store = 1 } } } 508 if has_store == 1 { 509 // BM25-ranked + archive-unified (production prefix WAR_PREFIX). 510 let rcb: i64 = nx_wiki_search_handle_bm25(search_flow, doc_store, WAR_PREFIX, 511 url_path, url_path_n, 512 resp_buf, resp_cap, out_resp_n) 513 if rcb != NX_WSW_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 514 return NX_WIKI_ROUTE_OK 515 } 516 let rc: i64 = nx_wiki_search_handle_v2(search_flow, search_idx, doc_store, 517 url_path, url_path_n, 518 resp_buf, resp_cap, out_resp_n) 519 if rc != NX_WSW_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 520 return NX_WIKI_ROUTE_OK 521 } 522 523 // /wiki/pipeline + /wiki/pipeline/<stage> -- V2.0 P-3 (substrate self-audit) 524 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki/pipeline" as *u8, 14) == 1 { 525 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 526 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 527 } 528 if (artifact_store as i64) == 0 { 529 return nx_wiki_route_serve_501(resp_buf, resp_cap, out_resp_n, 530 "artifact_store not initialized at startup" as *u8, 41) 531 } 532 let rc: i64 = nx_pipeline_route(artifact_store, url_path, url_path_n, 533 resp_buf, resp_cap, out_resp_n) 534 if rc != NX_PROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 535 return NX_WIKI_ROUTE_OK 536 } 537 538 // /wiki/dashboard -- LIVE (V1 product-ops dashboard; public read). 539 if nx_wiki_route_eq(url_path, url_path_n, "/wiki/dashboard" as *u8, 15) == 1 { 540 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 541 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 542 } 543 let rc: i64 = nx_wiki_dashboard_handle(doc_store, 544 resp_buf, resp_cap, out_resp_n) 545 if rc != NX_WDB_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 546 return NX_WIKI_ROUTE_OK 547 } 548 549 // /wiki/archive + /wiki/wayback/<hash> -- LIVE (V1 archive router). 550 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki/archive" as *u8, 13) == 1 { 551 let rc: i64 = nx_wiki_archive_route(archive_store, url_path, url_path_n, 552 resp_buf, resp_cap, out_resp_n) 553 if rc == NX_ARCH_OK { return NX_WIKI_ROUTE_OK } 554 return nx_wiki_route_serve_404(resp_buf, resp_cap, out_resp_n) 555 } 556 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki/wayback/" as *u8, 14) == 1 { 557 let rc: i64 = nx_wiki_archive_route(archive_store, url_path, url_path_n, 558 resp_buf, resp_cap, out_resp_n) 559 if rc == NX_ARCH_OK { return NX_WIKI_ROUTE_OK } 560 return nx_wiki_route_serve_404(resp_buf, resp_cap, out_resp_n) 561 } 562 563 // /wiki/graph -- LIVE (wiki R4: static link-graph view). Exact-match BEFORE 564 // the generic /wiki/<doc-name> catch so "graph" is not rendered as a doc. 565 // Renders a JS-free <svg> of the page link graph over the doc store. 566 if nx_wiki_route_eq(url_path, url_path_n, "/wiki/graph" as *u8, 11) == 1 { 567 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 568 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 569 } 570 let rc: i64 = nx_wiki_route_serve_graph(doc_store, resp_buf, resp_cap, out_resp_n) 571 if rc != NX_WIKI_ROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 572 return NX_WIKI_ROUTE_OK 573 } 574 575 // /wiki/status -- LIVE distance-to-S-class scorecard (sovereign, 576 // Nishi-rendered from Nishi data; no shell/TSV). Exact-match BEFORE 577 // the generic /wiki/<doc-name> catch so "status" is not 404'd as a doc. 578 if nx_wiki_route_eq(url_path, url_path_n, "/wiki/status" as *u8, 12) == 1 { 579 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 580 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 581 } 582 let rc: i64 = nx_wiki_status_handle(resp_buf, resp_cap, out_resp_n) 583 if rc != NX_WIKI_ROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 584 return NX_WIKI_ROUTE_OK 585 } 586 587 // /wiki/queue -- the live partnership work queue (dynamic; reads the journal) 588 if nx_wiki_route_eq(url_path, url_path_n, "/wiki/queue" as *u8, 11) == 1 { 589 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 590 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 591 } 592 let rc: i64 = nx_wiki_queue_handle(resp_buf, resp_cap, out_resp_n) 593 if rc != NX_WIKI_ROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 594 return NX_WIKI_ROUTE_OK 595 } 596 597 // /wiki/components -- Gitea-style component index 598 if nx_wiki_route_eq(url_path, url_path_n, "/wiki/components" as *u8, 16) == 1 { 599 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 600 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 601 } 602 let rc: i64 = nx_wiki_components_handle(resp_buf, resp_cap, out_resp_n) 603 if rc != NX_WIKI_ROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 604 return NX_WIKI_ROUTE_OK 605 } 606 607 // /wiki/component/<slug> -- researched + benchmarked component article. 608 // Prefix "/wiki/component" (15) matches "/wiki/component/<slug>" via the 609 // segment-boundary rule; slug begins at offset 16. 610 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki/component" as *u8, 15) == 1 { 611 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 612 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 613 } 614 if url_path_n <= 16 { 615 let rci: i64 = nx_wiki_components_handle(resp_buf, resp_cap, out_resp_n) 616 if rci != NX_WIKI_ROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 617 return NX_WIKI_ROUTE_OK 618 } 619 let slug: *u8 = (url_path as i64 + 16) as *u8 620 let slug_n: i64 = url_path_n - 16 621 let rc: i64 = nx_wiki_article_handle(slug, slug_n, resp_buf, resp_cap, out_resp_n) 622 if rc != NX_WIKI_ROUTE_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 623 return NX_WIKI_ROUTE_OK 624 } 625 626 // /wiki/<slug>/edit -- R1 AUTH-GATED page edit (GET form + POST save). 627 // MUST precede the generic /wiki/<doc-name> catch (which is GET-only and 628 // would 405 the POST). Both verbs gate on the reused session validator 629 // (nx_modern_auth_validate_session) inside nx_wiki_edit_handle; an 630 // unauthenticated edit is refused with 401, persisting nothing. 631 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki" as *u8, 5) == 1 { 632 if nx_wiki_route_ends_with(url_path, url_path_n, "/edit" as *u8, 5) == 1 { 633 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 634 if method_kind != NX_WIKI_ROUTE_METHOD_POST { 635 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 636 } 637 } 638 let rc: i64 = nx_wiki_edit_handle(doc_store, admin_cfg, 639 http_req_buf, http_headers_end, 640 url_path, url_path_n, 641 method_kind, now_s, 642 resp_buf, resp_cap, out_resp_n) 643 if rc != NX_WEH_OK { return 0 - NX_WIKI_ROUTE_HANDLER_FAILED } 644 return NX_WIKI_ROUTE_OK 645 } 646 } 647 648 // /wiki/<doc-name> -- render a doc 649 if nx_wiki_route_starts_with(url_path, url_path_n, "/wiki" as *u8, 5) == 1 { 650 if method_kind != NX_WIKI_ROUTE_METHOD_GET { 651 return nx_wiki_route_serve_405(resp_buf, resp_cap, out_resp_n) 652 } 653 // /wiki/ index page (special-case) 654 if url_path_n == 6 { 655 // "/wiki/" exactly 656 return nx_wiki_route_serve_index(resp_buf, resp_cap, out_resp_n) 657 } 658 if url_path_n == 5 { 659 // "/wiki" exactly 660 return nx_wiki_route_serve_index(resp_buf, resp_cap, out_resp_n) 661 } 662 // /wiki/<doc-name> -- LIVE (V1 doc handler + V2.0 P-4 pipeline banner). 663 // Composes doc_store URL lookup + nx_wiki_doc_render write_header_v1 664 // (V1 page format conformant) + V2.0 pipeline-stage banner via 665 // hub/nx_pipeline_banner (silent no-op if URL not in artifact-store 666 // map) + markdown body emit + footer. 404 if doc not in store. 667 let rc: i64 = nx_wiki_doc_handle(doc_store, artifact_store, 668 url_path, url_path_n, 669 resp_buf, resp_cap, out_resp_n) 670 if rc == NX_WDH_OK { return NX_WIKI_ROUTE_OK } 671 if rc == 0 - NX_WDH_NOT_FOUND { 672 return nx_wiki_route_serve_404(resp_buf, resp_cap, out_resp_n) 673 } 674 return 0 - NX_WIKI_ROUTE_HANDLER_FAILED 675 } 676 677 // / -- redirect to /wiki/ 678 if nx_wiki_route_eq(url_path, url_path_n, "/" as *u8, 1) == 1 { 679 let redirect: *u8 = "HTTP/1.1 302 Found\r\nLocation: /wiki/\r\nContent-Length: 0\r\n\r\n" as *u8 680 let off: *i64 = (sys_mmap(8)) as *i64 681 off[0] = 0 682 let rc: i64 = nx_wiki_route_append_z(resp_buf, off, resp_cap, redirect) 683 if rc != NX_WIKI_ROUTE_OK { return rc } 684 out_resp_n[0] = off[0] 685 return NX_WIKI_ROUTE_OK 686 } 687 688 // /* -> 404 689 return nx_wiki_route_serve_404(resp_buf, resp_cap, out_resp_n) 690}