code wiki / _hdl_build / nx_maturity_registry.nx

nx_maturity_registry.nx source

↩ module page · 198 lines · 10874 B

1// nx_maturity_registry.nx -- the nishifamily HUB maturity-flag registry + router decision. 2// 2026-07-29 seq1232 (P2 safe-subset): per-call scratch -> lazy statics (no-munmap page leak class). 3static mr_scr_fs: *i64 4static mr_scr_fe: *i64 5static mr_scr_ztmp: *i64 6static mr_scr_rtmp: *i64 7static mr_scr_pathbuf: *u8 8static mr_scr_titlebuf: *u8 9// 10// THE MISSING RUNG. Each hosted artifact carries TWO orthogonal flags in ONE data-driven registry: 11// maturity = WHERE it is exposed (production | beta | alpha | hub | private) 12// access = WHO may view it (public | viewer | member | operator) 13// A router derives, per request, the artifact's serve ZONE from `maturity` and gates entry by 14// `access` (granted >= required, reusing nx_access_lib's roles). A flag edit = ONE registry append: 15// it promotes/demotes (hub->alpha->beta->production) or pulls back, with NO code redeploy and NO 16// daemon restart -- so changing visibility can NEVER crash hosting (config change, not a rollout). 17// 18// FAIL-CLOSED BY CONSTRUCTION (the security property the old /wiki fall-through bug violated): 19// an artifact has EXACTLY ONE reachable URL = its maturity zone. A gated (hub/private) artifact 20// requested at its public-root URL resolves to TIER_MISMATCH, never served -- there is no second 21// public code-path that can leak it. WHERE is enforced before WHO on every serve. 22// 23// Registry line schema (TAB-separated, 7 fields, ADDITIVE-ONLY history per global rule #13): 24// artifact <TAB> maturity <TAB> access <TAB> path <TAB> title <TAB> load_date <TAB> is_current 25// Resolution is "most-specific path, then latest load_date wins"; latest is_current=0 = RETIRED 26// (a tombstone demote, appended -- prior rows are never edited or deleted, so history is sacred). 27// 28// Sovereign: composes the proven floors only -- fa_appendz (atomic framed-append store), 29// slk_* (data-driven line/field/path parsing), ag_resolve_level (roles). No new I/O primitive, 30// no third-party anything. license_tier: ORIGINAL 31import "nx_framed_append.nx" 32import "nx_site_lock_lib.nx" 33import "nx_access_lib.nx" 34const K_MAGIC_1024: i64 = 1024 35 36// ---- data-driven classifiers (no magic numbers leak to callers) ---- 37 38// maturity string -> code: production=0 beta=1 alpha=2 hub=3 private=4 ; invalid = -1 39func mr_tier_of(s: *u8, n: i64) -> i64 { 40 if slk_eq(s, n, "production" as *u8, 10) == 1 { return 0 } 41 if slk_eq(s, n, "beta" as *u8, 4) == 1 { return 1 } 42 if slk_eq(s, n, "alpha" as *u8, 5) == 1 { return 2 } 43 if slk_eq(s, n, "hub" as *u8, 3) == 1 { return 3 } 44 if slk_eq(s, n, "private" as *u8, 7) == 1 { return 4 } 45 return 0 - 1 46} 47 48// maturity code -> serve ZONE: ROOT=0 BETA=1 ALPHA=2 HUB=3. hub(3) and private(4) share the HUB 49// zone (both live under /hub); private is distinguished by access=operator, not by URL. 50func mr_zone_of(tier: i64) -> i64 { 51 if tier == 0 { return 0 } 52 if tier == 1 { return 1 } 53 if tier == 2 { return 2 } 54 return 3 55} 56 57// access string -> required level: public=0 viewer=1 member=2 operator=3 ; unknown = 3 (FAIL-CLOSED: 58// a typo'd access flag locks the artifact to operator, never accidentally exposes it). 59func mr_level_of(s: *u8, n: i64) -> i64 { 60 if slk_eq(s, n, "public" as *u8, 6) == 1 { return 0 } 61 if slk_eq(s, n, "viewer" as *u8, 6) == 1 { return 1 } 62 if slk_eq(s, n, "member" as *u8, 6) == 1 { return 2 } 63 if slk_eq(s, n, "operator" as *u8, 8) == 1 { return 3 } 64 return 3 65} 66 67// does s[0..n) start with pref[0..pn) ? 68func mr_starts_with(s: *u8, n: i64, pref: *u8, pn: i64) -> i64 { 69 if n < pn { return 0 } 70 var i: i64 = 0 71 while i < pn { if s[i] != pref[i] { return 0 } i = i + 1 } 72 return 1 73} 74 75// ---- the registry store (additive) ---- 76 77// Append ONE registry row (atomic, collision-safe via the proven fa_appendz floor). A promote/demote 78// is just another mr_put with a higher load_date (and is_current=1 to promote, 0 to retire). 79// Returns fa_appendz's result (bytes>0 ok, <0 error). 80func mr_put(regpath: *u8, artifact: *u8, maturity: *u8, access: *u8, 81 path: *u8, title: *u8, load_date: i64, is_current: i64) -> i64 { 82 let cap: i64 = K_MAGIC_1024 83 let rec: *u8 = sys_mmap(cap + 32) 84 var o: i64 = 0 85 o = fa_cat(rec, o, artifact); rec[o] = 9 as u8; o = o + 1 86 o = fa_cat(rec, o, maturity); rec[o] = 9 as u8; o = o + 1 87 o = fa_cat(rec, o, access); rec[o] = 9 as u8; o = o + 1 88 o = fa_cat(rec, o, path); rec[o] = 9 as u8; o = o + 1 89 o = fa_cat(rec, o, title); rec[o] = 9 as u8; o = o + 1 90 o = fa_catn(rec, o, load_date); rec[o] = 9 as u8; o = o + 1 91 o = fa_catn(rec, o, is_current) 92 rec[o] = 0 as u8 93 return fa_appendz(regpath, rec, cap) 94} 95 96// ---- the resolver: given a path, what is the CURRENT artifact there? ---- 97// Scans rows; among those whose `path` field is a segment-boundary prefix of the request `path`, 98// picks MOST-SPECIFIC (longest registry path) then LATEST load_date. Fills out_zone/out_req and copies 99// the matched base path + title. Returns 1 if a CURRENT artifact matches; 0 if none / latest is retired. 100func mr_resolve(reg: *u8, reglen: i64, path: *u8, pathlen: i64, 101 out_zone: *i64, out_req: *i64, 102 out_path: *u8, out_path_cap: i64, 103 out_title: *u8, out_title_cap: i64) -> i64 { 104 if (mr_scr_fs as i64) == 0 { mr_scr_fs = sys_mmap(8) as *i64 } 105 if (mr_scr_fe as i64) == 0 { mr_scr_fe = sys_mmap(8) as *i64 } 106 let fs: *i64 = mr_scr_fs; let fe: *i64 = mr_scr_fe 107 var best_pref: i64 = 0 - 1 108 var best_load: i64 = 0 - 1 109 var best_zone: i64 = 0 110 var best_req: i64 = 3 111 var best_cur: i64 = 0 112 var best_ps: i64 = 0; var best_pe: i64 = 0 113 var best_ts: i64 = 0; var best_te: i64 = 0 114 var ls: i64 = 0 115 while ls < reglen { 116 let le: i64 = slk_line_end(reg, reglen, ls) 117 if le > ls { if reg[ls] != (35 as u8) { 118 if slk_field(reg, ls, le, 6, fs, fe) == 1 { // require field 6 => well-formed 7-field row 119 slk_field(reg, ls, le, 3, fs, fe) 120 let p3s: i64 = fs[0]; let p3e: i64 = fe[0] 121 let preflen: i64 = p3e - p3s 122 if slk_path_match(path, pathlen, slk_at(reg, p3s), preflen) == 1 { 123 slk_field(reg, ls, le, 5, fs, fe) 124 let ld: i64 = slk_atoi(reg, fs[0], fe[0]) 125 var take: i64 = 0 126 if preflen > best_pref { take = 1 } 127 if preflen == best_pref { if ld > best_load { take = 1 } } 128 if take == 1 { 129 best_pref = preflen; best_load = ld 130 slk_field(reg, ls, le, 1, fs, fe) 131 best_zone = mr_zone_of(mr_tier_of(slk_at(reg, fs[0]), fe[0] - fs[0])) 132 slk_field(reg, ls, le, 2, fs, fe) 133 best_req = mr_level_of(slk_at(reg, fs[0]), fe[0] - fs[0]) 134 slk_field(reg, ls, le, 6, fs, fe) 135 best_cur = slk_atoi(reg, fs[0], fe[0]) 136 best_ps = p3s; best_pe = p3e 137 slk_field(reg, ls, le, 4, fs, fe) 138 best_ts = fs[0]; best_te = fe[0] 139 } 140 } 141 } 142 } } 143 ls = le + 1 144 } 145 if best_pref < 0 { return 0 } // no artifact at this path 146 if best_cur == 0 { return 0 } // latest state is a tombstone (retired) 147 out_zone[0] = best_zone 148 out_req[0] = best_req 149 var o: i64 = 0; let pn: i64 = best_pe - best_ps 150 while o < pn { if o < out_path_cap - 1 { out_path[o] = reg[best_ps + o] } o = o + 1 } 151 if pn < out_path_cap { out_path[pn] = 0 as u8 } else { out_path[out_path_cap - 1] = 0 as u8 } 152 var t: i64 = 0; let tn: i64 = best_te - best_ts 153 while t < tn { if t < out_title_cap - 1 { out_title[t] = reg[best_ts + t] } t = t + 1 } 154 if tn < out_title_cap { out_title[tn] = 0 as u8 } else { out_title[out_title_cap - 1] = 0 as u8 } 155 return 1 156} 157 158// ---- THE ROUTER DECISION (pure: bytes/identity in -> action out; in-process gateable, no socket) ---- 159// Action codes: 1 = SERVE (out_target = doc-root-relative file) 0 = DENY (gated, granted<required) 160// -1 = NOTFOUND (no current artifact) -2 = TIER_MISMATCH (artifact exists, 161// but not in the requested zone) 162func mr_route(reg: *u8, reglen: i64, roles: *u8, rlen: i64, 163 url: *u8, urllen: i64, handle: *u8, hlen: i64, 164 out_target: *u8, out_target_cap: i64, 165 out_req: *i64, out_grant: *i64) -> i64 { 166 // 1. requested ZONE + rest-of-path (the artifact path under that zone) 167 var zone: i64 = 0 168 var rs: i64 = 0 169 var matched: i64 = 0 170 if matched == 0 { if mr_starts_with(url, urllen, "/hub/" as *u8, 5) == 1 { zone = 3; rs = 5; matched = 1 } } 171 if matched == 0 { if mr_starts_with(url, urllen, "/beta/" as *u8, 6) == 1 { zone = 1; rs = 6; matched = 1 } } 172 if matched == 0 { if mr_starts_with(url, urllen, "/alpha/" as *u8, 7) == 1 { zone = 2; rs = 7; matched = 1 } } 173 if matched == 0 { zone = 0; rs = 0; if urllen > 0 { if url[0] == (47 as u8) { rs = 1 } } } 174 let rest: *u8 = ((url as i64) + rs) as *u8 175 let restlen: i64 = urllen - rs 176 // 2. resolve the artifact at `rest` 177 if (mr_scr_ztmp as i64) == 0 { mr_scr_ztmp = sys_mmap(8) as *i64 } 178 if (mr_scr_rtmp as i64) == 0 { mr_scr_rtmp = sys_mmap(8) as *i64 } 179 if (mr_scr_pathbuf as i64) == 0 { mr_scr_pathbuf = sys_mmap(512) } 180 if (mr_scr_titlebuf as i64) == 0 { mr_scr_titlebuf = sys_mmap(256) } 181 let ztmp: *i64 = mr_scr_ztmp; let rtmp: *i64 = mr_scr_rtmp 182 let pathbuf: *u8 = mr_scr_pathbuf; let titlebuf: *u8 = mr_scr_titlebuf 183 let found: i64 = mr_resolve(reg, reglen, rest, restlen, ztmp, rtmp, pathbuf, 512, titlebuf, 256) 184 if found == 0 { return 0 - 1 } // NOTFOUND 185 // 3. WHERE check: the artifact is only reachable in its own zone (fail-closed against leaks) 186 if ztmp[0] != zone { return 0 - 2 } // TIER_MISMATCH 187 // 4. WHO check: granted >= required (reuse the proven roles resolver) 188 let required: i64 = rtmp[0] 189 var granted: i64 = 0 190 if hlen > 0 { granted = ag_resolve_level(roles, rlen, handle, hlen) } 191 out_req[0] = required; out_grant[0] = granted 192 // out_target = the validated rest path (the doc-root-relative file to serve) 193 var k: i64 = 0 194 while k < restlen { if k < out_target_cap - 1 { out_target[k] = rest[k] } k = k + 1 } 195 if restlen < out_target_cap { out_target[restlen] = 0 as u8 } else { out_target[out_target_cap - 1] = 0 as u8 } 196 if granted >= required { return 1 } // SERVE 197 return 0 // DENY 198}