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}