code wiki / (root) / nx_compare_gw_lib.nx

nx_compare_gw_lib.nx source

↩ module page · 541 lines · 26076 B

1// nx_compare_gw_lib.nx -- THE PURE DECISION CORE of the /compare gateway: who may see which board. 2// 3// WHY A GATEWAY AT ALL (operator 2026-08-27: "/compare against the login walls so its behind the same thing 4// as opaque and has the same access structure -- i can see them as the owner but then others see different 5// levels of /compare so i can provide a client a /compare"). Today /compare is plain static docroot bytes: 6// no route row, no session, every domain public. This lib decides, per request, from THREE inputs -- 7// the viewer's LEVEL (resolved by the estate's one access spine, nx_hr_access: OPAQUE session -> HR level, 8// deny-by-default), the DOMAIN the path names, and the ACCESS the domain declares as DATA in 9// knowledge/compare/access.conf -- whether the bytes are served, and what an index at that level lists. 10// 11// ACCESS IS DATA, NEVER CODE (rule 11). access.conf rows: 12// default|<access> the access every domain gets unless a row names it (ABSENT = operator: fail-closed) 13// domain|<dom>|<access> one domain's access 14// access vocabulary, identical to nx_maturity_registry's mr_level_of so the hub and the compare wall cannot 15// disagree on a word: public=0 viewer=1 member=2 operator=3 ; an UNKNOWN word reads operator (fail-closed: 16// a typo locks a board to the owner, it never exposes one). HR enrols owner=3 and family/client=1, so a 17// client handed a /compare sees the public boards plus every board declared viewer; the owner sees all. 18// 19// NO-LEAK AT THE RENDER LAYER: a viewer's index bytes never contain the name of a board above their level 20// (absent from the bytes, not hidden by style), and a request for such a board answers exactly like a 21// request for a board that does not exist. Not-even-listed is the property, and it is measured by the gate. 22// license_tier: ORIGINAL No hw writes (Rule 26). 23import "nx_syscalls.nx" 24 25const CG_LVL_PUBLIC: i64 = 0 26const CG_LVL_VIEWER: i64 = 1 27const CG_LVL_MEMBER: i64 = 2 28const CG_LVL_OPERATOR: i64 = 3 29 30const CG_ROW_DEFAULT: *u8 = "default" as *u8 31const CG_ROW_DOMAIN: *u8 = "domain" as *u8 32const CG_PREFIX: *u8 = "/compare" as *u8 33// EC47 (2026-09-16): the feed link the served root page advertises (declared here, above its reader in cg_index_open: a module 34// const read above its declaration folds to 0, and the builder refuses that) 35const CG_FEED_LINK_TAG: *u8 = "<link rel='alternate' type='application/rss+xml' title='Nishi /compare daily positions and reviews' href='/compare/feed.xml'>" as *u8 36 37// decisions -- one per request, named 38const CG_D_SERVE: i64 = 1 39const CG_D_DENY: i64 = 0 40const CG_D_NOTFOUND: i64 = 0 - 1 41const CG_D_BADPATH: i64 = 0 - 2 42 43const CG_CH_NL: i64 = 10 44const CG_CH_CR: i64 = 13 45const CG_CH_SPACE: i64 = 32 46const CG_CH_HASH: i64 = 35 47const CG_CH_QUOTE: i64 = 34 48const CG_CH_BSLASH: i64 = 92 49const CG_CH_DOT: i64 = 46 50const CG_CH_SLASH: i64 = 47 51const CG_CH_PIPE: i64 = 124 52const CG_CH_QMARK: i64 = 63 53const CG_CH_COLON: i64 = 58 54const CG_MAXF: i64 = 4 55const CG_I64_BYTES: i64 = 8 56const CG_DOM_CAP: i64 = 128 57 58func cg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 59func cg_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var p: i64 = o; while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 } return p } 60func cg_catn(d: *u8, o: i64, src: *u8, n: i64) -> i64 { var i: i64 = 0; var p: i64 = o; while i < n { d[p] = src[i]; p = p + 1; i = i + 1 } return p } 61func cg_catnum(d: *u8, o: i64, v: i64) -> i64 { 62 let t: *u8 = sys_mmap(28) 63 var m: i64 = v 64 var p: i64 = o 65 if m < 0 { d[p] = 45 as u8; p = p + 1; m = 0 - m } 66 var k: i64 = 0 67 if m == 0 { t[0] = 48 as u8; k = 1 } 68 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 69 var i: i64 = 0 70 while i < k { d[p] = t[k-1-i]; p = p + 1; i = i + 1 } 71 sys_munmap(t, 28) 72 return p 73} 74// slice[a..b) == NUL-terminated literal 75func cg_sl_eq(buf: *u8, a: i64, b: i64, lit: *u8) -> i64 { 76 let l: i64 = cg_slen(lit) 77 if b - a != l { return 0 } 78 var i: i64 = 0 79 while i < l { if buf[a+i] != lit[i] { return 0 } i = i + 1 } 80 return 1 81} 82func cg_streq(a: *u8, b: *u8) -> i64 { 83 let n: i64 = cg_slen(a) 84 if n != cg_slen(b) { return 0 } 85 var i: i64 = 0 86 while i < n { if a[i] != b[i] { return 0 } i = i + 1 } 87 return 1 88} 89 90// ---- the access vocabulary: the SAME four words and the SAME fail-closed default as mr_level_of ---- 91func cg_access_level(buf: *u8, a: i64, b: i64) -> i64 { 92 if cg_sl_eq(buf, a, b, "public" as *u8) == 1 { return CG_LVL_PUBLIC } 93 if cg_sl_eq(buf, a, b, "viewer" as *u8) == 1 { return CG_LVL_VIEWER } 94 if cg_sl_eq(buf, a, b, "member" as *u8) == 1 { return CG_LVL_MEMBER } 95 if cg_sl_eq(buf, a, b, "operator" as *u8) == 1 { return CG_LVL_OPERATOR } 96 return CG_LVL_OPERATOR 97} 98func cg_level_name(l: i64) -> *u8 { 99 if l == CG_LVL_PUBLIC { return "public" as *u8 } 100 if l == CG_LVL_VIEWER { return "viewer" as *u8 } 101 if l == CG_LVL_MEMBER { return "member" as *u8 } 102 return "operator" as *u8 103} 104 105// ---- the conf: rows over a COUNTED buffer, comment lines skipped, fields split on the pipe ---- 106// Walk every row; call once per row via the caller's loop. Returns the field count for the row at [s,e) 107// into fa/fb (field start/end offsets), at most CG_MAXF fields. 108func cg_row_fields(buf: *u8, s: i64, e: i64, fa: *i64, fb: *i64) -> i64 { 109 var nf: i64 = 0 110 var start: i64 = s 111 var i: i64 = s 112 while i <= e { 113 var atend: i64 = 0 114 if i == e { atend = 1 } else { if buf[i] == (CG_CH_PIPE as u8) { atend = 1 } } 115 if atend == 1 { 116 if nf < CG_MAXF { fa[nf] = start; fb[nf] = i; nf = nf + 1 } 117 start = i + 1 118 } 119 i = i + 1 120 } 121 return nf 122} 123func cg_eol(buf: *u8, n: i64, i: i64) -> i64 { 124 var e: i64 = i 125 while e < n { if buf[e] == (CG_CH_NL as u8) { return e } e = e + 1 } 126 return n 127} 128 129// THE REQUIRED LEVEL FOR A DOMAIN. A `domain|<dom>|<access>` row wins; else the `default|<access>` row; 130// else OPERATOR -- a conf that names nothing exposes nothing. Later rows win over earlier ones for the same 131// key, so an append is an override (rule 13: history stays in the file). 132func cg_required(conf: *u8, n: i64, dom: *u8) -> i64 { 133 let fa: *i64 = sys_mmap(CG_MAXF * CG_I64_BYTES) as *i64 134 let fb: *i64 = sys_mmap(CG_MAXF * CG_I64_BYTES) as *i64 135 var dflt: i64 = CG_LVL_OPERATOR 136 var found: i64 = 0 - 1 137 var i: i64 = 0 138 while i < n { 139 let e: i64 = cg_eol(conf, n, i) 140 if e > i { if conf[i] != (CG_CH_HASH as u8) { 141 let nf: i64 = cg_row_fields(conf, i, e, fa, fb) 142 if nf == 2 { if cg_sl_eq(conf, fa[0], fb[0], CG_ROW_DEFAULT) == 1 { dflt = cg_access_level(conf, fa[1], fb[1]) } } 143 if nf == 3 { if cg_sl_eq(conf, fa[0], fb[0], CG_ROW_DOMAIN) == 1 { 144 if cg_sl_eq(conf, fa[1], fb[1], dom) == 1 { found = cg_access_level(conf, fa[2], fb[2]) } 145 } } 146 } } 147 i = e + 1 148 } 149 sys_munmap(fa as *u8, CG_MAXF * CG_I64_BYTES) 150 sys_munmap(fb as *u8, CG_MAXF * CG_I64_BYTES) 151 if found >= 0 { return found } 152 return dflt 153} 154 155// Enumerate the DECLARED domains of the conf into a caller buffer as NUL-separated names; returns the count. 156// Only `domain|` rows are declared -- a board that is not named is governed by the default and is listed by 157// the daemon from the docroot, never from here. 158func cg_declared(conf: *u8, n: i64, out: *u8, cap: i64, count_out: *i64) -> i64 { 159 let fa: *i64 = sys_mmap(CG_MAXF * CG_I64_BYTES) as *i64 160 let fb: *i64 = sys_mmap(CG_MAXF * CG_I64_BYTES) as *i64 161 var o: i64 = 0 162 var c: i64 = 0 163 var i: i64 = 0 164 while i < n { 165 let e: i64 = cg_eol(conf, n, i) 166 if e > i { if conf[i] != (CG_CH_HASH as u8) { 167 let nf: i64 = cg_row_fields(conf, i, e, fa, fb) 168 if nf == 3 { if cg_sl_eq(conf, fa[0], fb[0], CG_ROW_DOMAIN) == 1 { 169 let dl: i64 = fb[1] - fa[1] 170 if o + dl + 1 < cap { o = cg_catn(out, o, ((conf as i64) + fa[1]) as *u8, dl); out[o] = 0 as u8; o = o + 1; c = c + 1 } 171 } } 172 } } 173 i = e + 1 174 } 175 count_out[0] = c 176 return o 177} 178 179// ---- the request: path from the request line, one header value, the domain under /compare ---- 180// "METHOD <target> HTTP/x" -> the target up to but excluding any query string. Returns its length, 0 if none. 181func cg_req_path(req: *u8, n: i64, out: *u8, cap: i64) -> i64 { 182 var sp1: i64 = 0 - 1 183 var i: i64 = 0 184 while i < n { if sp1 < 0 { if req[i] == (CG_CH_SPACE as u8) { sp1 = i } } i = i + 1 } 185 if sp1 < 0 { return 0 } 186 var o: i64 = 0 187 var k: i64 = sp1 + 1 188 var go: i64 = 1 189 while go == 1 { 190 if k >= n { go = 0 } else { 191 let c: i64 = req[k] as i64 192 if c == CG_CH_SPACE { go = 0 } else { if c == CG_CH_QMARK { go = 0 } else { if c == CG_CH_NL { go = 0 } else { if c == CG_CH_CR { go = 0 } else { 193 if o < cap - 1 { out[o] = req[k]; o = o + 1 } 194 k = k + 1 195 } } } } 196 } 197 } 198 out[o] = 0 as u8 199 return o 200} 201// header value by name (name includes the colon), line-anchored; tolerant of CRLF and LF. 0 if absent. 202func cg_req_header(req: *u8, n: i64, name: *u8, out: *u8, cap: i64) -> i64 { 203 let nl: i64 = cg_slen(name) 204 var ls: i64 = 0 205 while ls < n { 206 let le: i64 = cg_eol(req, n, ls) 207 if le - ls >= nl { 208 var m: i64 = 1 209 var c: i64 = 0 210 while c < nl { if req[ls + c] != name[c] { m = 0; c = nl } else { c = c + 1 } } 211 if m == 1 { 212 var vs: i64 = ls + nl 213 while vs < le { if req[vs] == (CG_CH_SPACE as u8) { vs = vs + 1 } else { break } } 214 var ve: i64 = le 215 if ve > vs { if req[ve - 1] == (CG_CH_CR as u8) { ve = ve - 1 } } 216 var o: i64 = 0 217 var k: i64 = vs 218 while k < ve { if o < cap - 1 { out[o] = req[k]; o = o + 1 } k = k + 1 } 219 out[o] = 0 as u8 220 return o 221 } 222 } 223 ls = le + 1 224 } 225 out[0] = 0 as u8 226 return 0 227} 228 229// Boundary defence (rule 12): a docroot-relative path may not climb, may not be absolute, and may not carry 230// an empty segment. Returns 1 when safe. 231func cg_path_safe(p: *u8, n: i64) -> i64 { 232 if n <= 0 { return 0 } 233 if p[0] == (CG_CH_SLASH as u8) { return 0 } 234 var i: i64 = 0 235 while i + 1 < n { if p[i] == (CG_CH_DOT as u8) { if p[i + 1] == (CG_CH_DOT as u8) { return 0 } } i = i + 1 } 236 i = 0 237 while i + 1 < n { if p[i] == (CG_CH_SLASH as u8) { if p[i + 1] == (CG_CH_SLASH as u8) { return 0 } } i = i + 1 } 238 return 1 239} 240 241// Split a request path under the prefix: "/compare" or "/compare/" -> is_index=1, dom empty, rest empty. 242// "/compare/<dom>" -> dom, rest empty. "/compare/<dom>/<rest>" -> dom + rest. A path that does not start 243// with the prefix returns -1 (this daemon is only ever reached through the /compare route, so that is a 244// misroute and it is refused rather than guessed). Returns is_index (0/1) or -1. 245func cg_split(path: *u8, n: i64, dom: *u8, dcap: i64, rest: *u8, rcap: i64) -> i64 { 246 let pl: i64 = cg_slen(CG_PREFIX) 247 dom[0] = 0 as u8 248 rest[0] = 0 as u8 249 if n < pl { return 0 - 1 } 250 var i: i64 = 0 251 while i < pl { if path[i] != CG_PREFIX[i] { return 0 - 1 } i = i + 1 } 252 if n == pl { return 1 } 253 if path[pl] != (CG_CH_SLASH as u8) { return 0 - 1 } 254 if n == pl + 1 { return 1 } 255 var p: i64 = pl + 1 256 var o: i64 = 0 257 while p < n { if path[p] == (CG_CH_SLASH as u8) { break } if o < dcap - 1 { dom[o] = path[p]; o = o + 1 } p = p + 1 } 258 dom[o] = 0 as u8 259 var r: i64 = 0 260 if p < n { 261 p = p + 1 262 while p < n { if r < rcap - 1 { rest[r] = path[p]; r = r + 1 } p = p + 1 } 263 } 264 rest[r] = 0 as u8 265 return 0 266} 267 268// THE DECISION, pure and total. exists = the docroot holds the domain (the daemon stats it). 269// NOT-EVEN-LISTED IS TOTAL: a board the viewer may not see answers NOTFOUND -- the SAME answer a board that 270// does not exist gives -- for EVERY viewer below its level, not only for anonymous ones. That is the whole 271// point of "provide a client a /compare": a client handed a subset must not be able to discover that boards 272// exist above their tier, and a DENY (403) would leak exactly that existence. So below the bar is always 273// NOTFOUND. CG_D_DENY stays in the vocabulary for a caller that deliberately wants a visible 403, but 274// cg_decide never returns it -- the no-leak property is not left to the daemon to remember. 275func cg_decide(level: i64, required: i64, exists: i64) -> i64 { 276 if exists == 0 { return CG_D_NOTFOUND } 277 if level >= required { return CG_D_SERVE } 278 return CG_D_NOTFOUND 279} 280func cg_decision_name(d: i64) -> *u8 { 281 if d == CG_D_SERVE { return "SERVE" as *u8 } 282 if d == CG_D_DENY { return "DENY-LEVEL" as *u8 } 283 if d == CG_D_NOTFOUND { return "NOTFOUND" as *u8 } 284 if d == CG_D_BADPATH { return "REFUSED-BAD-PATH" as *u8 } 285 return "UNCLASSIFIED" as *u8 286} 287 288// "title":"..." out of a JSON buffer (the domain api.json), backslash-escapes copied through, bounded. 289func cg_json_str(buf: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 { 290 let kl: i64 = cg_slen(key) 291 var i: i64 = 0 292 while i + kl + 3 < n { 293 var m: i64 = 1 294 var c: i64 = 0 295 if buf[i] != (CG_CH_QUOTE as u8) { m = 0 } 296 while m == 1 { if c < kl { if buf[i + 1 + c] != key[c] { m = 0 } c = c + 1 } else { break } } 297 if m == 1 { if buf[i + 1 + kl] != (CG_CH_QUOTE as u8) { m = 0 } } 298 if m == 1 { if buf[i + 2 + kl] != (CG_CH_COLON as u8) { m = 0 } } 299 if m == 1 { if buf[i + 3 + kl] != (CG_CH_QUOTE as u8) { m = 0 } } 300 if m == 1 { 301 var p: i64 = i + 4 + kl 302 var o: i64 = 0 303 var go: i64 = 1 304 while go == 1 { 305 if p >= n { go = 0 } else { 306 let ch: i64 = buf[p] as i64 307 if ch == CG_CH_QUOTE { go = 0 } else { 308 if ch == CG_CH_BSLASH { if p + 1 < n { if o < cap - 1 { out[o] = buf[p + 1]; o = o + 1 } p = p + 2 } else { p = n } } 309 else { if o < cap - 1 { out[o] = buf[p]; o = o + 1 } p = p + 1 } 310 } 311 } 312 } 313 out[o] = 0 as u8 314 return o 315 } 316 i = i + 1 317 } 318 out[0] = 0 as u8 319 return 0 320} 321 322// ---- INDEX: one row per VISIBLE domain, and nothing else. The daemon supplies the candidate list (the 323// docroot's directories, NUL-separated) and a title per domain; this emits only what `level` may see. 324// The bytes for a hidden board are never written -- not-even-listed is a property of the output, and the 325// gate measures it by searching the bytes. 326func cg_index_open(out: *u8, level: i64, with_upgrade: i64) -> i64 { 327 var o: i64 = 0 328 o = cg_cat(out, o, "<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'><title>Nishi Compare</title>" as *u8) 329 // EC47 (2026-09-16): the served root page advertises the daily feed, as the regen hub does in the docroot file this page replaces 330 o = cg_cat(out, o, CG_FEED_LINK_TAG) 331 o = cg_cat(out, o, "<style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;max-width:900px;margin:0 auto;padding:24px;line-height:1.5}h1{font-size:1.6rem}ul{list-style:none;padding:0}li{padding:10px 0;border-bottom:1px solid rgb(220,220,220)}a{text-decoration:none}small{color:rgb(110,110,110);display:block}.lv{font-size:.8rem;color:rgb(110,110,110)}</style></head><body>" as *u8) 332 if with_upgrade == 1 { 333 // NO-COOKIE UPGRADE SHIM (the hub's OLGD_SPA pattern): a plain navigation carries no header, so a 334 // signed-in browser re-fetches the same path WITH its X-Nishi-Session and swaps in the leveled page. 335 o = cg_cat(out, o, "<script>(function(){var t=sessionStorage.getItem('nsess');if(!t){return}fetch(location.pathname,{headers:{'X-Nishi-Session':t}}).then(function(r){return r.ok?r.text():null}).then(function(x){if(x){document.open();document.write(x);document.close()}})})();</script>" as *u8) 336 } 337 // <main> LANDMARK (2026-08-27): nx_page_verify a11y-lite flagged the generated index as the ONE gateway 338 // page without a <main> landmark (every emitted board page has it). Opened here, closed in cg_index_close. 339 o = cg_cat(out, o, "<main><h1>Nishi Compare</h1><p class='lv'>viewing as " as *u8) 340 o = cg_cat(out, o, cg_level_name(level)) 341 o = cg_cat(out, o, " &middot; every board is measured against source at emit time &middot; <a href='/compare/feed.xml'>daily positions feed (RSS)</a></p><ul>" as *u8) 342 return o 343} 344func cg_index_row(out: *u8, o: i64, dom: *u8, title: *u8) -> i64 { 345 var p: i64 = o 346 p = cg_cat(out, p, "<li><a href='/compare/" as *u8) 347 p = cg_cat(out, p, dom) 348 p = cg_cat(out, p, "'>" as *u8) 349 if title[0] != (0 as u8) { p = cg_cat(out, p, title) } else { p = cg_cat(out, p, dom) } 350 p = cg_cat(out, p, "</a><small>/compare/" as *u8) 351 p = cg_cat(out, p, dom) 352 p = cg_cat(out, p, "</small></li>" as *u8) 353 return p 354} 355func cg_index_close(out: *u8, o: i64, shown: i64) -> i64 { 356 var p: i64 = o 357 p = cg_cat(out, p, "</ul><p class='lv'>" as *u8) 358 p = cg_catnum(out, p, shown) 359 p = cg_cat(out, p, " boards at this level &middot; generated by nx_compare_gw</p></main></body></html>" as *u8) 360 out[p] = 0 as u8 361 return p 362} 363 364// the filtered hub api.json: only the visible domains, in the same top-level shape consumers expect 365func cg_api_open(out: *u8, now: i64) -> i64 { 366 var o: i64 = cg_cat(out, 0, "{\"v\":1,\"api\":\"nishi-compare\",\"resource\":\"index\",\"generated_unix\":" as *u8) 367 o = cg_catnum(out, o, now) 368 o = cg_cat(out, o, ",\"gated\":1,\"comparisons\":[" as *u8) 369 return o 370} 371func cg_api_row(out: *u8, o: i64, first: i64, dom: *u8, title: *u8) -> i64 { 372 var p: i64 = o 373 if first == 0 { out[p] = 44 as u8; p = p + 1 } 374 p = cg_cat(out, p, "{\"domain\":\"" as *u8) 375 p = cg_cat(out, p, dom) 376 p = cg_cat(out, p, "\",\"title\":\"" as *u8) 377 // the title came out of a JSON string already; a quote inside it is re-escaped so the row stays valid 378 var i: i64 = 0 379 while title[i] != (0 as u8) { 380 if title[i] == (CG_CH_QUOTE as u8) { out[p] = CG_CH_BSLASH as u8; p = p + 1 } 381 if title[i] == (CG_CH_BSLASH as u8) { out[p] = CG_CH_BSLASH as u8; p = p + 1 } 382 out[p] = title[i]; p = p + 1 383 i = i + 1 384 } 385 p = cg_cat(out, p, "\",\"page\":\"/compare/" as *u8) 386 p = cg_cat(out, p, dom) 387 p = cg_cat(out, p, "\",\"data\":\"/compare/" as *u8) 388 p = cg_cat(out, p, dom) 389 p = cg_cat(out, p, "/api.json\"}" as *u8) 390 return p 391} 392func cg_api_close(out: *u8, o: i64, shown: i64) -> i64 { 393 var p: i64 = cg_cat(out, o, "],\"count\":" as *u8) 394 p = cg_catnum(out, p, shown) 395 p = cg_cat(out, p, "}\n" as *u8) 396 out[p] = 0 as u8 397 return p 398} 399 400// content type by extension -- the file kinds the compare docroot actually holds 401func cg_ctype(file: *u8) -> *u8 { 402 let n: i64 = cg_slen(file) 403 var dot: i64 = 0 - 1 404 var i: i64 = 0 405 while i < n { if file[i] == (CG_CH_DOT as u8) { dot = i } i = i + 1 } 406 if dot < 0 { return "application/octet-stream" as *u8 } 407 let ext: *u8 = ((file as i64) + dot) as *u8 408 if cg_streq(ext, ".html" as *u8) == 1 { return "text/html; charset=utf-8" as *u8 } 409 if cg_streq(ext, ".json" as *u8) == 1 { return "application/json" as *u8 } 410 if cg_streq(ext, ".css" as *u8) == 1 { return "text/css" as *u8 } 411 if cg_streq(ext, ".js" as *u8) == 1 { return "application/javascript" as *u8 } 412 if cg_streq(ext, ".png" as *u8) == 1 { return "image/png" as *u8 } 413 if cg_streq(ext, ".svg" as *u8) == 1 { return "image/svg+xml" as *u8 } 414 if cg_streq(ext, ".pdf" as *u8) == 1 { return "application/pdf" as *u8 } 415 if cg_streq(ext, ".md" as *u8) == 1 { return "text/markdown; charset=utf-8" as *u8 } 416 if cg_streq(ext, ".txt" as *u8) == 1 { return "text/plain; charset=utf-8" as *u8 } 417 if cg_streq(ext, ".xml" as *u8) == 1 { return "application/xml" as *u8 } 418 return "application/octet-stream" as *u8 419} 420 421// ---- EC47 (2026-09-16): the /compare feed, projected per level ---- 422// /compare/feed.xml is ONE file carrying an item per board per day, so the gateway projects it the way it projects the 423// index: an <item> whose <link> names a board the level may not open is ABSENT from the bytes (never hidden), and an 424// item this projector cannot attribute to a board is absent at EVERY level (an unattributable row is served to nobody). 425// The board is the segment after "/compare/" in the item's <link>. Returns the projected length; -1 when the source is 426// not a feed it understands (an <item> without its close) or the projection would not fit -- the daemon then answers 427// 503, never a half feed. kept/dropped are written so the daemon can announce the projection it served. 428const CG_FEED_ITEM_OPEN: *u8 = "<item>" as *u8 429const CG_FEED_ITEM_CLOSE: *u8 = "</item>" as *u8 430const CG_FEED_LINK_OPEN: *u8 = "<link>" as *u8 431const CG_FEED_BOARD_MARK: *u8 = "/compare/" as *u8 432const CG_CH_LT: i64 = 60 433// bounded substring search: the offset of needle in buf[from..n), or -1 434func cg_find(buf: *u8, n: i64, from: i64, needle: *u8) -> i64 { 435 let m: i64 = cg_slen(needle) 436 if m == 0 { return from } 437 var i: i64 = from 438 while i + m <= n { 439 var j: i64 = 0 440 var ok: i64 = 1 441 while j < m { if buf[i + j] != needle[j] { ok = 0; j = m } else { j = j + 1 } } 442 if ok == 1 { return i } 443 i = i + 1 444 } 445 return 0 - 1 446} 447// the board an item's link names: the bytes after "/compare/" up to the next '/' or '<', NUL-terminated into dom; 448// returns the board length, 0 when the item carries no attributable link 449func cg_feed_item_board(src: *u8, ib: i64, ie: i64, dom: *u8, dcap: i64) -> i64 { 450 let l: i64 = cg_find(src, ie, ib, CG_FEED_LINK_OPEN) 451 if l < 0 { return 0 } 452 let b: i64 = cg_find(src, ie, l, CG_FEED_BOARD_MARK) 453 if b < 0 { return 0 } 454 var p: i64 = b + cg_slen(CG_FEED_BOARD_MARK) 455 var o: i64 = 0 456 while p < ie { 457 let c: i64 = src[p] as i64 458 if c == CG_CH_SLASH { p = ie } else { if c == CG_CH_LT { p = ie } else { 459 if o + 1 >= dcap { return 0 } 460 dom[o] = c as u8; o = o + 1; p = p + 1 461 } } 462 } 463 dom[o] = 0 as u8 464 return o 465} 466func cg_feed_project(src: *u8, n: i64, level: i64, conf: *u8, cn: i64, out: *u8, cap: i64, dom: *u8, dcap: i64, kept: *i64, dropped: *i64) -> i64 { 467 kept[0] = 0 468 dropped[0] = 0 469 var p: i64 = cg_find(src, n, 0, CG_FEED_ITEM_OPEN) 470 if p < 0 { 471 // no items at all: the feed is its channel metadata and is served whole 472 if n > cap { return 0 - 1 } 473 var z: i64 = 0 474 while z < n { out[z] = src[z]; z = z + 1 } 475 return n 476 } 477 // the head: channel metadata before the first item 478 if p > cap { return 0 - 1 } 479 var o: i64 = 0 480 while o < p { out[o] = src[o]; o = o + 1 } 481 var cursor: i64 = p 482 while p >= 0 { 483 let c: i64 = cg_find(src, n, p, CG_FEED_ITEM_CLOSE) 484 if c < 0 { return 0 - 1 } 485 var e: i64 = c + cg_slen(CG_FEED_ITEM_CLOSE) 486 if e < n { if src[e] == (CG_CH_NL as u8) { e = e + 1 } } 487 let dl: i64 = cg_feed_item_board(src, p, e, dom, dcap) 488 var serve: i64 = 0 489 if dl > 0 { if cg_decide(level, cg_required(conf, cn, dom), 1) == CG_D_SERVE { serve = 1 } } 490 if serve == 1 { 491 if o + (e - p) > cap { return 0 - 1 } 492 var k: i64 = p 493 while k < e { out[o] = src[k]; o = o + 1; k = k + 1 } 494 kept[0] = kept[0] + 1 495 } else { dropped[0] = dropped[0] + 1 } 496 cursor = e 497 p = cg_find(src, n, e, CG_FEED_ITEM_OPEN) 498 } 499 // the tail: everything after the last item (the channel and rss closes) 500 if o + (n - cursor) > cap { return 0 - 1 } 501 var t: i64 = cursor 502 while t < n { out[o] = src[t]; o = o + 1; t = t + 1 } 503 return o 504} 505 506// ---- THE FILE SERVED UNDER A BOARD (2026-09-16) ---- 507// THE DEFECT, MEASURED THROUGH THE EDGE: every board's frontier radar lives at <board>/frontier/index.html and the hub 508// links it as <board>/frontier, but the daemon took the rest of the path as a FILE name and read the DIRECTORY as a 509// file: the read returned no bytes and the gateway answered 200 OK, application/octet-stream, Content-Length 0 -- 510// a false success on /compare/lang/frontier, /compare/stem/frontier and every other radar, while the page itself 511// served fine at .../frontier/index.html. The decision now lives here, pure, so the gate reaches it: a rest that 512// names a directory is served as its index.html, and an empty rest is the board's own index.html. 513// cg_board_file writes the served path (relative to the board) into out and returns its length; out must hold 514// rest plus CG_INDEX_SUFFIX_BYTES. 515const CG_INDEX_FILE: *u8 = "index.html" 516const CG_INDEX_SUFFIX_BYTES: i64 = 16 517const CG_CH_SLASH: i64 = 47 518func cg_board_file(rest: *u8, rest_is_dir: i64, out: *u8) -> i64 { 519 var o: i64 = 0 520 var n: i64 = 0 521 while rest[n] != (0 as u8) { n = n + 1 } 522 if n == 0 { o = cg_cat(out, 0, CG_INDEX_FILE); out[o] = 0 as u8; return o } 523 while o < n { out[o] = rest[o]; o = o + 1 } 524 if rest_is_dir == 1 { 525 if out[o - 1] != (CG_CH_SLASH as u8) { out[o] = CG_CH_SLASH as u8; o = o + 1 } 526 o = cg_cat(out, o, CG_INDEX_FILE) 527 } 528 out[o] = 0 as u8 529 return o 530} 531// A READ THAT FILLED THE BODY RESERVE IS NOT A PAGE. The reader stops at the reserve, so a page larger than it would 532// have been served truncated under 200 OK; the verdict refuses that instead. CG_BODY_SERVE . CG_BODY_TOO_LARGE 533// (n reached the reserve) . CG_BODY_MISSING (unreadable). 534const CG_BODY_SERVE: i64 = 1 535const CG_BODY_TOO_LARGE: i64 = 2 536const CG_BODY_MISSING: i64 = 0 - 1 537func cg_body_verdict(n: i64, cap: i64) -> i64 { 538 if n < 0 { return CG_BODY_MISSING } 539 if n >= cap { return CG_BODY_TOO_LARGE } 540 return CG_BODY_SERVE 541}