nx_lmexport.nx source
↩ module page · 139 lines · 8738 B
1// nx_lmexport.nx -- SOVEREIGN replacement for nx_landmine_export.cron.sh (2026-08-07).
2//
3// OPERATOR DOCTRINE: .sh is not the Nishi ecosystem. But this wrapper is NOT a pure swap -- I said it was,
4// from reading its FIRST LINE, and filed that as guidance (retracted in debt 1786114148). It carries THREE
5// safety properties, and a bare organ row would have silently deleted all three:
6// (1) FAIL-CLOSED ON EMPTY -- an empty plane load is REFUSED, never exported (the vacuous-verify law).
7// (2) SHRINK GUARD -- a GENERATED VIEW that loses a third of its rows is a CLOBBER SIGNAL, not
8// an export. Refuse when new < cur*2/3.
9// (3) ATOMIC REPLACE -- assemble the whole file in a temp, then rename over the target, so a
10// reader never observes a half-written registry.
11// (STAR)A WRAPPER THAT LOOKS LIKE THE LAST WRAPPER IS NOT THE SAME WRAPPER -- ENUMERATE ITS SERVICES EVERY
12// TIME. The argument-plus-redirection shape that made fallbackharden and ddqbeat pure swaps is exactly what
13// made this one LOOK swappable; the resemblance is the hazard.
14//
15// FAITHFULNESS NOTE ON THE SHRINK TEST. The shell computed $((CUR * 2 / 3)) -- INTEGER division, which
16// TRUNCATES -- and refused when NEW was strictly less than that. The obvious "cleaner" rewrite (new*3 <
17// cur*2) is NOT the same predicate: at cur=10 the shell threshold is 6 and admits new=6, while the cross-
18// multiplied form rejects it. A guard must be ported EXACTLY, not improved in passing, so the truncating
19// form is reproduced verbatim.
20// (STAR)PORTING A GUARD IS NOT REFACTORING IT -- AN OFF-BY-ONE IN A SAFETY PREDICATE IS A SAFETY CHANGE.
21//
22// nx_lmexport (no args; the clock dispatches it bare)
23// Exit: 0 OK · 2 empty-load REFUSED · 3 shrink REFUSED · 1 internal failure.
24// Output goes to stdout, which the clock's per-job capture routes to logs/lmexport.log automatically.
25// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
26import "nx_syscalls.nx"
27
28const LM_PLANE: *u8 = "knowledge/store/landmine-"
29const LM_CLI: *u8 = "/volume1/homes/elderwesto/nishihost/nx_store_put.elf"
30const LM_TMP: *u8 = "/tmp/nx_lmexport.tmp"
31// TEMP LIVES BESIDE THE TARGET, NOT IN /tmp (2026-08-07, MEASURED: the first live run returned
32// `lm-export FAIL rename (target untouched)` because /tmp and the NAS home are DIFFERENT FILESYSTEMS and
33// rename(2) returns EXDEV across devices).
34// THIS EXPOSED A LATENT DEFECT IN THE SHELL VERSION IT REPLACES: `mv /tmp/lm_export.full <target>` LOOKS
35// atomic, but mv SILENTLY FALLS BACK TO COPY+UNLINK on EXDEV -- so the .sh's "atomic replace" was atomic
36// only while /tmp happened to share a filesystem with the registry, and degraded to a NON-ATOMIC
37// copy-then-delete otherwise, with a window where a reader sees a partial file.
38// (STAR)`mv` IS NOT `rename` -- A TOOL THAT SILENTLY DEGRADES ACROSS A BOUNDARY HIDES THE VERY PROPERTY
39// YOU WERE RELYING ON, AND THE PORT THAT REFUSES IS THE ONE THAT TELLS YOU.
40// Writing the temp in the target's own directory makes the rename same-device and therefore GENUINELY
41// atomic -- strictly stronger than the script it replaces. Dotfile so a directory listing stays clean.
42//
43// (STAR)THE SUBJECT OF THAT PARAGRAPH IS LM_FULL BELOW, NOT LM_TMP ABOVE, AND THE PLACEMENT SAID
44// OTHERWISE. THERE ARE TWO TEMPS AND THEY HAVE DIFFERENT JOBS:
45// LM_TMP = /tmp/... -- captures the CLI's STDOUT and is READ BACK. Never renamed, so /tmp is right.
46// LM_FULL = beside the target -- the file RENAMED OVER the registry. Must be same-device (EXDEV).
47// Read in file order the EXDEV warning appears to indict LM_TMP, and it cost a real investigation before
48// the two subjects separated. (STAR)A COMMENT BETWEEN TWO DECLARATIONS IS READ AS BELONGING TO THE ONE
49// ABOVE IT -- NAME THE SUBJECT WHENEVER A NEIGHBOUR COULD PLAUSIBLY CLAIM THE WARNING.
50const LM_FULL: *u8 = "knowledge/registry/.lmexport.tmp"
51const LM_TARGET: *u8 = "knowledge/registry/landmines.tsv"
52const LM_MODE: i64 = 0x1a4
53const LM_CAP: i64 = 4194304
54const LM_H1: *u8 = "# GENERATED VIEW (F871 2026-07-20) -- SSOT is the knowledge/store/landmine- seg-store plane. DO NOT hand-edit:\\n"
55const LM_H2: *u8 = "# hand edits are OVERWRITTEN by nx_lmexport.elf (clock beat). Add/retire landmines via:\\n"
56const LM_H3: *u8 = "# ./nx_store_put.elf knowledge/store/landmine- put <actor> <LM-id> <category> <text> (replaces by id, hist- provenanced)\\n"
57
58func lm_o(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
59func lm_cat(d: *u8, o: i64, s: *u8) -> i64 { var x: i64=o; var i: i64=0; while s[i]!=(0 as u8){d[x]=s[i];x=x+1;i=i+1} return x }
60func lm_num(d: *u8, o: i64, v: i64) -> i64 { var x: i64=o; var mm: i64=v; if mm<0{d[x]=45 as u8;x=x+1;mm=0-mm} if mm==0{d[x]=48 as u8;return x+1} let t:*u8=sys_mmap(24); var k:i64=0; while mm>0{t[k]=(48+(mm%10)) as u8;mm=mm/10;k=k+1} var j:i64=0; while j<k{d[x]=t[k-1-j];x=x+1;j=j+1} return x }
61// TELEMETRY IS READ BY HUMANS AND MINERS, SO IT MUST PARSE ON SIGHT (2026-08-07). v1 emitted
62// `lm-export OK rows= prev= 31 31` because it appended BOTH numbers after BOTH labels -- which reads as
63// two EMPTY fields followed by two bare integers, i.e. exactly like a bug. In an organ whose own purpose
64// is that "the trend IS the telemetry", a log line that looks broken is a defect in the product.
65// (STAR)A LABEL SEPARATED FROM ITS VALUE IS WORSE THAN NO LABEL -- IT READS AS A MISSING VALUE.
66func lm_kv(m: *u8, o: i64, k: *u8, v: i64) -> i64 { var x: i64 = lm_cat(m, o, k); x = lm_num(m, x, v); return x }
67func lm_say(tag: *u8, a: i64, b: i64) -> i64 {
68 let m: *u8 = sys_mmap(256); var o: i64 = 0
69 o = lm_cat(m, o, tag)
70 o = lm_kv(m, o, " rows=" as *u8, a)
71 o = lm_kv(m, o, " prev=" as *u8, b)
72 m[o]=10 as u8; o=o+1; m[o]=0 as u8
73 sys_write(1, m, o); return 0
74}
75
76func main() -> i64 {
77 // ---- 1. load the plane through the SAME CLI the shell used, stdout captured to a temp ----
78 let pid: i64 = sys_fork()
79 if pid == 0 {
80 let ofd: i64 = sys_openat_wr(LM_TMP, LM_MODE)
81 if ofd >= 0 { sys_dup3(ofd, 1, 0) }
82 let av: *i64 = sys_mmap(64) as *i64
83 av[0] = LM_CLI as i64; av[1] = LM_PLANE as i64; av[2] = "load" as *u8 as i64; av[3] = 0
84 let ev: *i64 = sys_mmap(32) as *i64
85 ev[0] = "PATH=/usr/bin:/bin" as *u8 as i64; ev[1] = 0
86 sys_execve(LM_CLI, av, ev)
87 sys_exit(127)
88 }
89 if pid < 0 { lm_o("lm-export FAIL fork\\n" as *u8); sys_exit(1); return 1 }
90 let st: *i64 = sys_mmap(16) as *i64
91 sys_wait4(pid, st, 0)
92
93 // ---- 2. GUARD 1: fail-closed on an empty load ----
94 let lp: *i64 = sys_mmap(8) as *i64
95 let data: *u8 = sys_read_file(LM_TMP, lp)
96 if (data as i64) == 0 { lm_o("lm-export REFUSED: empty plane load\\n" as *u8); sys_exit(2); return 2 }
97 let dn: i64 = lp[0]
98 if dn <= 0 { lm_o("lm-export REFUSED: empty plane load\\n" as *u8); sys_exit(2); return 2 }
99
100 // count payload rows the way `wc -l` does: one per newline
101 var nrows: i64 = 0
102 var i: i64 = 0
103 while i < dn { if data[i] == (10 as u8) { nrows = nrows + 1 } i = i + 1 }
104 if nrows <= 0 { lm_o("lm-export REFUSED: empty plane load\\n" as *u8); sys_exit(2); return 2 }
105
106 // ---- 3. GUARD 2: shrink. `grep -vc "^#"` = lines NOT starting with '#'. ----
107 let cp: *i64 = sys_mmap(8) as *i64
108 let cur: *u8 = sys_read_file(LM_TARGET, cp)
109 var ncur: i64 = 0
110 if (cur as i64) != 0 {
111 let cn: i64 = cp[0]
112 var j: i64 = 0
113 var at_bol: i64 = 1
114 while j < cn {
115 if at_bol == 1 { if cur[j] != (35 as u8) { ncur = ncur + 1 } }
116 if cur[j] == (10 as u8) { at_bol = 1 } else { at_bol = 0 }
117 j = j + 1
118 }
119 }
120 // EXACTLY the shell predicate: threshold = (cur*2)/3 with integer truncation; refuse if new < threshold.
121 let thresh: i64 = (ncur * 2) / 3
122 if nrows < thresh { lm_say("lm-export REFUSED shrink" as *u8, nrows, ncur); sys_exit(3); return 3 }
123
124 // ---- 4. GUARD 3: assemble in full, then ATOMIC rename over the target ----
125 let out: *u8 = sys_mmap(LM_CAP)
126 var o: i64 = 0
127 o = lm_cat(out, o, LM_H1)
128 o = lm_cat(out, o, LM_H2)
129 o = lm_cat(out, o, LM_H3)
130 var k: i64 = 0
131 while k < dn { out[o] = data[k]; o = o + 1; k = k + 1 }
132 let fd: i64 = sys_openat_wr(LM_FULL, LM_MODE)
133 if fd < 0 { lm_o("lm-export FAIL cannot open temp\\n" as *u8); sys_exit(1); return 1 }
134 sys_write(fd, out, o)
135 sys_close(fd)
136 if sys_renameat(LM_FULL, LM_TARGET) != 0 { lm_o("lm-export FAIL rename (target untouched)\\n" as *u8); sys_exit(1); return 1 }
137 lm_say("lm-export OK" as *u8, nrows, ncur)
138 sys_exit(0)
139 return 0
140}