code wiki / _hdl_build / nx_retire_path.nx
nx_retire_path.nx source
↩ module page · 524 lines · 32666 B
1// nx_retire_path.nx -- the missing sovereign primitive: take a file or directory OUT of a served tree
2// without ever deleting it. The estate had governance/atlas/debt sweeps but nothing that could retire a
3// path, so scratch artifacts accumulated on PUBLIC surfaces and the only "fix" anyone had was ssh rm.
4//
5// SAFE BY CONSTRUCTION (mirrors the established md_promote_deny substring-deny pattern rather than
6// inventing a new safety model):
7// * NEVER deletes -- sys_renameat only, so the bytes survive and the move is reversible (rule 13).
8// * moves INTO knowledge/retired/ (outside every docroot) so a retired path stops being served.
9// * REFUSES: any path containing ".." (traversal), a shallow path -- the rule COUNTS SLASHES and needs
10// at least 2, i.e. dir/subdir/file, so `knowledge/x.tsv` is REFUSED while `knowledge/status/x.tsv` is
11// allowed. (The message used to say "must be >=2 segments deep", which names the wrong unit and reads
12// as satisfied by the very path it rejects; corrected 2026-08-14 after it sent a caller hunting.)
13// so it can never retire a whole docroot or a hub-root file), and any path whose name carries a
14// dangerous substring (elf, key, cap, secret, opaque, .reg, .conf, daemon, serve, vault, mint).
15// * REFUSES a target that does not exist -- a no-op must never look like a success.
16// verbs: retire <path> | selftest
17// expect_exit: 0 license_tier: ORIGINAL
18import "nx_syscalls.nx"
19import "nx_emit_guard.nx"
20
21const RP_NAMEBUF: i64 = 1024
22
23func rt_say(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
24func rt_num(v: i64) -> i64 { if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } let t: *u8 = sys_mmap(28); var k: i64 = 0; while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } while k > 0 { k = k - 1; sys_write(1, (((t as i64) + k) as *u8), 1) } return 0 }
25
26func rt_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
27// case-sensitive substring test (the deny list is lowercase and so are our paths)
28func rt_has(s: *u8, lit: *u8) -> i64 {
29 let sl: i64 = rt_len(s)
30 let ll: i64 = rt_len(lit)
31 if ll == 0 { return 0 }
32 var i: i64 = 0
33 while i + ll <= sl {
34 var j: i64 = 0
35 var ok: i64 = 1
36 while j < ll { if s[i + j] != lit[j] { ok = 0; j = ll } else { j = j + 1 } }
37 if ok == 1 { return 1 }
38 i = i + 1
39 }
40 return 0
41}
42// does s end with lit? (suffix test -- the staged-artifact exception below is a SUFFIX rule, never a
43// substring rule: a substring test would also match a LIVE path that merely mentions '.elf.new')
44func rt_ends(s: *u8, lit: *u8) -> i64 {
45 let sl: i64 = rt_len(s)
46 let ll: i64 = rt_len(lit)
47 if ll == 0 { return 0 }
48 if ll > sl { return 0 }
49 var i: i64 = 0
50 while i < ll { if s[sl - ll + i] != lit[i] { return 0 } i = i + 1 }
51 return 1
52}
53// SECRET-MATERIAL guards, applied to EVERY path including the staged-artifact exception. These are the
54// checks that protect CREDENTIALS; they are deliberately separate from the artifact-TYPE checks (.elf/
55// .conf/.reg) and the depth rule, because those two protect LIVE-SERVING paths -- a distinction the
56// staged exception depends on.
57// ONE TABLE, EVERY READER. The deny tokens were written out THREE times: in rt_refuse_secret, in
58// rt_refuse, and as PROSE inside rt_reason's code-4 message. The prose copy drifted -- it listed 9 tokens
59// while the predicate checked 11, and the two it omitted (`daemon`, `serve`) are precisely the ones that
60// fired on a real caller. That caller read "protected name (.elf .key .cap .conf .reg secret opaque vault
61// mint)", found NONE of them in `buildroot/runtime/nx_servedrift.nx`, and reasonably wrote the guard up as
62// broken. It was not: `nx_servedrift` contains `serve`, and refusing it is CORRECT.
63// ★★★★★★A CORRECT REFUSAL WEARING AN INCOMPLETE REASON IS THE MOST EXPENSIVE FAILURE SHAPE THERE IS --
64// IT SPENDS THE READER'S TRUST ON A DECISION THAT WAS RIGHT, AND TEACHES THEM TO ROUTE AROUND THE GUARD.
65// ★★★★★THE EARLIER DRY FIX WENT ONE LEVEL TOO SHALLOW: it derived the code->message MAP from the code and
66// left the MESSAGE reciting a hand-maintained copy of the predicate's own terms. Deriving the map buys
67// nothing while the leaf is still duplicated -- and this organ's header ALREADY required a refusal to name
68// WHICH rule fired rather than recite every rule it might have applied. Code 4 recited nine and named none.
69static rt_hit: *u8
70
71func rt_deny_n() -> i64 { return 11 }
72func rt_deny_tok(i: i64) -> *u8 {
73 if i == 0 { return ".elf" as *u8 }
74 if i == 1 { return ".key" as *u8 }
75 if i == 2 { return ".cap" as *u8 }
76 if i == 3 { return ".conf" as *u8 }
77 if i == 4 { return ".reg" as *u8 }
78 if i == 5 { return "secret" as *u8 }
79 if i == 6 { return "opaque" as *u8 }
80 if i == 7 { return "vault" as *u8 }
81 if i == 8 { return "mint" as *u8 }
82 if i == 9 { return "daemon" as *u8 }
83 if i == 10 { return "serve" as *u8 }
84 return "" as *u8
85}
86// The SECRET subset protects CREDENTIALS and applies to EVERY path INCLUDING the staged-artifact
87// exception; the rest protect LIVE-SERVING paths, which the staged exception deliberately bypasses.
88// Keeping that split as a COLUMN of one table, rather than as a second hand-written list, is what stops
89// the two from drifting apart again.
90func rt_deny_is_secret(i: i64) -> i64 {
91 if i == 1 { return 1 }
92 if i == 2 { return 1 }
93 if i == 5 { return 1 }
94 if i == 6 { return 1 }
95 if i == 7 { return 1 }
96 if i == 8 { return 1 }
97 return 0
98}
99func rt_refuse_secret(path: *u8) -> i64 {
100 var i: i64 = 0
101 while i < rt_deny_n() {
102 if rt_deny_is_secret(i) == 1 {
103 if rt_has(path, rt_deny_tok(i)) == 1 { rt_hit = rt_deny_tok(i); return 4 }
104 }
105 i = i + 1
106 }
107 return 0
108}
109// prefix test -- needed by the DIRECTORY lane below, which refuses served roots by prefix rather than by
110// depth, because for a directory the blast radius of a wrong call is a whole site rather than one file.
111func rt_starts(s: *u8, lit: *u8) -> i64 {
112 var i: i64 = 0
113 while lit[i] != (0 as u8) {
114 if s[i] != lit[i] { return 0 }
115 i = i + 1
116 }
117 return 1
118}
119func rt_slashes(s: *u8) -> i64 {
120 var c: i64 = 0
121 var i: i64 = 0
122 while s[i] != (0 as u8) { if s[i] == (47 as u8) { c = c + 1 } i = i + 1 }
123 return c
124}
125const RT_COMPARE_ROOT: *u8 = "sites/nishifamily/compare/"
126const RT_COMPARE_MIN_SLASHES: i64 = 4
127const RT_SLASH: i64 = 47
128// 0 = allowed, otherwise a refusal code naming the reason
129func rt_refuse(path: *u8) -> i64 {
130 if rt_len(path) < 3 { return 1 }
131 if rt_has(path, ".." as *u8) == 1 { return 2 }
132 // ---- STAGED-ARTIFACT EXCEPTION (2026-08-03, debt 1785771205) ----------------------------------
133 // WHY IT IS SAFE, not a loosened guard: `<name>.elf.new` is a STAGED artifact. The LIVE binary is
134 // `<name>.elf` (or the extensionless `<name>`), so a `.elf.new` is BY CONSTRUCTION not serving any
135 // request -- retiring one can never stop a running service, which is exactly what the depth rule and
136 // the `.elf` name rule exist to prevent. Those two guards protect LIVE-SERVING paths; they were
137 // ALSO, as a side effect, making the estate's 512 stray staged artifacts unretirable -- including a
138 // 202,843 B `nx_hostctl.elf.new` sitting in the SUPERVISOR's deploy slot matching neither the live
139 // binary nor any source build. A guard that blocks the cleanup of the very hazard class it cannot
140 // see is protecting the wrong noun.
141 // SUFFIX, never substring: a live path that merely CONTAINS '.elf.new' is not exempted.
142 // Secret-material checks STILL APPLY (a `*.key.new` stays refused), and rename-not-delete means the
143 // bytes survive in knowledge/retired/ -- so a staged artifact retired in error is restorable.
144 if rt_ends(path, ".elf.new" as *u8) == 1 { return rt_refuse_secret(path) }
145 // ...and the BANKED twin. /api/build now renames an existing staged artifact to `<t>.sov.elf.new.prev`
146 // before recompiling (debt 1785772241) -- a remedy that CREATES artifacts must also create the means to
147 // sweep them, or the fix becomes the next litter source. Same reasoning as above: a banked staged copy
148 // is even further from live than the staged one, so it can never be serving anything.
149 if rt_ends(path, ".elf.new.prev" as *u8) == 1 { return rt_refuse_secret(path) }
150 // ---- SERVING-ROOT SOURCE-STRAY EXCEPTION (2026-08-17) ------------------------------------------
151 // WHY IT IS SAFE, not a loosened guard: `runtime/<name>.nx` at exactly ONE slash is NishiLang SOURCE
152 // in the SERVING root. Source is never served and never executed from this tree -- the build lane is
153 // buildroot-anchored (nx_sov_build_run chdirs to buildroot/), so the only resolver that can ever see
154 // such a file is a CWD-rooted one: the shadow-resolution defect class adjudicated 2026-08-17
155 // (ep_src_path was made buildroot-anchored-by-construction for exactly this reason). Retiring one can
156 // never stop a running service; LEAVING one arms the trap where a nishihost-CWD caller silently
157 // compiles a different file than the build lane. Measured population when written: 8 stray sources,
158 // each byte-identical to its buildroot twin or an adjudicated probe stub.
159 // STATED IMPRECISION: unlike the staged lane this walk applies the FULL deny-token table (source can
160 // be named anything), so a stray whose NAME carries a protected token (e.g. nx_servedrift.nx) stays
161 // unretirable here -- over-refusal is the direction that costs a stray, never a service.
162 if rt_slashes(path) == 1 { if rt_starts(path, "runtime/" as *u8) == 1 { if rt_ends(path, ".nx" as *u8) == 1 {
163 var sd: i64 = 0
164 while sd < rt_deny_n() {
165 if rt_has(path, rt_deny_tok(sd)) == 1 { rt_hit = rt_deny_tok(sd); return 4 }
166 sd = sd + 1
167 }
168 return 0
169 } } }
170 // ---- COMPARE-BOARD PAGE EXCEPTION (2026-09-16) --------------------------------------------------
171 // WHY IT IS SAFE, not a loosened guard: a file under sites/nishifamily/compare/<board>/ is a PAGE the /compare
172 // gateway reads from disk; no daemon runs from that tree, so retiring one can never stop a service. The <board>
173 // segment is a DOMAIN NAME from regen.list -- a data word -- and the token table read it as a material type:
174 // `cleanserve` carries `serve`, so a 60-day-old copy of an earlier radar page, sitting as a regular FILE exactly
175 // where the generator must create cleanserve/frontier/, could never be retired, and every full regen published
176 // RED on it (RECEIPT-TRANSACTION-FAIL syscall=-20, ENOTDIR). The FULL token table still applies to everything
177 // BELOW the board segment (compare/<board>/x.elf, compare/<board>/nx_media_serve stay refused), the SECRET subset
178 // still applies to the WHOLE path (a board segment carrying vault or mint stays refused), traversal was refused
179 // above, and only a file below a board enters: the board directory and the compare root carry too few slashes.
180 // STATED IMPRECISION: the prefix is the nishifamily compare docroot by name; a second compare docroot would need
181 // its own row, and until then it stays in the stricter general lane (over-refusal, never a service).
182 if rt_starts(path, RT_COMPARE_ROOT) == 1 { if rt_slashes(path) >= RT_COMPARE_MIN_SLASHES {
183 let csec: i64 = rt_refuse_secret(path)
184 if csec != 0 { return csec }
185 var bend: i64 = rt_len(RT_COMPARE_ROOT)
186 while path[bend] != (0 as u8) { if path[bend] == (RT_SLASH as u8) { break } bend = bend + 1 }
187 let below: *u8 = (path as i64 + bend + 1) as *u8
188 var cd: i64 = 0
189 while cd < rt_deny_n() {
190 if rt_has(below, rt_deny_tok(cd)) == 1 { rt_hit = rt_deny_tok(cd); return 4 }
191 cd = cd + 1
192 }
193 return 0
194 } }
195 if rt_slashes(path) < 2 { return 3 }
196 var d: i64 = 0
197 while d < rt_deny_n() {
198 if rt_has(path, rt_deny_tok(d)) == 1 { rt_hit = rt_deny_tok(d); return 4 }
199 d = d + 1
200 }
201 return 0
202}
203// ---- THE DIRECTORY LANE (2026-08-15) ---------------------------------------------------------------
204// WHY IT EXISTS: the file rule COUNTS SLASHES and needs >=2 (dir/subdir/file), which is right for files
205// and leaves a whole class unreachable -- an accidental orphan DIRECTORY is created at exactly
206// `knowledge/<name>`, one slash, so the organ that exists to clean up stray artifacts could not touch the
207// commonest stray shape. Measured this session: a 235-file 9.7 MB duplicate mirror sat at
208// knowledge/gfxmirror with no lane to retire it, and an unreferenced tree is not merely wasted disk --
209// every reachability question asked over the tree silently consumes it as if it were a registry.
210//
211// WHY IT IS NOT A LOOSENED GUARD. It is strictly NARROWER than the file lane in the direction that
212// matters, and it borrows the estate's own established idiom for a removal that cannot be undone by
213// guessing: TWO NAMES. nx_toolreg's removal verb takes `<name> <by>` precisely so one name is never
214// enough, and the same reasoning applies with more force here, because a directory carries everything
215// beneath it. Every existing protection still applies unchanged -- traversal, the full 11-token protected
216// table, secret material, must-exist, and rename-never-delete.
217// * REFUSES a bare top-level name (zero slashes): `knowledge` can never be retired.
218// * REFUSES anything under a SERVED root by prefix. A directory under sites/ is a site or part of one,
219// and the cost of being wrong there is a whole surface going dark, so the directory lane simply does
220// not go there. That is a STATED limitation, not an oversight: retiring inside a docroot stays a
221// file-at-a-time operation where the existing >=2 rule already applies.
222// * REFUSES unless the confirmation name is byte-identical to the path.
223const RT_SERVED_ROOT: *u8 = "sites/"
224
225func rt_same(a: *u8, b: *u8) -> i64 {
226 var i: i64 = 0
227 while a[i] != (0 as u8) {
228 if a[i] != b[i] { return 0 }
229 i = i + 1
230 }
231 if b[i] != (0 as u8) { return 0 }
232 return 1
233}
234
235func rt_refuse_dir(path: *u8, confirm: *u8) -> i64 {
236 if rt_len(path) < 3 { return 1 }
237 if rt_has(path, ".." as *u8) == 1 { return 2 }
238 if rt_slashes(path) < 1 { return 6 }
239 if rt_starts(path, RT_SERVED_ROOT) == 1 { return 5 }
240 var d: i64 = 0
241 while d < rt_deny_n() {
242 if rt_has(path, rt_deny_tok(d)) == 1 { rt_hit = rt_deny_tok(d); return 4 }
243 d = d + 1
244 }
245 // the two-name check runs LAST so a caller who mistyped a PROTECTED path still learns which rule
246 // protects it, rather than being told only that their confirmation did not match.
247 if rt_same(path, confirm) == 0 { return 7 }
248 return 0
249}
250
251// flatten "a/b/c" -> "a_b_c" into dst
252func rt_flatten(dst: *u8, src: *u8) -> i64 {
253 var o: i64 = 0
254 var i: i64 = 0
255 while src[i] != (0 as u8) {
256 if src[i] == (47 as u8) { dst[o] = 95 as u8 } else { dst[o] = src[i] }
257 o = o + 1
258 i = i + 1
259 }
260 return o
261}
262func rt_cat(dst: *u8, off: i64, s: *u8) -> i64 {
263 var o: i64 = off
264 var i: i64 = 0
265 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
266 return o
267}
268
269// ONE PLACE NAMES EACH REFUSAL, AND THE MESSAGE READS IT.
270// The refusal used to print a hand-maintained "1=... 2=... 3=... 4=..." list beside rt_refuse's codes:
271// two parallel lists, so adding a code left the message silently lying, and correcting the WORDING (as was
272// done earlier today, after code 3 named the wrong unit and sent a caller hunting) does nothing to stop the
273// next drift. ★★★A MESSAGE MAINTAINED BESIDE THE PREDICATE IT DESCRIBES IS A DUPLICATE RULER; DERIVE IT
274// FROM THE CODE AND THERE IS ONE PLACE TO GET IT RIGHT.
275// It also prints ONLY the rule that actually fired -- the estate's own law that a refusal must say WHICH
276// rule refused, not recite every rule it might have applied.
277// ★UNKNOWN IS ITS OWN BUCKET and it accuses THIS ORGAN, not the caller's path: if rt_refuse ever returns a
278// code rt_reason does not name, the drift ANNOUNCES itself instead of mislabelling the refusal as one of
279// the codes it happens to sit near.
280func rt_reason(code: i64) -> *u8 {
281 if code == 1 { return "path too short to be a real artifact" as *u8 }
282 if code == 2 { return "traversal -- the path contains .." as *u8 }
283 if code == 3 { return "too shallow -- needs >=2 slashes (dir/subdir/file), so a file sitting directly inside a top-level dir is refused" as *u8 }
284 if code == 4 { return "protected name -- live/serving or secret material; the token that fired is named as token= below" as *u8 }
285 if code == 5 { return "directory lane refuses a SERVED root -- a directory under sites/ is a site or part of one, and retiring it would take a whole surface dark; retire inside a docroot one file at a time" as *u8 }
286 if code == 6 { return "directory lane needs >=1 slash -- a bare top-level name like knowledge can never be retired" as *u8 }
287 if code == 7 { return "confirmation name does not match the path -- retiredir takes the path TWICE on purpose, so one name is never enough to move a whole tree" as *u8 }
288 return "UNRECOGNISED CODE -- rt_refuse returned a code rt_reason does not name. That is a defect in THIS ORGAN, not in the path you gave it." as *u8
289}
290
291// returns 0 on success; prints the refusal and returns nonzero otherwise
292func rt_retire(path: *u8) -> i64 {
293 let why: i64 = rt_refuse(path)
294 if why != 0 {
295 rt_say("RETIRE-REFUSED code=" as *u8); rt_num(why)
296 rt_say(" path=" as *u8); rt_say(path)
297 rt_say(" reason=" as *u8); rt_say(rt_reason(why))
298 // NAME THE RULE THAT FIRED, NOT THE SET IT BELONGS TO. ★UNKNOWN IS ITS OWN BUCKET and it accuses
299 // THIS ORGAN: a code 4 that reached here without recording a token is a defect in rt_refuse, and
300 // saying so is strictly better than printing a plausible token nobody verified fired.
301 if why == 4 {
302 rt_say(" token=" as *u8)
303 if (rt_hit as i64) == 0 { rt_say("UNRECORDED -- defect in THIS ORGAN: rt_refuse returned 4 without recording which token matched" as *u8) } else { rt_say(rt_hit) }
304 }
305 rt_say("\n" as *u8)
306 return why
307 }
308 if eg_have(path) == 0 {
309 // a no-op must never look like a success
310 rt_say("RETIRE-ABSENT path=" as *u8); rt_say(path); rt_say("\n" as *u8)
311 return 9
312 }
313 sys_mkdir("knowledge/retired" as *u8, 493)
314 let dst: *u8 = eg_buf(RP_NAMEBUF, "retire destination" as *u8)
315 var o: i64 = rt_cat(dst, 0, "knowledge/retired/" as *u8)
316 o = o + rt_flatten((((dst as i64) + o) as *u8), path)
317 o = rt_cat(dst, o, "-" as *u8)
318 // stamp so repeated retires of the same name never collide
319 var ts: i64 = sys_now_realtime_sec()
320 let tb: *u8 = eg_buf(32, "retire stamp" as *u8)
321 var k: i64 = 0
322 while ts > 0 { tb[k] = (48 + (ts % 10)) as u8; ts = ts / 10; k = k + 1 }
323 while k > 0 { k = k - 1; dst[o] = tb[k]; o = o + 1 }
324 dst[o] = 0 as u8
325 let rc: i64 = sys_renameat(path, dst)
326 if rc != 0 {
327 rt_say("RETIRE-FAIL rename rc=" as *u8); rt_num(rc)
328 rt_say(" path=" as *u8); rt_say(path); rt_say("\n" as *u8)
329 return 3
330 }
331 rt_say("{\"organ\":\"nx_retire_path\",\"v\":1,\"retired\":\"" as *u8); rt_say(path)
332 rt_say("\",\"to\":\"" as *u8); rt_say(dst)
333 rt_say("\",\"deleted\":0,\"reversible\":1}\n" as *u8)
334 return 0
335}
336
337// The directory twin of rt_retire. Same rename-never-delete move, same stamped destination, so a tree
338// retired in error is restored by renaming it back -- the bytes are never at risk.
339func rt_retiredir(path: *u8, confirm: *u8) -> i64 {
340 let why: i64 = rt_refuse_dir(path, confirm)
341 if why != 0 {
342 rt_say("RETIREDIR-REFUSED code=" as *u8); rt_num(why)
343 rt_say(" path=" as *u8); rt_say(path)
344 rt_say(" reason=" as *u8); rt_say(rt_reason(why))
345 if why == 4 {
346 rt_say(" token=" as *u8)
347 if (rt_hit as i64) == 0 { rt_say("UNRECORDED -- defect in THIS ORGAN" as *u8) } else { rt_say(rt_hit) }
348 }
349 rt_say("\n" as *u8)
350 return why
351 }
352 if eg_have(path) == 0 {
353 rt_say("RETIREDIR-ABSENT path=" as *u8); rt_say(path); rt_say("\n" as *u8)
354 return 9
355 }
356 sys_mkdir("knowledge/retired" as *u8, 493)
357 let dst: *u8 = eg_buf(RP_NAMEBUF, "retiredir destination" as *u8)
358 var o: i64 = rt_cat(dst, 0, "knowledge/retired/" as *u8)
359 o = o + rt_flatten((((dst as i64) + o) as *u8), path)
360 o = rt_cat(dst, o, "-" as *u8)
361 var ts: i64 = sys_now_realtime_sec()
362 let tb: *u8 = eg_buf(32, "retiredir stamp" as *u8)
363 var k: i64 = 0
364 while ts > 0 { tb[k] = (48 + (ts % 10)) as u8; ts = ts / 10; k = k + 1 }
365 while k > 0 { k = k - 1; dst[o] = tb[k]; o = o + 1 }
366 dst[o] = 0 as u8
367 let rc: i64 = sys_renameat(path, dst)
368 if rc != 0 {
369 rt_say("RETIREDIR-FAIL rename rc=" as *u8); rt_num(rc)
370 rt_say(" path=" as *u8); rt_say(path); rt_say("\n" as *u8)
371 return 3
372 }
373 rt_say("{\x22organ\x22:\x22nx_retire_path\x22,\x22v\x22:1,\x22retireddir\x22:\x22" as *u8); rt_say(path)
374 rt_say("\x22,\x22to\x22:\x22" as *u8); rt_say(dst)
375 rt_say("\x22,\x22deleted\x22:0,\x22reversible\x22:1}\n" as *u8)
376 return 0
377}
378
379func main(argc: i64, argv: *i64) -> i64 {
380 if argc < 2 {
381 rt_say("usage: nx_retire_path {retire <path> | selftest}\n" as *u8)
382 rt_say(" moves a path into knowledge/retired/ (never deletes; bytes preserved, reversible)\n" as *u8)
383 sys_exit(2)
384 return 2
385 }
386 let verb: *u8 = argv[1] as *u8
387 if rt_has(verb, "selftest" as *u8) == 1 {
388 var fails: i64 = 0
389 // the refusals are the whole safety story, so they are what the selftest proves
390 if rt_refuse("nx_mgmt_api.elf" as *u8) == 0 { fails = fails + 1 }
391 if rt_refuse("tools_cap_secret.key" as *u8) == 0 { fails = fails + 1 }
392 if rt_refuse("sites/../../etc/passwd" as *u8) == 0 { fails = fails + 1 }
393 if rt_refuse("daemons.reg" as *u8) == 0 { fails = fails + 1 }
394 if rt_refuse("sites" as *u8) == 0 { fails = fails + 1 }
395 if rt_refuse("sites/nishifamily" as *u8) == 0 { fails = fails + 1 }
396 if rt_refuse("nx_sites_daemon_v2.elf" as *u8) == 0 { fails = fails + 1 }
397 // and a legitimate scratch path must be ALLOWED (a guard that refuses everything is useless)
398 if rt_refuse("sites/nishifamily/factory/gen-coffee/d1" as *u8) != 0 { fails = fails + 1 }
399 if rt_refuse("knowledge/status/uigen_tabprobe.txt" as *u8) != 0 { fails = fails + 1 }
400 // ---- STAGED-ARTIFACT EXCEPTION: it must ADMIT the hazard class and REFUSE every neighbour ----
401 // ADMIT: root-level staged elfs (both staging shapes) -- the 512-strong stray class
402 if rt_refuse("nx_hostctl.elf.new" as *u8) != 0 { fails = fails + 1 }
403 if rt_refuse("nx_daemon_supervisor.sov.elf.new" as *u8) != 0 { fails = fails + 1 }
404 // ADMIT the BANKED twin that /api/build's new anti-clobber bank creates (debt 1785772241)
405 if rt_refuse("nx_tool_argecho.sov.elf.new.prev" as *u8) != 0 { fails = fails + 1 }
406 // REFUSE a LIVE .prev -- promote/deploy bank the RUNNING binary under this name, and that copy is
407 // the rollback source. Only the STAGED lineage (.elf.new.prev) is exempt, never `<name>.elf.prev`.
408 if rt_refuse("nx_mgmt_api.elf.prev" as *u8) == 0 { fails = fails + 1 }
409 // REFUSE: the LIVE binary of the same organ (the one-character difference that matters most)
410 if rt_refuse("nx_hostctl.elf" as *u8) == 0 { fails = fails + 1 }
411 // REFUSE: staged SECRET material -- the secret guards survive the exception
412 if rt_refuse("tools_cap_secret.key.new" as *u8) == 0 { fails = fails + 1 }
413 if rt_refuse("opaque_keys.bin.elf.new" as *u8) == 0 { fails = fails + 1 }
414 // REFUSE: traversal is never exempted, even wearing the staged suffix
415 if rt_refuse("../../etc/evil.elf.new" as *u8) == 0 { fails = fails + 1 }
416 // REFUSE: SUFFIX not substring -- a live path merely CONTAINING the marker is not exempt
417 if rt_refuse("nx_thing.elf.new.elf" as *u8) == 0 { fails = fails + 1 }
418 // ---- SOURCE-STRAY EXCEPTION (2026-08-17): ADMIT the shadow class, REFUSE every neighbour ----
419 // ADMIT: a one-slash serving-root source -- the measured shadow class this lane exists for
420 if rt_refuse("runtime/nx_regdup.nx" as *u8) != 0 { fails = fails + 1 }
421 // REFUSE: same depth, not source -- an artifact wearing runtime/ is not exempted
422 if rt_refuse("runtime/nx_regdup.elf" as *u8) == 0 { fails = fails + 1 }
423 // REFUSE + NAME: the full token table survives this exception (unlike the staged lane)
424 if rt_refuse("runtime/nx_servedrift.nx" as *u8) != 4 { fails = fails + 1 }
425 if rt_has(rt_hit, "serve" as *u8) != 1 { fails = fails + 1 }
426 // REFUSE: the prefix is `runtime/` exactly -- a sibling dir cannot borrow the lane
427 if rt_refuse("myruntime/nx_regdup.nx" as *u8) == 0 { fails = fails + 1 }
428 // REFUSE: traversal is never exempted in any lane
429 if rt_refuse("runtime/../nx_regdup.nx" as *u8) == 0 { fails = fails + 1 }
430 // ---- COMPARE-BOARD PAGE EXCEPTION (2026-09-16): ADMIT a page below a board whose NAME carries a token ----
431 // ADMIT: the exact stale page that held every full regen RED
432 if rt_refuse("sites/nishifamily/compare/cleanserve/frontier" as *u8) != 0 { fails = fails + 1 }
433 // REFUSE + NAME: the full table still applies below the board segment
434 if rt_refuse("sites/nishifamily/compare/cleanserve/nx_media_serve" as *u8) != 4 { fails = fails + 1 }
435 if rt_has(rt_hit, "serve" as *u8) != 1 { fails = fails + 1 }
436 if rt_refuse("sites/nishifamily/compare/search/nx_thing.elf" as *u8) != 4 { fails = fails + 1 }
437 // REFUSE: the secret subset still applies to the WHOLE path, board segment included
438 if rt_refuse("sites/nishifamily/compare/vaultboard/index.html" as *u8) != 4 { fails = fails + 1 }
439 if rt_has(rt_hit, "vault" as *u8) != 1 { fails = fails + 1 }
440 // REFUSE: the board directory itself never enters the lane (too few slashes, general table fires)
441 if rt_refuse("sites/nishifamily/compare/cleanserve" as *u8) != 4 { fails = fails + 1 }
442 // REFUSE: the prefix is exact -- a sibling tree and a lookalike root cannot borrow the lane
443 if rt_refuse("sites/nishifamily/cleanserve/frontier" as *u8) != 4 { fails = fails + 1 }
444 if rt_refuse("sites/nishifamily/compareX/cleanserve/frontier" as *u8) != 4 { fails = fails + 1 }
445 // REFUSE: traversal is never exempted in any lane
446 if rt_refuse("sites/nishifamily/compare/cleanserve/../../x" as *u8) != 2 { fails = fails + 1 }
447 // absent target must not report success
448 if rt_retire("sites/nishifamily/factory/definitely-not-here-xyz" as *u8) != 9 { fails = fails + 1 }
449 // and an absent STAGED artifact must also refuse (the exception must not fake a success)
450 if rt_retire("nx_definitely_not_staged_xyz.elf.new" as *u8) != 9 { fails = fails + 1 }
451 // ---- THE REFUSAL MUST NAME THE RULE THAT FIRED (regression, 2026-08-15) -------------------
452 // The exact caller path that exposed the drift. It MUST refuse -- `nx_servedrift` contains `serve`.
453 if rt_refuse("buildroot/runtime/nx_servedrift.nx" as *u8) != 4 { fails = fails + 1 }
454 // ANTI-VACUITY: refusing is NOT enough. A guard that refuses correctly while naming the wrong rule
455 // passes every refuse-only test -- which is precisely how the 9-vs-11 drift survived. Assert the
456 // ORGAN NAMED THE TOKEN, not merely that it said no.
457 if rt_len(rt_hit) != 5 { fails = fails + 1 }
458 if rt_has(rt_hit, "serve" as *u8) != 1 { fails = fails + 1 }
459 // the OTHER token the stale message omitted -- proven reachable AND correctly named
460 if rt_refuse("sites/nishifamily/nx_daemon_notes.txt" as *u8) != 4 { fails = fails + 1 }
461 if rt_has(rt_hit, "daemon" as *u8) != 1 { fails = fails + 1 }
462 // neg-control-reason-naming: an ALLOWED path must still be allowed after all that token traffic,
463 // so the naming machinery cannot have turned the guard into one that refuses everything.
464 if rt_refuse("knowledge/status/uigen_tabprobe.txt" as *u8) != 0 { fails = fails + 1 }
465 // ---- THE DIRECTORY LANE (2026-08-15) -----------------------------------------------------
466 // REFUSE a bare top-level name: the commonest catastrophic typo, and the one the file rule's
467 // slash count was really protecting against.
468 if rt_refuse_dir("knowledge" as *u8, "knowledge" as *u8) != 6 { fails = fails + 1 }
469 // REFUSE a SERVED root and anything under it -- by PREFIX, so depth cannot buy a way in.
470 if rt_refuse_dir("sites/nishifamily" as *u8, "sites/nishifamily" as *u8) != 5 { fails = fails + 1 }
471 if rt_refuse_dir("sites/nishifamily/world" as *u8, "sites/nishifamily/world" as *u8) != 5 { fails = fails + 1 }
472 // REFUSE traversal, which is never exempted in either lane
473 if rt_refuse_dir("knowledge/../../etc" as *u8, "knowledge/../../etc" as *u8) != 2 { fails = fails + 1 }
474 // REFUSE protected material -- the full token table still applies to directories
475 if rt_refuse_dir("knowledge/opaque_store" as *u8, "knowledge/opaque_store" as *u8) != 4 { fails = fails + 1 }
476 if rt_refuse_dir("knowledge/daemon_logs" as *u8, "knowledge/daemon_logs" as *u8) != 4 { fails = fails + 1 }
477 // ★THE TWO-NAME CONFIRMATION IS THE POINT: the SAME path that is allowed with a matching
478 // confirmation must be REFUSED when the confirmation differs by one character. Without this
479 // tooth the second argument would be decoration.
480 if rt_refuse_dir("knowledge/gfxmirror" as *u8, "knowledge/gfxmirro" as *u8) != 7 { fails = fails + 1 }
481 if rt_refuse_dir("knowledge/gfxmirror" as *u8, "knowledge/gfxmirrorx" as *u8) != 7 { fails = fails + 1 }
482 // neg-control-dir-allow: a guard that refuses everything passes every refusal tooth above, so a
483 // legitimate orphan directory with a MATCHING confirmation must be ALLOWED.
484 if rt_refuse_dir("knowledge/gfxmirror" as *u8, "knowledge/gfxmirror" as *u8) != 0 { fails = fails + 1 }
485 // an absent directory must not report success
486 if rt_retiredir("knowledge/definitely-not-here-xyz" as *u8, "knowledge/definitely-not-here-xyz" as *u8) != 9 { fails = fails + 1 }
487 // NO UNREACHABLE ROWS: every table entry must be a real token, so the table can never grow a row
488 // the predicate cannot fire on or the message cannot name.
489 var ti: i64 = 0
490 while ti < rt_deny_n() {
491 if rt_len(rt_deny_tok(ti)) == 0 { fails = fails + 1 }
492 ti = ti + 1
493 }
494 rt_say("NX-RETIRE-PATH selftest fails=" as *u8); rt_num(fails)
495 // ★THE VERDICT LINE DOES NOT RECITE COUNTS. The old one hand-maintained "12 protected paths / 4
496 // retirable / 2 absent" beside the teeth -- the SAME duplicate-ruler defect this session came here
497 // to fix, one line below the fix. Adding a tooth silently made it lie.
498 if fails == 0 { rt_say(" verdict=GREEN (all refusal, allow, staged-exception and reason-naming teeth passed)\n" as *u8); sys_exit(0); return 0 }
499 rt_say(" verdict=RED\n" as *u8)
500 sys_exit(1)
501 return 1
502 }
503 // EXACT verb match, never substring: `retire` is a prefix of `retiredir`, so a substring test would
504 // route every retiredir call into the file lane and refuse it for the wrong reason.
505 if rt_same(verb, "retiredir" as *u8) == 1 {
506 if argc < 4 {
507 rt_say("usage: nx_retire_path retiredir <path> <same-path-again>\n" as *u8)
508 rt_say(" the path is required TWICE on purpose -- one name is never enough to move a whole tree\n" as *u8)
509 sys_exit(2)
510 return 2
511 }
512 let drc: i64 = rt_retiredir(argv[2] as *u8, argv[3] as *u8)
513 sys_exit(drc)
514 return drc
515 }
516 if argc < 3 {
517 rt_say("usage: nx_retire_path {retire <path> | retiredir <path> <path> | selftest}\n" as *u8)
518 sys_exit(2)
519 return 2
520 }
521 let rc: i64 = rt_retire(argv[2] as *u8)
522 sys_exit(rc)
523 return rc
524}