code wiki / _hdl_build / nx_coedit_gate.nx
nx_coedit_gate.nx source
↩ module page · 201 lines · 11928 B
1// nx_coedit_gate.nx -- gate for the CO-EDITING KERNEL (RGA text CRDT). The rows ARE the CRDT laws:
2// 1 type+apply a session of position-based edits materializes exactly
3// 2 CONVERGENCE merge(A,B) and merge(B,A) apply to the SAME bytes (pinned): concurrent append after
4// the same char ordered by the (counter,replica) tie-break; a concurrent delete lands
5// 3 tombstone-anchor inserting AFTER a concurrently-DELETED char still places correctly (RGA property)
6// 4 idempotent-remerge merge(AB, A) == AB (byte-equal log; re-applying ops is a no-op)
7// 5 edit-del position-based delete produces the pinned text
8// 6 conflict-loud same op id with DIFFERENT content = LOUD protocol violation (never silent)
9// 7 badref-loud insert after an unknown id = LOUD
10// license_tier: ORIGINAL
11import "nx_syscalls.nx"
12import "nx_gate_verdict.nx"
13
14func eg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8) { n=n+1 } return n }
15func eg_p(s: *u8) -> i64 { sys_write(1, s, eg_slen(s)); return 0 }
16func eg_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { dst[off+i]=s[i]; i=i+1 } return off+i }
17func eg_catn(dst: *u8, off: i64, v: i64) -> i64 {
18 var o: i64=off; var m: i64=v; if m<0 { dst[o]=45 as u8; o=o+1; m=0-m }
19 let t: *u8=sys_mmap(28); var k: i64=0; if m==0 { t[0]=48 as u8; k=1 }
20 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
21 var i: i64=0; while i<k { dst[o+i]=t[k-1-i]; i=i+1 } return o+k
22}
23func eg_write(path: *u8, content: *u8) -> i64 {
24 let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 { return 0-1 }
25 sys_write(fd, content, eg_slen(content)); sys_close(fd); return 0
26}
27func eg_readall(path: *u8, szout: *i64) -> *u8 {
28 let fd: i64=sys_openat_rd(path); if fd<0 { szout[0]=0-1; return 0 as *u8 }
29 let buf: *u8=sys_mmap(1048576); var got: i64=0; var n: i64=1
30 while n>0 { n=sys_read(fd,(buf as i64+got) as *u8,65536); if n>0 { got=got+n } }
31 sys_close(fd); szout[0]=got; return buf
32}
33func eg_runv(elf: *u8, args: *i64, outpath: *u8) -> i64 {
34 let pid: i64=sys_fork()
35 if pid==0 {
36 if (outpath as i64)!=0 { let ofd: i64=sys_openat_wr(outpath,0x1a4); if ofd>=0 { sys_dup3(ofd,1,0); sys_dup3(ofd,2,0) } }
37 let argv: *i64=sys_mmap(128) as *i64
38 argv[0]=elf as i64; var i: i64=0; var go: i64=1
39 while go==1 { if args[i]==0 { go=0 } else { argv[i+1]=args[i]; i=i+1 } }
40 argv[i+1]=0
41 let envp: *i64=sys_mmap(16) as *i64; envp[0]=0
42 sys_execve(elf, argv, envp); sys_exit(127)
43 }
44 let st: *i64=sys_mmap(16) as *i64; sys_wait4(pid, st, 0)
45 let sig: i64=st[0]&0x7f; if sig!=0 { return 128+sig }
46 return (st[0]>>8)&0xff
47}
48func eg_has(path: *u8, needle: *u8) -> i64 {
49 let szp: *i64=sys_mmap(16) as *i64; let b: *u8=eg_readall(path, szp); let sz: i64=szp[0]
50 let n: i64=eg_slen(needle); if sz<n { return 0 }
51 var i: i64=0
52 while i+n<=sz { var ok: i64=1; var j: i64=0; while j<n { if b[i+j]!=needle[j] { ok=0; j=n } else { j=j+1 } } if ok==1 { return 1 } i=i+1 }
53 return 0
54}
55func eg_fileis(path: *u8, exact: *u8) -> i64 {
56 let szp: *i64=sys_mmap(16) as *i64; let b: *u8=eg_readall(path, szp)
57 let n: i64=eg_slen(exact)
58 if szp[0] != n { return 0 }
59 var i: i64=0
60 while i<n { if b[i]!=exact[i] { return 0 } i=i+1 }
61 return 1
62}
63func eg_fileeq(p1: *u8, p2: *u8) -> i64 {
64 let s1: *i64=sys_mmap(16) as *i64
65 let s2: *i64=sys_mmap(16) as *i64
66 let b1: *u8=eg_readall(p1, s1)
67 let b2: *u8=eg_readall(p2, s2)
68 if s1[0] != s2[0] { return 0 }
69 if s1[0]<0 { return 0 }
70 var i: i64=0
71 while i<s1[0] { if b1[i] != b2[i] { return 0 } i=i+1 }
72 return 1
73}
74func eg_args6(a1: *u8, a2: *u8, a3: *u8, a4: *u8, a5: *u8, a6: *u8) -> *i64 {
75 let a: *i64=sys_mmap(64) as *i64
76 a[0]=a1 as i64; a[1]=a2 as i64; a[2]=a3 as i64; a[3]=a4 as i64; a[4]=a5 as i64; a[5]=a6 as i64; a[6]=0
77 return a
78}
79func eg_row(name: *u8, pass: i64) -> i64 {
80 eg_p("ROW " as *u8); eg_p(name); if pass==1 { eg_p(" PASS\n" as *u8) } else { eg_p(" FAIL\n" as *u8) } return pass
81}
82// create/truncate to an EMPTY file. NEVER pass a "" literal (constant-pool ALIASING would write garbage).
83func eg_mkempty(path: *u8) -> i64 {
84 let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 { return 0-1 }
85 sys_close(fd)
86 return 0
87}
88func eg_cp(src: *u8, dst: *u8) -> i64 {
89 let szp: *i64=sys_mmap(16) as *i64
90 let b: *u8=eg_readall(src, szp)
91 if szp[0]<0 { return 0-1 }
92 let fd: i64=sys_openat_wr(dst, 0x1a4); if fd<0 { return 0-1 }
93 sys_write(fd, b, szp[0]); sys_close(fd); return 0
94}
95
96func main(argc: i64, argv: *i64) -> i64 {
97 eg_p("=== COEDIT GATE: RGA text CRDT -- convergence, tie-break, tombstone anchors, idempotent re-merge, LOUD conflicts ===\n" as *u8)
98 let ce: *u8="buildroot/_build/nx_coedit.sov.elf" as *u8
99 let pr: i64=sys_openat_rd(ce)
100 if pr>=0 { sys_close(pr) } else {
101 eg_p(" instrument missing -> rebuilding\n" as *u8)
102 eg_runv("_offc/nx_sov_build_run.elf" as *u8, eg_args6("nx_coedit" as *u8, "--build-only" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_rebuild.out" as *u8)
103 }
104
105 var pass: i64=0
106 var r: i64=0
107
108 // row 1: replica 1 types "Hi!" into a fresh log; apply materializes it exactly
109 eg_mkempty("/tmp/eg_l1" as *u8)
110 eg_runv(ce, eg_args6("edit" as *u8, "/tmp/eg_l1" as *u8, "1" as *u8, "0" as *u8, "ins" as *u8, "Hi!" as *u8), "/tmp/eg_r1a.txt" as *u8)
111 let rc1: i64=eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_l1" as *u8, "/tmp/eg_t1.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r1b.txt" as *u8)
112 r=0
113 if rc1==0 { if eg_fileis("/tmp/eg_t1.txt" as *u8, "Hi!" as *u8)==1 { r=1 } }
114 pass=pass+eg_row("type-apply" as *u8, r)
115
116 // shared base for rows 2-4: replica 1 types "abc" (ids 1:1 a, 1:2 b, 1:3 c)
117 eg_write("/tmp/eg_base" as *u8, "I 1 1 R a\nI 1 2 1:1 b\nI 1 3 1:2 c\n" as *u8)
118
119 // row 2: CONVERGENCE + tie-break. A(rep1) appends d after c (1:4); B(rep2) appends e after c (2:4) and
120 // deletes b (2:5 -> 1:2). RGA: children of c desc by (ctr,rep) -> 2:4 before 1:4 => "a c e d" minus b = "aced".
121 eg_cp("/tmp/eg_base" as *u8, "/tmp/eg_A" as *u8)
122 eg_cp("/tmp/eg_base" as *u8, "/tmp/eg_B" as *u8)
123 eg_runv(ce, eg_args6("ins" as *u8, "/tmp/eg_A" as *u8, "1" as *u8, "4" as *u8, "1:3" as *u8, "d" as *u8), "/tmp/eg_x.txt" as *u8)
124 eg_runv(ce, eg_args6("ins" as *u8, "/tmp/eg_B" as *u8, "2" as *u8, "4" as *u8, "1:3" as *u8, "e" as *u8), "/tmp/eg_x.txt" as *u8)
125 eg_runv(ce, eg_args6("del" as *u8, "/tmp/eg_B" as *u8, "2" as *u8, "5" as *u8, "1:2" as *u8, 0 as *u8), "/tmp/eg_x.txt" as *u8)
126 eg_runv(ce, eg_args6("merge" as *u8, "/tmp/eg_A" as *u8, "/tmp/eg_B" as *u8, "/tmp/eg_AB" as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r2a.txt" as *u8)
127 eg_runv(ce, eg_args6("merge" as *u8, "/tmp/eg_B" as *u8, "/tmp/eg_A" as *u8, "/tmp/eg_BA" as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r2b.txt" as *u8)
128 eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_AB" as *u8, "/tmp/eg_tAB.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r2c.txt" as *u8)
129 eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_BA" as *u8, "/tmp/eg_tBA.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r2d.txt" as *u8)
130 r=0
131 if eg_fileis("/tmp/eg_tAB.txt" as *u8, "aced" as *u8)==1 { if eg_fileeq("/tmp/eg_tAB.txt" as *u8, "/tmp/eg_tBA.txt" as *u8)==1 { r=1 } }
132 pass=pass+eg_row("convergence-tiebreak" as *u8, r)
133
134 // row 3: tombstone anchor -- A deletes c (1:4 -> 1:3); B inserts f AFTER c (2:4 -> after 1:3).
135 // Both orders: c is gone but still anchors f => "abf".
136 eg_cp("/tmp/eg_base" as *u8, "/tmp/eg_C" as *u8)
137 eg_cp("/tmp/eg_base" as *u8, "/tmp/eg_D" as *u8)
138 eg_runv(ce, eg_args6("del" as *u8, "/tmp/eg_C" as *u8, "1" as *u8, "4" as *u8, "1:3" as *u8, 0 as *u8), "/tmp/eg_x.txt" as *u8)
139 eg_runv(ce, eg_args6("ins" as *u8, "/tmp/eg_D" as *u8, "2" as *u8, "4" as *u8, "1:3" as *u8, "f" as *u8), "/tmp/eg_x.txt" as *u8)
140 eg_runv(ce, eg_args6("merge" as *u8, "/tmp/eg_C" as *u8, "/tmp/eg_D" as *u8, "/tmp/eg_CD" as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r3a.txt" as *u8)
141 eg_runv(ce, eg_args6("merge" as *u8, "/tmp/eg_D" as *u8, "/tmp/eg_C" as *u8, "/tmp/eg_DC" as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r3b.txt" as *u8)
142 eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_CD" as *u8, "/tmp/eg_tCD.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r3c.txt" as *u8)
143 eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_DC" as *u8, "/tmp/eg_tDC.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r3d.txt" as *u8)
144 r=0
145 if eg_fileis("/tmp/eg_tCD.txt" as *u8, "abf" as *u8)==1 { if eg_fileeq("/tmp/eg_tCD.txt" as *u8, "/tmp/eg_tDC.txt" as *u8)==1 { r=1 } }
146 pass=pass+eg_row("tombstone-anchor" as *u8, r)
147
148 // row 4: idempotent re-merge -- merge(AB, A) == AB byte-equal
149 eg_runv(ce, eg_args6("merge" as *u8, "/tmp/eg_AB" as *u8, "/tmp/eg_A" as *u8, "/tmp/eg_ABA" as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r4.txt" as *u8)
150 r=eg_fileeq("/tmp/eg_AB" as *u8, "/tmp/eg_ABA" as *u8)
151 pass=pass+eg_row("idempotent-remerge" as *u8, r)
152
153 // row 5: position-based delete -- type "abcd" fresh, delete pos1 count2 -> "ad"
154 eg_mkempty("/tmp/eg_l5" as *u8)
155 eg_runv(ce, eg_args6("edit" as *u8, "/tmp/eg_l5" as *u8, "1" as *u8, "0" as *u8, "ins" as *u8, "abcd" as *u8), "/tmp/eg_r5a.txt" as *u8)
156 eg_runv(ce, eg_args6("edit" as *u8, "/tmp/eg_l5" as *u8, "1" as *u8, "1" as *u8, "del" as *u8, "2" as *u8), "/tmp/eg_r5b.txt" as *u8)
157 let rc5: i64=eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_l5" as *u8, "/tmp/eg_t5.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r5c.txt" as *u8)
158 r=0
159 if rc5==0 { if eg_fileis("/tmp/eg_t5.txt" as *u8, "ad" as *u8)==1 { r=1 } }
160 pass=pass+eg_row("edit-del" as *u8, r)
161
162 // row 6 (neg): SAME id, DIFFERENT content = LOUD conflict
163 eg_write("/tmp/eg_l6" as *u8, "I 1 1 R x\nI 1 1 R y\n" as *u8)
164 let rc6: i64=eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_l6" as *u8, "/tmp/eg_t6.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r6.txt" as *u8)
165 r=0
166 if rc6==1 { if eg_has("/tmp/eg_r6.txt" as *u8, "err=3" as *u8)==1 { r=1 } }
167 pass=pass+eg_row("conflict-loud" as *u8, r)
168
169 // row 7 (neg): insert after an unknown id = LOUD bad-ref
170 eg_write("/tmp/eg_l7" as *u8, "I 1 1 R x\nI 1 2 9:9 y\n" as *u8)
171 let rc7: i64=eg_runv(ce, eg_args6("apply" as *u8, "/tmp/eg_l7" as *u8, "/tmp/eg_t7.txt" as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/eg_r7.txt" as *u8)
172 r=0
173 if rc7==1 { if eg_has("/tmp/eg_r7.txt" as *u8, "err=2" as *u8)==1 { r=1 } }
174 pass=pass+eg_row("badref-loud" as *u8, r)
175
176 let permil: i64=(pass*1000)/7
177 let logfd: i64=sys_openat_append("knowledge/status/vizsla_gate.log" as *u8, 0x1a4)
178 var fdi: i64=0
179 while fdi<2 {
180 var fd: i64=1; if fdi==1 { fd=logfd }
181 if fd>0 {
182 let line: *u8=sys_mmap(256); var o: i64=0
183 o=eg_cat(line,o,"COEDIT-GATE epoch=" as *u8); o=eg_catn(line,o,sys_now_realtime_sec())
184 o=eg_cat(line,o," rows=7 pass=" as *u8); o=eg_catn(line,o,pass)
185 o=eg_cat(line,o," permil=" as *u8); o=eg_catn(line,o,permil)
186 if pass==7 { o=eg_cat(line,o," verdict=GREEN\n" as *u8) } else { o=eg_cat(line,o," verdict=RED\n" as *u8) }
187 sys_write(fd, line, o)
188 }
189 fdi=fdi+1
190 }
191 if logfd>0 { sys_close(logfd) }
192 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
193 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
194 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
195 let ctr__dry: *i64 = gv_ctr()
196 ctr__dry[0] = pass
197 ctr__dry[1] = 7
198 let rc__dry: i64 = gv_verdict("COEDIT-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
199 sys_exit(rc__dry)
200 return rc__dry
201}