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}