code wiki / (root) / nx_gate_discover.nx

nx_gate_discover.nx source

↩ module page · 267 lines · 11314 B

1// nx_gate_discover.nx -- SOVEREIGN gate discovery (NishiLang, no .sh, no grep). 2// 3// The gate runner (nx_gate_runner.nx) hardcodes N=19 names[]/gates[] entries. 4// That list ROTS: a gate added under _hdl_build/ that nobody hand-enrolls is 5// silently never run. This organ is the DYNAMIC FEED for the runner: it scans 6// _hdl_build/ for *_test.nx gate files and returns the discovered count + the 7// list of absolute paths. The runner (or any consumer) iterates the result 8// instead of a frozen array -- self-REGISTRATION, not hand-maintenance. 9// 10// SINGLE RESPONSIBILITY: this organ DISCOVERS (enumerate + filter + path-join + 11// fail-loud-on-implausible-count). It does NOT run gates. A consumer feeds the 12// returned NxGateList to gr_gate(). The discovery is ADDITIVE: it does not edit 13// the runner; the runner opts in later. 14// 15// VERIFY-BEFORE-ENROLL (cardinal: a discovered gate must be a real, compilable 16// gate): the FAIL-LOUD floor (nx_gate_discover_floor_ok) refuses an implausibly 17// low count -- if the scan returns fewer gates than the known floor, the 18// directory was mis-pathed / the getdents call failed / the filesystem is not 19// where we think it is. A 0-or-tiny count is NEVER a healthy discovery; it is a 20// silent-skip waiting to happen, so we surface it loudly. The COMPILABILITY half 21// of verify-before-enroll is the gate-runner's gr_gate() the consumer calls on 22// each discovered path (nx_gate_runner.nx) -- this organ proves the candidate 23// SET is plausible; gr_gate proves each candidate COMPILES + PROVES. 24// 25// REUSES (concept-not-copy, DRY): 26// nx_dir.nx -- nx_dir_list (getdents enumeration into rows+arena), 27// nx_dir_name_ends_with (suffix filter), nx_dir_row_at, 28// NxDirRow/NxDirResult, NX_DIR_OK/NX_DIR_EMPTY/... 29// (transitively) -- syscalls.nx -> nx_syscalls.nx (sys_write/mmap/exit), 30// nx_fcntl.nx, nx_dirent.nx 31// 32// license_tier: ORIGINAL 33 34import "nx_dir.nx" 35 36// ===== Discovery verdicts ======================================== 37 38const NX_GD_OK: i64 = 0 // discovered a plausible, non-empty gate set 39const NX_GD_EMPTY: i64 = 1 // directory readable but no *_test.nx gates 40const NX_GD_IMPLAUSIBLE: i64 = 2 // count below the fail-loud floor (mis-path / FS fault) 41const NX_GD_SCAN_FAILED: i64 = 3 // underlying nx_dir_list failed (open/getdents) 42const NX_GD_TRUNCATED: i64 = 4 // more gates than out_capacity (raise the cap) 43const NX_GD_BAD_ARGS: i64 = 5 44 45// The plausibility floor. The hand-enrolled runner already lists 16 distinct 46// _hdl_build/ gates (and the directory holds at least that many *_test.nx 47// files). A scan that finds fewer than this many gates is implausible: the 48// directory was not where we expected, or getdents short-read. Data-driven, one 49// named constant -- no magic number buried in a branch (cardinal #11). 50const NX_GD_PLAUSIBLE_FLOOR: i64 = 10 51 52// Sizing for the scan. _hdl_build/ holds well under this many entries; the 53// runner's universe is ~20 gates. Bounded, no unbounded growth. 54const NX_GD_MAX_GATES: i64 = 256 55const NX_GD_NAME_ARENA: i64 = 32768 // 256 names * ~128 bytes headroom 56const NX_GD_PATH_ARENA: i64 = 131072 // 256 abs-paths * ~512 bytes headroom 57 58// ===== NxGateList -- the discovered feed ========================= 59// 60// paths[i] (0 <= i < count) is a NUL-terminated absolute path to a discovered 61// *_test.nx gate, ready to hand to gr_gate(). names[i] is the bare filename 62// (also NUL-terminated). Both point into arenas held by the list. 63 64struct NxGateList { 65 verdict: i64, // NX_GD_* 66 count: i64, // number of discovered gates 67 paths: *i64, // [count] of *u8 (absolute paths) 68 names: *i64, // [count] of *u8 (bare filenames) 69 name_arena: *u8, // backing store for filenames 70 path_arena: *u8, // backing store for absolute paths 71 path_used: i64, 72 dir_path: *u8, // the directory scanned (for forensics) 73} 74 75const NX_GATE_LIST_BYTES: i64 = 72 76 77// ===== small string helpers (local, no copy of foreign helpers) == 78 79func gd_strlen(s: *u8) -> i64 { 80 var n: i64 = 0 81 while s[n] != 0 as u8 { n = n + 1 } 82 return n 83} 84 85// Append src[0..len) + a NUL into arena at off; return new off, or -1 on 86// overflow. Mirrors nx_dir_arena_copy_name's contract but for path-joins. 87func gd_arena_append(src: *u8, len: i64, arena: *u8, cap: i64, off: i64) -> i64 { 88 if off + len + 1 > cap { return 0 - 1 } 89 var i: i64 = 0 90 while i < len { 91 arena[off + i] = src[i] 92 i = i + 1 93 } 94 arena[off + len] = 0 95 return off + len + 1 96} 97 98// Byte-equality of two NUL-terminated strings (1 == equal). 99func gd_streq(a: *u8, b: *u8) -> i64 { 100 var i: i64 = 0 101 while a[i] != 0 as u8 { 102 if a[i] != b[i] { return 0 } 103 i = i + 1 104 } 105 if b[i] != 0 as u8 { return 0 } 106 return 1 107} 108 109// ===== suffix constant =========================================== 110// We discover NishiLang gate tests: files named "*_test.nx". 111 112func gd_suffix() -> *u8 { return "_test.nx" as *u8 } 113 114// ===== nx_gate_discover -- the discovery transaction ============= 115// 116// Scans dir_path for *_test.nx, joining each onto dir_path to produce an 117// absolute path. Fills `out`. Returns out.verdict. include floor=1 enforces the 118// fail-loud plausibility floor; floor=0 reports EMPTY/OK without the floor (used 119// only by the negative-control branch of the test). 120func nx_gate_discover(dir_path: *u8, out: *NxGateList, enforce_floor: i64) -> i64 { 121 if out == 0 as *NxGateList { return NX_GD_BAD_ARGS } 122 out.verdict = NX_GD_BAD_ARGS 123 out.count = 0 124 out.path_used = 0 125 out.dir_path = dir_path 126 if dir_path == 0 as *u8 { return NX_GD_BAD_ARGS } 127 128 // arenas + index tables. 129 let rows: *NxDirRow = sys_mmap(NX_GD_MAX_GATES * NX_DIR_ROW_BYTES) as *NxDirRow 130 let dname_arena: *u8 = sys_mmap(NX_GD_NAME_ARENA) 131 let res: *NxDirResult = sys_mmap(NX_DIR_RESULT_BYTES) as *NxDirResult 132 133 let name_arena: *u8 = sys_mmap(NX_GD_NAME_ARENA) 134 let path_arena: *u8 = sys_mmap(NX_GD_PATH_ARENA) 135 let names: *i64 = sys_mmap(NX_GD_MAX_GATES * 8) as *i64 136 let paths: *i64 = sys_mmap(NX_GD_MAX_GATES * 8) as *i64 137 out.name_arena = name_arena 138 out.path_arena = path_arena 139 out.names = names 140 out.paths = paths 141 142 // 1. ENUMERATE (reuse nx_dir_list -- getdents under the hood). 143 let dv: i64 = nx_dir_list(dir_path, rows, NX_GD_MAX_GATES, 144 dname_arena, NX_GD_NAME_ARENA, 0, res) 145 if dv == NX_DIR_OPEN_FAILED { out.verdict = NX_GD_SCAN_FAILED; return NX_GD_SCAN_FAILED } 146 if dv == NX_DIR_GETDENTS_FAILED { out.verdict = NX_GD_SCAN_FAILED; return NX_GD_SCAN_FAILED } 147 if dv == NX_DIR_BAD_ARGS { out.verdict = NX_GD_SCAN_FAILED; return NX_GD_SCAN_FAILED } 148 // NX_DIR_OK / NX_DIR_EMPTY / NX_DIR_TRUNCATED all leave usable rows. 149 150 // 2. FILTER (*_test.nx) + 3. PATH-JOIN (dir_path + "/" + name). 151 let suffix: *u8 = gd_suffix() 152 let slen: i64 = gd_strlen(suffix) 153 let dlen: i64 = gd_strlen(dir_path) 154 var name_off: i64 = 0 155 var path_off: i64 = 0 156 var n_filled_total: i64 = res.n_filled 157 var count: i64 = 0 158 var truncated: i64 = 0 159 160 var i: i64 = 0 161 while i < n_filled_total { 162 let row: *NxDirRow = nx_dir_row_at(rows, i) 163 // regular file only (skip stray subdirs), ending in "_test.nx". 164 var keep: i64 = 0 165 if nx_dir_row_is_regular_file(row) == 1 { keep = 1 } 166 if keep == 1 { 167 if nx_dir_name_ends_with(row.name_ptr, row.name_len, suffix, slen) == 0 { keep = 0 } 168 } 169 if keep == 1 { 170 if count >= NX_GD_MAX_GATES { 171 truncated = 1 172 i = n_filled_total // stop scanning 173 } 174 } 175 if keep == 1 { 176 if truncated == 0 { 177 // copy filename 178 let nstart: i64 = name_off 179 let no2: i64 = gd_arena_append(row.name_ptr, row.name_len, 180 name_arena, NX_GD_NAME_ARENA, name_off) 181 if no2 < 0 { truncated = 1 } 182 if truncated == 0 { 183 name_off = no2 184 names[count] = (name_arena as i64 + nstart) 185 // build absolute path: dir_path + '/' + name 186 let pstart: i64 = path_off 187 var po: i64 = gd_arena_append(dir_path, dlen, path_arena, NX_GD_PATH_ARENA, path_off) 188 if po < 0 { truncated = 1 } 189 if truncated == 0 { 190 // overwrite the NUL just written with '/', then append name+NUL 191 po = po - 1 192 path_arena[po] = 0x2F // '/' 193 po = po + 1 194 let po2: i64 = gd_arena_append(row.name_ptr, row.name_len, 195 path_arena, NX_GD_PATH_ARENA, po) 196 if po2 < 0 { truncated = 1 } 197 if truncated == 0 { 198 path_off = po2 199 paths[count] = (path_arena as i64 + pstart) 200 count = count + 1 201 } 202 } 203 } 204 } 205 } 206 i = i + 1 207 } 208 209 out.count = count 210 out.path_used = path_off 211 212 if truncated == 1 { out.verdict = NX_GD_TRUNCATED; return NX_GD_TRUNCATED } 213 if count == 0 { out.verdict = NX_GD_EMPTY; return NX_GD_EMPTY } 214 215 // 4. FAIL-LOUD plausibility floor (verify-before-enroll: a healthy scan 216 // finds at least the known floor of gates; fewer = mis-path / FS fault). 217 if enforce_floor == 1 { 218 if count < NX_GD_PLAUSIBLE_FLOOR { out.verdict = NX_GD_IMPLAUSIBLE; return NX_GD_IMPLAUSIBLE } 219 } 220 221 out.verdict = NX_GD_OK 222 return NX_GD_OK 223} 224 225// ===== convenience: is a named gate in the discovered list? ====== 226// Returns the index of the gate whose bare filename == want, else -1. 227func nx_gate_discover_find(out: *NxGateList, want: *u8) -> i64 { 228 if out == 0 as *NxGateList { return 0 - 1 } 229 var i: i64 = 0 230 while i < out.count { 231 let nm: *u8 = out.names[i] as *u8 232 if gd_streq(nm, want) == 1 { return i } 233 i = i + 1 234 } 235 return 0 - 1 236} 237 238// ===== convenience: print the discovered feed ==================== 239func nx_gate_discover_print(out: *NxGateList) -> i64 { 240 let pre: *u8 = "gate-discover: count=" 241 sys_write(1, pre, gd_strlen(pre)) 242 // small decimal print 243 let n: i64 = out.count 244 if n == 0 { 245 sys_write(1, "0" as *u8, 1) 246 } 247 if n != 0 { 248 let d: *u8 = sys_mmap(24) 249 var m: i64 = n 250 var k: i64 = 0 251 while m > 0 { d[k] = (0x30 + (m % 10)) as u8; m = m / 10; k = k + 1 } 252 let o: *u8 = sys_mmap(24) 253 var j: i64 = 0 254 while j < k { o[j] = d[k - 1 - j]; j = j + 1 } 255 sys_write(1, o, k) 256 } 257 sys_write(1, "\n" as *u8, 1) 258 var i: i64 = 0 259 while i < out.count { 260 let nm: *u8 = out.names[i] as *u8 261 sys_write(1, " " as *u8, 2) 262 sys_write(1, nm, gd_strlen(nm)) 263 sys_write(1, "\n" as *u8, 1) 264 i = i + 1 265 } 266 return 0 267}