code wiki / (root) / nx_lmexport.nx

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}