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}