code wiki / _hdl_build / nx_dedupe_source_lib.nx

nx_dedupe_source_lib.nx source

↩ module page · 157 lines · 8116 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 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 } 22 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 } 23 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 24} 25func ds_namec(c: i64) -> i64 { 26 if c>=97 { if c<=122 { return 1 } } 27 if c>=65 { if c<=90 { return 1 } } 28 if c>=48 { if c<=57 { return 1 } } 29 if c==95 { return 1 } 30 return 0 31} 32// sanitize a target basename to [a-zA-Z0-9_] into out (<=cap); returns length or -1 if empty/overflow/bad char 33func ds_sanitize(src: *u8, out: *u8, cap: i64) -> i64 { 34 var i: i64=0 35 while src[i]!=(0 as u8) { 36 if i>=cap { return 0-1 } 37 let c: i64 = src[i] as i64 38 if ds_namec(c)==0 { return 0-1 } 39 out[i]=src[i] 40 i=i+1 41 } 42 if i==0 { return 0-1 } 43 out[i]=0 as u8 44 return i 45} 46func ds_exists(path: *u8) -> i64 { 47 let fd: i64 = sys_openat_rd(path) 48 if fd<0 { return 0 } 49 sys_close(fd) 50 return 1 51} 52func ds_filesize(path: *u8) -> i64 { 53 let fd: i64 = sys_openat_rd(path) 54 if fd<0 { return 0-1 } 55 let buf: *u8 = sys_mmap(K_MAGIC_65536) 56 var total: i64=0 57 var nrd: i64=sys_read(fd, buf, K_MAGIC_65536) 58 while nrd>0 { total=total+nrd; nrd=sys_read(fd, buf, K_MAGIC_65536) } 59 sys_close(fd) 60 return total 61} 62 63// reconcile: canon = the SSOT source, shadow = the stale twin to soft-delete. bakout receives the backup path. 64// returns: 0 reconciled | 2 canon-missing(REFUSE) | 10 no-shadow(benign single-copy) | 3 rename-failed | 4 verify-failed 65func ds_reconcile(canon: *u8, shadow: *u8, bakout: *u8) -> i64 { 66 if ds_exists(canon)==0 { return 2 } 67 if ds_exists(shadow)==0 { return 10 } 68 var b: i64 = ds_cat(bakout, 0, shadow) 69 b = ds_cat(bakout, b, ".dupe-reconciled" as *u8) 70 let rc: i64 = sys_renameat(shadow, bakout) 71 if rc<0 { return 3 } 72 if ds_exists(shadow)==1 { return 4 } 73 if ds_exists(bakout)==0 { return 4 } 74 return 0 75} 76 77// ★SCOPE FIX (F1124). The DETECTOR (nx_dup_source_check) discovers ~13 source trees; this RECONCILER 78// knew only TWO, so every hazard living in bin/, _retired/ or a nested runtime/runtime/ reported 79// dual_copy:0 and could never be cleared -- the board stayed RED no matter how much work was done. 80// A detector whose scope exceeds its fixer's leaves permanent red. Now the tree PAIR is a parameter: 81// pass any canonical/shadow directory the detector named, and the same proven soft-delete+backup 82// applies. Passing nullptr keeps the historical defaults, so every existing caller is unchanged. 83func ds_resolve_pair(nm: *u8, canon_dir: *u8, shadow_dir: *u8, emit: i64) -> i64 { 84 let canon: *u8 = sys_mmap(512) 85 var c: i64 = 0 86 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) } 87 c = ds_cat(canon, c, nm); c = ds_cat(canon, c, ".nx" as *u8) 88 let shadow: *u8 = sys_mmap(512) 89 var s: i64 = 0 90 if shadow_dir == (0 as *u8) { s = ds_cat(shadow, 0, "buildroot/runtime/" as *u8) } else { s = ds_cat(shadow, 0, shadow_dir) } 91 s = ds_cat(shadow, s, nm); s = ds_cat(shadow, s, ".nx" as *u8) 92 let bak: *u8 = sys_mmap(512) 93 let csz: i64 = ds_filesize(canon) 94 let code: i64 = ds_reconcile(canon, shadow, bak) 95 if emit==1 { 96 let o: *u8 = sys_mmap(K_MAGIC_1024) 97 var p: i64 = ds_cat(o, 0, "{\"organ\":\"nx_dedupe_source\",\"target\":\"" as *u8) 98 p = ds_cat(o, p, nm) 99 p = ds_cat(o, p, "\",\"canonical_bytes\":" as *u8) 100 p = ds_catn(o, p, csz) 101 p = ds_cat(o, p, ",\"code\":" as *u8) 102 p = ds_catn(o, p, code) 103 p = ds_cat(o, p, ",\"action\":\"" as *u8) 104 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) } } } } 105 if code==0 { p = ds_cat(o, p, "\",\"backup\":\"" as *u8); p = ds_cat(o, p, bak) } 106 p = ds_cat(o, p, "\",\"canonical_dir\":\"" as *u8) 107 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) } 108 p = ds_cat(o, p, "\",\"shadow_dir\":\"" as *u8) 109 if shadow_dir == (0 as *u8) { p = ds_cat(o, p, "buildroot/runtime/" as *u8) } else { p = ds_cat(o, p, shadow_dir) } 110 p = ds_cat(o, p, "\",\"env\":\"canonical=SSOT;soft-delete-rename;rule-13-rollback;server-side-atomic;refuse-if-no-canonical\"}\ 111" as *u8) 112 sys_write(1, o, p) 113 } 114 return code 115} 116 117// Historical entry point: the default _hdl_build <- runtime pair. Kept byte-compatible so every 118// existing caller and the KAT behave exactly as before (rule 19: add, never break). 119func ds_resolve_target(nm: *u8, emit: i64) -> i64 { 120 return ds_resolve_pair(nm, 0 as *u8, 0 as *u8, emit) 121} 122 123// hermetic selftest on CWD kat files (no buildroot touched). 0 = GREEN. 124func ds_write_kat(path: *u8, body: *u8) -> i64 { 125 let fd: i64 = sys_openat_wr(path, 420) 126 if fd<0 { return 0-1 } 127 var n: i64=0; while body[n]!=(0 as u8){n=n+1} 128 sys_write(fd, body, n) 129 sys_close(fd) 130 return 0 131} 132func ds_selftest() -> i64 { 133 var f: i64=0 134 let bak: *u8 = sys_mmap(512) 135 ds_write_kat("dsktcanon.nx" as *u8, "CANON with the fix\ 136" as *u8) 137 ds_write_kat("dsktshadow.nx" as *u8, "SHADOW stale different bytes\ 138" as *u8) 139 let r1: i64 = ds_reconcile("dsktcanon.nx" as *u8, "dsktshadow.nx" as *u8, bak) 140 if r1!=0 { f=f+1 } 141 if ds_exists("dsktshadow.nx" as *u8)!=0 { f=f+1 } 142 if ds_exists(bak)!=1 { f=f+1 } 143 let r2: i64 = ds_reconcile("dsktcanon.nx" as *u8, "dsktshadow.nx" as *u8, bak) 144 if r2!=10 { f=f+1 } 145 let bak2: *u8 = sys_mmap(512) 146 let r3: i64 = ds_reconcile("dsktcanon_missing.nx" as *u8, "dsktshadow.nx" as *u8, bak2) 147 if r3!=2 { f=f+1 } 148 ds_write_kat("dsktident_c.nx" as *u8, "same bytes\ 149" as *u8) 150 ds_write_kat("dsktident_s.nx" as *u8, "same bytes\ 151" as *u8) 152 let r4: i64 = ds_reconcile("dsktident_c.nx" as *u8, "dsktident_s.nx" as *u8, bak2) 153 if r4!=0 { f=f+1 } 154 if ds_exists("dsktident_s.nx" as *u8)!=0 { f=f+1 } 155 if ds_exists("dsktident_c.nx" as *u8)!=1 { f=f+1 } 156 return f 157}