nx_confdup.nx source
↩ module page · 531 lines · 21091 B
1// nx_confdup.nx -- find CONFIG FILES THAT DECLARE THE SAME CONCERN TWICE.
2//
3// WHY THIS EXISTS (measured 2026-07-30, media-vault lane): /api/promote reads
4// `knowledge/organ_kinds.conf` (PLURAL, values hyphenated `one-shot`) while
5// nx_organkind reads `knowledge/status/organ_kind.conf` (SINGULAR, values
6// unhyphenated `oneshot`). I added a row to the singular file, promote kept
7// working, and I concluded the row was the fix -- it was INERT. Undeclared
8// names fall through to a name-substring heuristic, so the wrong file "worked"
9// by luck until a name that failed the heuristic (nx_mvault_walk) became
10// unshippable by any sanctioned route.
11//
12// ★★★LAW: A SILENTLY-INERT CONFIG ROW IS WORSE THAN A MISSING ONE -- it
13// manufactures false confidence. The failure is invisible precisely because
14// the other source of truth keeps answering.
15//
16// This is a RECURRING class in this ecosystem, not a one-off:
17// * organ_kind.conf vs organ_kinds.conf (this session)
18// * nx_fs root=nishihost vs /api/build buildroot/runtime -- TWO ROOTS ONE NAME
19// * the ruler reading the wrong evidence root (rollup saw 181 of 1164 logs)
20// So it gets a DETECTOR rather than another banked warning.
21//
22// WHAT IT FLAGS -- two independent signals, reported separately because they
23// have different remedies:
24// COLLIDE-STEM two config files whose names normalize to the SAME stem
25// (plural/singular, hyphen/underscore, trailing digits).
26// Remedy: one file, or make one provably derived.
27// COLLIDE-BASE the SAME basename living under two different directories.
28// Remedy: name the roots apart, or collapse them.
29//
30// Normalization is deliberately AGGRESSIVE (drop ext, lowercase, '-'->'_',
31// strip trailing digits/underscores, strip ONE trailing plural 's') because a
32// false positive costs a glance while a false negative costs a session.
33//
34// nx_confdup scan [root] (default root: knowledge)
35//
36// Read-only. license_tier: ORIGINAL No hw writes (Rule 26).
37
38import "nx_syscalls.nx"
39const CD_MAGIC_2048: i64 = 2048
40
41const CD_QCAP: i64 = 1048576 // dir-queue arena
42const CD_DIRBUF: i64 = 262144 // getdents buffer
43const CD_ARENA: i64 = 1048576 // path + stem text arena
44const CD_MAXFILES: i64 = 8192 // bounded, and the cap is REPORTED not silent
45const CD_PATH: i64 = 4096
46const CD_STEM: i64 = 256
47const CD_OUT: i64 = 1048576
48const CD_FBUF: i64 = 1048576 // per-file read buffer for the COLLIDE-ROW pass
49const CD_MAXROWS: i64 = 16384 // bounded, and the cap is REPORTED not silent
50const CD_KEY: i64 = 160
51const CD_UNIQPCT: i64 = 90 // a file is "unique-key shaped" when >=90% of its rows carry a distinct key
52
53func cd_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
54
55func cd_cat(dst: *u8, off: i64, s: *u8) -> i64 {
56 var o: i64 = off
57 var i: i64 = 0
58 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
59 return o
60}
61
62func cd_ch(dst: *u8, off: i64, c: i64) -> i64 { dst[off] = c as u8; return off + 1 }
63
64func cd_udec(dst: *u8, off: i64, v: i64) -> i64 {
65 if v == 0 { dst[off] = 48 as u8; return off + 1 }
66 let tmp: *u8 = sys_mmap(32)
67 var m: i64 = v
68 var k: i64 = 0
69 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
70 var o: i64 = off
71 var i: i64 = k - 1
72 while i >= 0 { dst[o] = tmp[i]; o = o + 1; i = i - 1 }
73 sys_munmap(tmp, 32)
74 return o
75}
76
77// "<dir>/<name>" into dst; returns length
78func cd_join(dst: *u8, dir: *u8, name: *u8) -> i64 {
79 var o: i64 = cd_cat(dst, 0, dir)
80 o = cd_ch(dst, o, 47)
81 o = cd_cat(dst, o, name)
82 dst[o] = 0 as u8
83 return o
84}
85
86// last path component of a NUL-terminated path
87func cd_basename(p: *u8) -> *u8 {
88 let n: i64 = cd_len(p)
89 var last: i64 = 0 - 1
90 var i: i64 = 0
91 while i < n {
92 if p[i] == (47 as u8) { last = i }
93 i = i + 1
94 }
95 return ((p as i64) + last + 1) as *u8
96}
97
98// does the name end in ".conf"?
99func cd_is_conf(name: *u8) -> i64 {
100 let n: i64 = cd_len(name)
101 if n < 6 { return 0 }
102 let s: *u8 = ".conf" as *u8
103 var i: i64 = 0
104 while i < 5 {
105 if name[n - 5 + i] != s[i] { return 0 }
106 i = i + 1
107 }
108 return 1
109}
110
111// Normalize a basename to a concern STEM. See the header for why this is
112// aggressive. Returns the stem length.
113func cd_norm(dst: *u8, name: *u8) -> i64 {
114 let n: i64 = cd_len(name)
115 // drop the extension (last '.')
116 var end: i64 = n
117 var i: i64 = n - 1
118 var scanning: i64 = 1
119 while scanning == 1 {
120 if i <= 0 { scanning = 0 } else {
121 if name[i] == (46 as u8) { end = i; scanning = 0 } else { i = i - 1 }
122 }
123 }
124 var o: i64 = 0
125 i = 0
126 while i < end {
127 var c: i64 = name[i]
128 if c >= 65 { if c <= 90 { c = c + 32 } } // lowercase
129 if c == 45 { c = 95 } // '-' -> '_'
130 if o < CD_STEM - 2 { dst[o] = c as u8; o = o + 1 }
131 i = i + 1
132 }
133 // strip trailing digits (foo2 == foo)
134 var go: i64 = 1
135 while go == 1 {
136 go = 0
137 if o > 1 {
138 let c2: i64 = dst[o - 1]
139 if c2 >= 48 { if c2 <= 57 { o = o - 1; go = 1 } }
140 }
141 }
142 // strip trailing underscores
143 go = 1
144 while go == 1 {
145 go = 0
146 if o > 1 { if dst[o - 1] == (95 as u8) { o = o - 1; go = 1 } }
147 }
148 // strip ONE trailing plural 's' (kind/kinds)
149 if o > 2 { if dst[o - 1] == (115 as u8) { o = o - 1 } }
150 dst[o] = 0 as u8
151 return o
152}
153
154func cd_streq(a: *u8, b: *u8) -> i64 {
155 var i: i64 = 0
156 while 1 == 1 {
157 if a[i] != b[i] { return 0 }
158 if a[i] == (0 as u8) { return 1 }
159 i = i + 1
160 }
161 return 0
162}
163
164// ---- COLLIDE-ROW: the SAME KEY declared TWICE INSIDE one config file -------------------------
165// WHY THIS WAS ADDED (measured 2026-08-06). This detector shipped able to see duplicate config
166// FILES and completely blind to duplicate config ROWS -- and the row case is the COMMON one. It
167// bit live: nx_schema_backfill auto-appended a second `nx_prodladder` row to tool_schemas.conf
168// carrying DIFFERENT safety flags (destructive=1 open_world=1) than the hand-written row
169// (read_only=1 destructive=0). Both were true-looking, both were live, and whichever the reader
170// happens to hit decides how the fleet treats that tool. nx_confdup scanned that exact file and
171// returned collisions=3 -- none of them this one.
172// ★★AN INSTRUMENT BLIND TO THE COMMON CASE ONLY EVER SEES CRISES.
173// ★A GENERATED ROW AND A HAND-WRITTEN ROW DISAGREEING IS NOT A TIE -- IT IS TWO ANSWERS TO A
174// QUESTION THAT MUST HAVE ONE.
175// Key = field 0 (up to the first TAB or SPACE), which is how every reader in this corpus keys a
176// .conf row. Blank lines and `#` comments are skipped; a repeated key is reported with EVERY line
177// number, because the remedy needs to know which copy to delete.
178func cd_read(path: *u8, buf: *u8, cap: i64) -> i64 {
179 let fd: i64 = sys_openat_rd(path)
180 if fd < 0 { return 0 }
181 var tot: i64 = 0
182 var go: i64 = 1
183 while go == 1 {
184 let n: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot)
185 if n <= 0 { go = 0 } else {
186 tot = tot + n
187 if tot >= cap { go = 0 }
188 }
189 }
190 sys_close(fd)
191 return tot
192}
193
194func cd_rowdups(path: *u8, fbuf: *u8, keyoff: *i64, keyline: *i64, keytaken: *i64,
195 karena: *u8, out: *u8, oin: i64, dupbox: *i64) -> i64 {
196 var o: i64 = oin
197 let n: i64 = cd_read(path, fbuf, CD_FBUF)
198 if n <= 0 { return o }
199
200 var ko: i64 = 0
201 var nk: i64 = 0
202 var line: i64 = 0
203 var i: i64 = 0
204 while i < n {
205 line = line + 1
206 var e: i64 = i
207 var scan: i64 = 1
208 while scan == 1 {
209 if e >= n { scan = 0 } else {
210 if fbuf[e] == (10 as u8) { scan = 0 } else { e = e + 1 }
211 }
212 }
213 // ★FIELD 0 ENDS AT A TAB IN A TAB-DELIMITED FILE, EVEN IF IT CONTAINS SPACES.
214 // Splitting unconditionally on space MANUFACTURED a duplicate: langintel_axiskw.conf is
215 // TAB-delimited and its keys are phrases -- "stack overflow", "stack frame", "stack probe"
216 // -- which a space-split collapses into three copies of "stack". A detector that invents
217 // findings is worse than one that misses them, because it burns the reader's trust on the
218 // real ones. So: if the line has a TAB, the key ends at the TAB; fall back to SPACE only
219 // for genuinely whitespace-delimited files.
220 var hastab: i64 = 0
221 var w: i64 = i
222 while w < e {
223 if fbuf[w] == (9 as u8) { hastab = 1; w = e } else { w = w + 1 }
224 }
225 var kend: i64 = i
226 var s2: i64 = 1
227 while s2 == 1 {
228 if kend >= e { s2 = 0 } else {
229 let c: i64 = fbuf[kend] as i64
230 var stop: i64 = 0
231 if c == 9 { stop = 1 }
232 if hastab == 0 { if c == 32 { stop = 1 } }
233 if stop == 1 { s2 = 0 } else { kend = kend + 1 }
234 }
235 }
236 let klen: i64 = kend - i
237 var ok: i64 = 1
238 if klen <= 0 { ok = 0 }
239 if klen >= CD_KEY { ok = 0 }
240 if ok == 1 { if fbuf[i] == (35 as u8) { ok = 0 } }
241 if ok == 1 { if fbuf[i] == (13 as u8) { ok = 0 } }
242 if ok == 1 {
243 if nk < CD_MAXROWS {
244 if ko + klen + 2 < CD_FBUF {
245 keyoff[nk] = ko
246 var c3: i64 = 0
247 while c3 < klen { karena[ko + c3] = fbuf[i + c3]; c3 = c3 + 1 }
248 karena[ko + klen] = 0 as u8
249 ko = ko + klen + 1
250 keyline[nk] = line
251 keytaken[nk] = 0
252 nk = nk + 1
253 }
254 }
255 }
256 i = e + 1
257 }
258
259 // ★★A DUPLICATE IS ONLY SUSPICIOUS IN A FILE WHOSE KEYS ARE OTHERWISE UNIQUE.
260 // Many .conf files here are deliberately MULTI-ROW-PER-KEY tables -- a category in field 0
261 // followed by N parameter rows (body_state_profiles.conf `preg` x20, uefi_fence_deny.conf `a` x12).
262 // Those are correct BY DESIGN. Reporting them buries the one real finding under noise, which is
263 // exactly how a detector earns being ignored -- the same way this one was ignorable before.
264 // So measure the FILE'S OWN SHAPE first and only report duplicates where uniqueness is the rule.
265 // Files skipped for shape are COUNTED and reported, never silently dropped.
266 var distinct: i64 = 0
267 var z: i64 = 0
268 while z < nk { keytaken[z] = 0; z = z + 1 }
269 z = 0
270 while z < nk {
271 if keytaken[z] == 0 {
272 distinct = distinct + 1
273 let kz: *u8 = ((karena as i64) + keyoff[z]) as *u8
274 keytaken[z] = 1
275 var y: i64 = z + 1
276 while y < nk {
277 if keytaken[y] == 0 {
278 let ky: *u8 = ((karena as i64) + keyoff[y]) as *u8
279 if cd_streq(kz, ky) == 1 { keytaken[y] = 1 }
280 }
281 y = y + 1
282 }
283 }
284 z = z + 1
285 }
286 if nk > 0 {
287 if (distinct * 100) / nk < CD_UNIQPCT { dupbox[1] = dupbox[1] + 1; return o }
288 }
289 z = 0
290 while z < nk { keytaken[z] = 0; z = z + 1 }
291
292 var a: i64 = 0
293 while a < nk {
294 if keytaken[a] == 0 {
295 let ka: *u8 = ((karena as i64) + keyoff[a]) as *u8
296 var cnt: i64 = 1
297 var b: i64 = a + 1
298 while b < nk {
299 if keytaken[b] == 0 {
300 let kb: *u8 = ((karena as i64) + keyoff[b]) as *u8
301 if cd_streq(ka, kb) == 1 { cnt = cnt + 1 }
302 }
303 b = b + 1
304 }
305 if cnt > 1 {
306 dupbox[0] = dupbox[0] + 1
307 if o < CD_OUT - CD_MAGIC_2048 {
308 o = cd_cat(out, o, "COLLIDE-ROW file=" as *u8)
309 o = cd_cat(out, o, path)
310 o = cd_cat(out, o, " key=" as *u8)
311 o = cd_cat(out, o, ka)
312 o = cd_cat(out, o, " count=" as *u8)
313 o = cd_udec(out, o, cnt)
314 o = cd_cat(out, o, " lines=" as *u8)
315 o = cd_udec(out, o, keyline[a])
316 }
317 keytaken[a] = 1
318 b = a + 1
319 while b < nk {
320 if keytaken[b] == 0 {
321 let kb2: *u8 = ((karena as i64) + keyoff[b]) as *u8
322 if cd_streq(ka, kb2) == 1 {
323 if o < CD_OUT - CD_MAGIC_2048 {
324 o = cd_ch(out, o, 44)
325 o = cd_udec(out, o, keyline[b])
326 }
327 keytaken[b] = 1
328 }
329 }
330 b = b + 1
331 }
332 if o < CD_OUT - CD_MAGIC_2048 { o = cd_ch(out, o, 10) }
333 }
334 }
335 a = a + 1
336 }
337 return o
338}
339
340func main(argc: i64, argv: *i64) -> i64 {
341 var root: *u8 = "knowledge" as *u8
342 if argc > 2 { root = (argv[2]) as *u8 }
343
344 let q: *u8 = sys_mmap(CD_QCAP)
345 let dbuf: *u8 = sys_mmap(CD_DIRBUF)
346 let arena: *u8 = sys_mmap(CD_ARENA)
347 let pathoff: *i64 = sys_mmap(8 * CD_MAXFILES) as *i64
348 let stemoff: *i64 = sys_mmap(8 * CD_MAXFILES) as *i64
349 let taken: *i64 = sys_mmap(8 * CD_MAXFILES) as *i64
350 let path: *u8 = sys_mmap(CD_PATH)
351 let stem: *u8 = sys_mmap(CD_STEM)
352 let out: *u8 = sys_mmap(CD_OUT)
353
354 var ao: i64 = 0 // arena offset
355 var nf: i64 = 0 // file count
356 var truncated: i64 = 0
357 var dirs: i64 = 0
358 var qh: i64 = 0
359 var qt: i64 = 0
360
361 var rl: i64 = 0
362 while root[rl] != (0 as u8) { q[qt] = root[rl]; qt = qt + 1; rl = rl + 1 }
363 q[qt] = 0 as u8
364 qt = qt + 1
365
366 while qh < qt {
367 let dir: *u8 = ((q as i64) + qh) as *u8
368 var dl: i64 = 0
369 while dir[dl] != (0 as u8) { dl = dl + 1 }
370 qh = qh + dl + 1
371 dirs = dirs + 1
372 let dfd: i64 = sys_openat_rd(dir)
373 if dfd >= 0 {
374 var done: i64 = 0
375 while done == 0 {
376 let nread: i64 = sys_getdents64(dfd, dbuf, CD_DIRBUF)
377 if nread <= 0 { done = 1 } else {
378 var off: i64 = 0
379 while off < nread {
380 let drec: *u8 = ((dbuf as i64) + off) as *u8
381 let reclen: i64 = dirent_reclen(drec)
382 let dtype: i64 = dirent_type(drec)
383 let name: *u8 = dirent_name(drec)
384 var skip: i64 = 0
385 if name[0] == (46 as u8) { skip = 1 } // . .. dotdirs
386 if skip == 0 {
387 if dtype == DT_DIR {
388 let need: i64 = cd_join(path, dir, name)
389 if qt + need + 2 < CD_QCAP {
390 var c: i64 = 0
391 while c <= need { q[qt + c] = path[c]; c = c + 1 }
392 qt = qt + need + 1
393 }
394 }
395 if dtype == DT_REG {
396 if cd_is_conf(name) == 1 {
397 let pl: i64 = cd_join(path, dir, name)
398 let sl: i64 = cd_norm(stem, name)
399 if nf >= CD_MAXFILES { truncated = 1 } else {
400 if ao + pl + sl + 4 >= CD_ARENA { truncated = 1 } else {
401 pathoff[nf] = ao
402 var c2: i64 = 0
403 while c2 <= pl { arena[ao + c2] = path[c2]; c2 = c2 + 1 }
404 ao = ao + pl + 1
405 stemoff[nf] = ao
406 c2 = 0
407 while c2 <= sl { arena[ao + c2] = stem[c2]; c2 = c2 + 1 }
408 ao = ao + sl + 1
409 taken[nf] = 0
410 nf = nf + 1
411 }
412 }
413 }
414 }
415 }
416 if reclen <= 0 { off = nread } else { off = off + reclen }
417 }
418 }
419 }
420 sys_close(dfd)
421 }
422 }
423
424 var o: i64 = 0
425 o = cd_cat(out, o, "=== nx_confdup -- config files declaring the SAME CONCERN twice ===\n" as *u8)
426 o = cd_cat(out, o, "root=" as *u8)
427 o = cd_cat(out, o, root)
428 o = cd_cat(out, o, " dirs=" as *u8)
429 o = cd_udec(out, o, dirs)
430 o = cd_cat(out, o, " conf_files=" as *u8)
431 o = cd_udec(out, o, nf)
432 if truncated == 1 { o = cd_cat(out, o, " TRUNCATED=1 (raise CD_MAXFILES/CD_ARENA; NOT a silent cap)" as *u8) }
433 o = cd_ch(out, o, 10)
434
435 var groups: i64 = 0
436 var basegroups: i64 = 0
437 var i2: i64 = 0
438 while i2 < nf {
439 if taken[i2] == 0 {
440 let si: *u8 = ((arena as i64) + stemoff[i2]) as *u8
441 var members: i64 = 0
442 var j: i64 = i2 + 1
443 while j < nf {
444 if taken[j] == 0 {
445 let sj: *u8 = ((arena as i64) + stemoff[j]) as *u8
446 if cd_streq(si, sj) == 1 { members = members + 1 }
447 }
448 j = j + 1
449 }
450 if members > 0 {
451 groups = groups + 1
452 // are the BASENAMES identical too? then it is the two-roots-one-name shape
453 let pi: *u8 = ((arena as i64) + pathoff[i2]) as *u8
454 var samebase: i64 = 0
455 j = i2 + 1
456 while j < nf {
457 if taken[j] == 0 {
458 let sj2: *u8 = ((arena as i64) + stemoff[j]) as *u8
459 if cd_streq(si, sj2) == 1 {
460 let pj: *u8 = ((arena as i64) + pathoff[j]) as *u8
461 if cd_streq(cd_basename(pi), cd_basename(pj)) == 1 { samebase = 1 }
462 }
463 }
464 j = j + 1
465 }
466 if samebase == 1 {
467 basegroups = basegroups + 1
468 o = cd_cat(out, o, "COLLIDE-BASE stem=" as *u8)
469 } else {
470 o = cd_cat(out, o, "COLLIDE-STEM stem=" as *u8)
471 }
472 o = cd_cat(out, o, si)
473 o = cd_ch(out, o, 10)
474 o = cd_cat(out, o, " " as *u8)
475 o = cd_cat(out, o, pi)
476 o = cd_ch(out, o, 10)
477 taken[i2] = 1
478 j = i2 + 1
479 while j < nf {
480 if taken[j] == 0 {
481 let sj3: *u8 = ((arena as i64) + stemoff[j]) as *u8
482 if cd_streq(si, sj3) == 1 {
483 o = cd_cat(out, o, " " as *u8)
484 o = cd_cat(out, o, ((arena as i64) + pathoff[j]) as *u8)
485 o = cd_ch(out, o, 10)
486 taken[j] = 1
487 }
488 }
489 j = j + 1
490 }
491 }
492 }
493 i2 = i2 + 1
494 }
495
496 // ---- COLLIDE-ROW pass: same key twice inside ONE file (see cd_rowdups header) ----
497 let fbuf: *u8 = sys_mmap(CD_FBUF)
498 let karena: *u8 = sys_mmap(CD_FBUF)
499 let keyoff: *i64 = sys_mmap(8 * CD_MAXROWS) as *i64
500 let keyline: *i64 = sys_mmap(8 * CD_MAXROWS) as *i64
501 let keytaken: *i64 = sys_mmap(8 * CD_MAXROWS) as *i64
502 let dupbox: *i64 = sys_mmap(16) as *i64
503 dupbox[0] = 0
504 dupbox[1] = 0
505 var fi: i64 = 0
506 while fi < nf {
507 o = cd_rowdups(((arena as i64) + pathoff[fi]) as *u8, fbuf, keyoff, keyline, keytaken, karena, out, o, dupbox)
508 fi = fi + 1
509 }
510
511 o = cd_cat(out, o, "\nNX-CONFDUP collisions=" as *u8)
512 o = cd_udec(out, o, groups)
513 o = cd_cat(out, o, " same_basename=" as *u8)
514 o = cd_udec(out, o, basegroups)
515 o = cd_cat(out, o, " row_dups=" as *u8)
516 o = cd_udec(out, o, dupbox[0])
517 o = cd_cat(out, o, " multirow_files_skipped=" as *u8)
518 o = cd_udec(out, o, dupbox[1])
519 o = cd_cat(out, o, " scanned=" as *u8)
520 o = cd_udec(out, o, nf)
521 var bad: i64 = groups
522 if dupbox[0] > 0 { bad = bad + dupbox[0] }
523 if bad == 0 {
524 o = cd_cat(out, o, " verdict=GREEN (no config concern is declared twice, and no file declares a key twice)\n" as *u8)
525 } else {
526 o = cd_cat(out, o, " verdict=RED (each group is a place a config row can be SILENTLY INERT, and each COLLIDE-ROW is two answers to a question that must have one)\n" as *u8)
527 }
528 sys_write(1, out, o)
529 if bad == 0 { return 0 }
530 return 1
531}