code wiki / _hdl_build / nx_dedupe_source_lib.nx

nx_dedupe_source_lib.nx source

↩ module page · 163 lines · 8615 B

1// nx_dedupe_source_lib.nx -- the RESOLUTION half of the seq207/seq169 dual-copy source hazard. 2// The mgmt build guard (ma_build_dup_divergent) DETECTS when <t>.nx exists in BOTH buildroot/runtime/ 3// and buildroot/runtime/_hdl_build/ with DIFFERENT bytes and REFUSES the build (a divergent runtime twin 4// silently regresses its consumer once a build resolves the _hdl_build SSOT). Until now there was NO 5// sovereign FIX -- only ssh. This is it, ecosystem-wide + reusable for the whole class. 6// ACTION (rule-13 SOFT-DELETE, never a hard rm): the _hdl_build tree is the build SSOT, so the stale 7// runtime/ shadow is RENAMED out of the build path to <t>.nx.dupe-reconciled (bytes preserved = rollback). 8// After: only ONE <t>.nx remains in the source tree -> the guard's missing-either branch passes -> the 9// canonical (fixed) source builds -> no consumer regresses. Server-side atomic sys_renameat; fail-safe: 10// refuse if the canonical SSOT is absent (never reconcile toward a missing truth); benign no-op if there 11// is no shadow (already single-copy); VERIFY the shadow is gone + the backup exists after. 12// license_tier: ORIGINAL genealogy: seq207 class; pairs with nx_mgmt_api ma_build_dup_divergent 13import "nx_syscalls.nx" 14const K_MAGIC_65536: i64 = 65536 15const K_MAGIC_1024: i64 = 1024 16 17func ds_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; var p: i64=o; while s[i]!=(0 as u8){d[p]=s[i]; p=p+1; i=i+1} d[p]=0 as u8; return p } 18func ds_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19func ds_w_num(v: i64) -> i64 { let b: *u8 = sys_mmap(28); let n: i64 = ds_catn(b, 0, v); sys_write(1, b, n); return 0 } 20func ds_catn(d: *u8, o: i64, v: i64) -> i64 { 21 if v < 0 { d[o] = 45 as u8; return ds_catn(d, o + 1, 0 - v) } 22 // NEGATIVES (2026-08-07). Without this the `while m > 0` loop below never runs for a 23 // negative value and this function emits ZERO CHARACTERS, silently corrupting whatever 24 // format it is writing into. Handled AT THE SIGNATURE so it is independent of which 25 // cursor variable the body happens to use. Non-negative input is byte-identical (rule 19). 26 if v < 0 { d[o] = 45 as u8; return ds_catn(d, o + 1, 0 - v) } 27 let t: *u8 = sys_mmap(28); var m: i64=v; var p: i64=o; if m<0 { d[p]=45 as u8; p=p+1; m=0-m } 28 var k: i64=0; if m==0 { t[0]=48 as u8; k=1 } while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 29 var i: i64=0; while i<k { d[p]=t[k-1-i]; p=p+1; i=i+1 } d[p]=0 as u8; return p 30} 31func ds_namec(c: i64) -> i64 { 32 if c>=97 { if c<=122 { return 1 } } 33 if c>=65 { if c<=90 { return 1 } } 34 if c>=48 { if c<=57 { return 1 } } 35 if c==95 { return 1 } 36 return 0 37} 38// sanitize a target basename to [a-zA-Z0-9_] into out (<=cap); returns length or -1 if empty/overflow/bad char 39func ds_sanitize(src: *u8, out: *u8, cap: i64) -> i64 { 40 var i: i64=0 41 while src[i]!=(0 as u8) { 42 if i>=cap { return 0-1 } 43 let c: i64 = src[i] as i64 44 if ds_namec(c)==0 { return 0-1 } 45 out[i]=src[i] 46 i=i+1 47 } 48 if i==0 { return 0-1 } 49 out[i]=0 as u8 50 return i 51} 52func ds_exists(path: *u8) -> i64 { 53 let fd: i64 = sys_openat_rd(path) 54 if fd<0 { return 0 } 55 sys_close(fd) 56 return 1 57} 58func ds_filesize(path: *u8) -> i64 { 59 let fd: i64 = sys_openat_rd(path) 60 if fd<0 { return 0-1 } 61 let buf: *u8 = sys_mmap(K_MAGIC_65536) 62 var total: i64=0 63 var nrd: i64=sys_read(fd, buf, K_MAGIC_65536) 64 while nrd>0 { total=total+nrd; nrd=sys_read(fd, buf, K_MAGIC_65536) } 65 sys_close(fd) 66 return total 67} 68 69// reconcile: canon = the SSOT source, shadow = the stale twin to soft-delete. bakout receives the backup path. 70// returns: 0 reconciled | 2 canon-missing(REFUSE) | 10 no-shadow(benign single-copy) | 3 rename-failed | 4 verify-failed 71func ds_reconcile(canon: *u8, shadow: *u8, bakout: *u8) -> i64 { 72 if ds_exists(canon)==0 { return 2 } 73 if ds_exists(shadow)==0 { return 10 } 74 var b: i64 = ds_cat(bakout, 0, shadow) 75 b = ds_cat(bakout, b, ".dupe-reconciled" as *u8) 76 let rc: i64 = sys_renameat(shadow, bakout) 77 if rc<0 { return 3 } 78 if ds_exists(shadow)==1 { return 4 } 79 if ds_exists(bakout)==0 { return 4 } 80 return 0 81} 82 83// ★SCOPE FIX (F1124). The DETECTOR (nx_dup_source_check) discovers ~13 source trees; this RECONCILER 84// knew only TWO, so every hazard living in bin/, _retired/ or a nested runtime/runtime/ reported 85// dual_copy:0 and could never be cleared -- the board stayed RED no matter how much work was done. 86// A detector whose scope exceeds its fixer's leaves permanent red. Now the tree PAIR is a parameter: 87// pass any canonical/shadow directory the detector named, and the same proven soft-delete+backup 88// applies. Passing nullptr keeps the historical defaults, so every existing caller is unchanged. 89func ds_resolve_pair(nm: *u8, canon_dir: *u8, shadow_dir: *u8, emit: i64) -> i64 { 90 let canon: *u8 = sys_mmap(512) 91 var c: i64 = 0 92 if canon_dir == (0 as *u8) { c = ds_cat(canon, 0, "buildroot/runtime/_hdl_build/" as *u8) } else { c = ds_cat(canon, 0, canon_dir) } 93 c = ds_cat(canon, c, nm); c = ds_cat(canon, c, ".nx" as *u8) 94 let shadow: *u8 = sys_mmap(512) 95 var s: i64 = 0 96 if shadow_dir == (0 as *u8) { s = ds_cat(shadow, 0, "buildroot/runtime/" as *u8) } else { s = ds_cat(shadow, 0, shadow_dir) } 97 s = ds_cat(shadow, s, nm); s = ds_cat(shadow, s, ".nx" as *u8) 98 let bak: *u8 = sys_mmap(512) 99 let csz: i64 = ds_filesize(canon) 100 let code: i64 = ds_reconcile(canon, shadow, bak) 101 if emit==1 { 102 let o: *u8 = sys_mmap(K_MAGIC_1024) 103 var p: i64 = ds_cat(o, 0, "{\"organ\":\"nx_dedupe_source\",\"target\":\"" as *u8) 104 p = ds_cat(o, p, nm) 105 p = ds_cat(o, p, "\",\"canonical_bytes\":" as *u8) 106 p = ds_catn(o, p, csz) 107 p = ds_cat(o, p, ",\"code\":" as *u8) 108 p = ds_catn(o, p, code) 109 p = ds_cat(o, p, ",\"action\":\"" as *u8) 110 if code==0 { p = ds_cat(o, p, "RECONCILED-shadow-soft-deleted-to-.dupe-reconciled" as *u8) } else { if code==10 { p = ds_cat(o, p, "NO-OP-already-single-copy" as *u8) } else { if code==2 { p = ds_cat(o, p, "REFUSED-canonical-SSOT-missing" as *u8) } else { if code==3 { p = ds_cat(o, p, "FAIL-rename-errno" as *u8) } else { p = ds_cat(o, p, "FAIL-post-verify" as *u8) } } } } 111 if code==0 { p = ds_cat(o, p, "\",\"backup\":\"" as *u8); p = ds_cat(o, p, bak) } 112 p = ds_cat(o, p, "\",\"canonical_dir\":\"" as *u8) 113 if canon_dir == (0 as *u8) { p = ds_cat(o, p, "buildroot/runtime/_hdl_build/" as *u8) } else { p = ds_cat(o, p, canon_dir) } 114 p = ds_cat(o, p, "\",\"shadow_dir\":\"" as *u8) 115 if shadow_dir == (0 as *u8) { p = ds_cat(o, p, "buildroot/runtime/" as *u8) } else { p = ds_cat(o, p, shadow_dir) } 116 p = ds_cat(o, p, "\",\"env\":\"canonical=SSOT;soft-delete-rename;rule-13-rollback;server-side-atomic;refuse-if-no-canonical\"}\ 117" as *u8) 118 sys_write(1, o, p) 119 } 120 return code 121} 122 123// Historical entry point: the default _hdl_build <- runtime pair. Kept byte-compatible so every 124// existing caller and the KAT behave exactly as before (rule 19: add, never break). 125func ds_resolve_target(nm: *u8, emit: i64) -> i64 { 126 return ds_resolve_pair(nm, 0 as *u8, 0 as *u8, emit) 127} 128 129// hermetic selftest on CWD kat files (no buildroot touched). 0 = GREEN. 130func ds_write_kat(path: *u8, body: *u8) -> i64 { 131 let fd: i64 = sys_openat_wr(path, 420) 132 if fd<0 { return 0-1 } 133 var n: i64=0; while body[n]!=(0 as u8){n=n+1} 134 sys_write(fd, body, n) 135 sys_close(fd) 136 return 0 137} 138func ds_selftest() -> i64 { 139 var f: i64=0 140 let bak: *u8 = sys_mmap(512) 141 ds_write_kat("dsktcanon.nx" as *u8, "CANON with the fix\ 142" as *u8) 143 ds_write_kat("dsktshadow.nx" as *u8, "SHADOW stale different bytes\ 144" as *u8) 145 let r1: i64 = ds_reconcile("dsktcanon.nx" as *u8, "dsktshadow.nx" as *u8, bak) 146 if r1!=0 { f=f+1 } 147 if ds_exists("dsktshadow.nx" as *u8)!=0 { f=f+1 } 148 if ds_exists(bak)!=1 { f=f+1 } 149 let r2: i64 = ds_reconcile("dsktcanon.nx" as *u8, "dsktshadow.nx" as *u8, bak) 150 if r2!=10 { f=f+1 } 151 let bak2: *u8 = sys_mmap(512) 152 let r3: i64 = ds_reconcile("dsktcanon_missing.nx" as *u8, "dsktshadow.nx" as *u8, bak2) 153 if r3!=2 { f=f+1 } 154 ds_write_kat("dsktident_c.nx" as *u8, "same bytes\ 155" as *u8) 156 ds_write_kat("dsktident_s.nx" as *u8, "same bytes\ 157" as *u8) 158 let r4: i64 = ds_reconcile("dsktident_c.nx" as *u8, "dsktident_s.nx" as *u8, bak2) 159 if r4!=0 { f=f+1 } 160 if ds_exists("dsktident_s.nx" as *u8)!=0 { f=f+1 } 161 if ds_exists("dsktident_c.nx" as *u8)!=1 { f=f+1 } 162 return f 163}