code wiki / _hdl_build / nx_coedit_live_gate.nx

nx_coedit_live_gate.nx source

↩ module page · 275 lines · 14161 B

1// nx_coedit_live_gate.nx -- LIVE real-time co-editing, END-TO-END over real loopback HTTP (the fork idiom of 2// nx_relate_live_gate). This is the R2 proof that flips census OF-C1: the REAL blessed daemon 3// (/tmp/nx_coedit.sov.elf serve) is one process; TWO independent client PROCESSES concurrently edit the SAME 4// document through it and both CONVERGE to identical text -- which is exactly "real-time co-editing". 5// 6// server child = `nx_coedit serve <canon.log> <port>` (accept-loop, serves until killed) 7// client A (rep 1) = POST its ops "AC-after-root" then POLL-pull until the log carries BOTH replicas 8// client B (rep 2) = POST its ops "BD-after-root" then POLL-pull likewise 9// convergence (pinned): root children order desc by (counter,replica) => rep2 first => "BDAC" on BOTH. 10// Then the parent fork-execs `nx_coedit apply` on each client's pulled log and asserts BDAC == BDAC, and that 11// the canonical server log UNIONED all 4 ops with NO duplicates (idempotent under repeated polling). 12// expect_exit: 0 license_tier: ORIGINAL 13import "nx_syscalls.nx" 14 15import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 16import "nx_gate_verdict.nx" 17func lg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8) { n=n+1 } return n } 18func lg_p(s: *u8) -> i64 { sys_write(1, s, lg_slen(s)); return 0 } 19func lg_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 } 20func lg_catn(dst: *u8, off: i64, v: i64) -> i64 { 21 var o: i64=off; var m: i64=v; if m<0 { dst[o]=45 as u8; o=o+1; m=0-m } 22 let t: *u8=sys_mmap(28); var k: i64=0; if m==0 { t[0]=48 as u8; k=1 } 23 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 24 var i: i64=0; while i<k { dst[o+i]=t[k-1-i]; i=i+1 } return o+k 25} 26func lg_uint(dst: *u8, off: i64, v: i64) -> i64 { return lg_catn(dst, off, v) } 27func lg_write(path: *u8, buf: *u8, n: i64) -> i64 { 28 let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 { return 0-1 } 29 sys_write(fd, buf, n); sys_close(fd); return 0 30} 31func lg_mkempty(path: *u8) -> i64 { let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 { return 0-1 } sys_close(fd); return 0 } 32func lg_readall(path: *u8, szout: *i64) -> *u8 { 33 let fd: i64=sys_openat_rd(path); if fd<0 { szout[0]=0-1; return 0 as *u8 } 34 let buf: *u8=sys_mmap(1048576); var got: i64=0; var n: i64=1 35 while n>0 { n=sys_read(fd,(buf as i64+got) as *u8,65536); if n>0 { got=got+n } } 36 sys_close(fd); szout[0]=got; return buf 37} 38func lg_has(buf: *u8, n: i64, needle: *u8) -> i64 { 39 let nl: i64=lg_slen(needle); if n<nl { return 0 } 40 var i: i64=0 41 while i+nl<=n { var ok: i64=1; var j: i64=0; while j<nl { if buf[i+j]!=needle[j] { ok=0; j=nl } else { j=j+1 } } if ok==1 { return 1 } i=i+1 } 42 return 0 43} 44func lg_count(buf: *u8, n: i64, needle: *u8) -> i64 { 45 let nl: i64=lg_slen(needle); if n<nl { return 0 } 46 var c: i64=0; var i: i64=0 47 while i+nl<=n { var ok: i64=1; var j: i64=0; while j<nl { if buf[i+j]!=needle[j] { ok=0; j=nl } else { j=j+1 } } if ok==1 { c=c+1; i=i+nl } else { i=i+1 } } 48 return c 49} 50func lg_filehas(path: *u8, needle: *u8) -> i64 { let sp: *i64=sys_mmap(16) as *i64; let b: *u8=lg_readall(path, sp); if sp[0]<0 { return 0 } return lg_has(b, sp[0], needle) } 51func lg_fileis(path: *u8, exact: *u8) -> i64 { 52 let sp: *i64=sys_mmap(16) as *i64; let b: *u8=lg_readall(path, sp) 53 let n: i64=lg_slen(exact); if sp[0]!=n { return 0 } 54 var i: i64=0; while i<n { if b[i]!=exact[i] { return 0 } i=i+1 } return 1 55} 56func lg_fileeq(p1: *u8, p2: *u8) -> i64 { 57 let s1: *i64=sys_mmap(16) as *i64; let s2: *i64=sys_mmap(16) as *i64 58 let b1: *u8=lg_readall(p1, s1); let b2: *u8=lg_readall(p2, s2) 59 if s1[0]!=s2[0] { return 0 } if s1[0]<0 { return 0 } 60 var i: i64=0; while i<s1[0] { if b1[i]!=b2[i] { return 0 } i=i+1 } return 1 61} 62 63func lg_addr(out: *u8, port: i64) -> i64 { 64 out[0]=2 as u8; out[1]=0 as u8 65 out[2]=((port>>8)&0xff) as u8; out[3]=(port&0xff) as u8 66 out[4]=127 as u8; out[5]=0 as u8; out[6]=0 as u8; out[7]=1 as u8 67 var i: i64=8; while i<16 { out[i]=0 as u8; i=i+1 } return 0 68} 69func lg_sleep(spins: i64) -> i64 { var s: i64=0; while s<spins { s=s+1 } return 0 } 70 71// one HTTP exchange to 127.0.0.1:port; returns response length in out (headers+body), or <0 72func lg_http(port: i64, req: *u8, reqn: i64, out: *u8, cap: i64) -> i64 { 73 let addr: *u8=sys_mmap(16) 74 lg_addr(addr, port) 75 let cfd: i64=sys_socket(AF_INET, SOCK_STREAM, 0) 76 if cfd<0 { return 0-1 } 77 sys_set_socket_timeout(cfd, 4) // no client socket can block indefinitely 78 if nx_connect_bounded(cfd, addr, 16, NX_CONN_DEFAULT_MS)<0 { sys_close(cfd); return 0-2 } 79 var off: i64=0 80 while off<reqn { let w: i64=sys_write(cfd, (req as i64+off) as *u8, reqn-off); if w<=0 { sys_close(cfd); return 0-3 } off=off+w } 81 var n: i64=0 82 var go: i64=1 83 while go==1 { 84 if n>=cap-1 { go=0 } else { let r: i64=sys_read(cfd, (out as i64+n) as *u8, cap-1-n); if r<=0 { go=0 } else { n=n+r } } 85 } 86 sys_close(cfd) 87 return n 88} 89 90// POST /sync with body[0,blen); response into out. blen=0 => a pure pull. 91func lg_post(port: i64, body: *u8, blen: i64, out: *u8, cap: i64) -> i64 { 92 let req: *u8=sys_mmap(65536) 93 var o: i64=0 94 o=lg_cat(req, o, "POST /sync HTTP/1.1\r\nHost: c\r\nContent-Length: " as *u8) 95 o=lg_uint(req, o, blen) 96 o=lg_cat(req, o, "\r\nConnection: close\r\n\r\n" as *u8) 97 var i: i64=0 98 while i<blen { req[o]=body[i]; o=o+1; i=i+1 } 99 return lg_http(port, req, o, out, cap) 100} 101 102// body offset in an HTTP response (after \r\n\r\n), or the whole thing if no header terminator 103func lg_bodyoff(resp: *u8, rn: i64) -> i64 { 104 var i: i64=0 105 while i+3<rn { if resp[i]==(13 as u8) { if resp[i+1]==(10 as u8) { if resp[i+2]==(13 as u8) { if resp[i+3]==(10 as u8) { return i+4 } } } } i=i+1 } 106 return 0 107} 108 109// a client process: push its `ops` to the shared server (one POST), then one pull to confirm it gets a 110// well-formed response body. BOUNDED (no poll loop => no orchestration hang). The CONVERGENCE assertion is 111// the PARENT's final pull after BOTH clients have pushed; order-independence itself is proven by the kernel 112// gate. Returns 0 if push + pull both succeeded, else a diagnostic code. 113func lg_push(port: i64, ops: *u8, outlog: *u8) -> i64 { 114 lg_sleep(700000) // stagger past the server bind+listen 115 let resp: *u8=sys_mmap(1048576) 116 let pn: i64=lg_post(port, ops, lg_slen(ops), resp, 1048576) // push (length from the string, no truncation) 117 if pn<=0 { return 21 } 118 let rn: i64=lg_post(port, ops, 0, resp, 1048576) // one pull (empty body) 119 if rn<=0 { return 22 } 120 let bo: i64=lg_bodyoff(resp, rn) 121 lg_write(outlog, ((resp as i64)+bo) as *u8, rn-bo) 122 return 0 123} 124 125// fork+exec `nx_coedit apply <inlog> <outtxt>`; wait; return child exit code 126func lg_apply(inlog: *u8, outtxt: *u8) -> i64 { 127 let pid: i64=sys_fork() 128 if pid==0 { 129 let lfd: i64=sys_openat_wr("/tmp/clv_apply.log\x00" as *u8, 0x1a4) 130 if lfd>=0 { sys_dup3(lfd,1,0); sys_dup3(lfd,2,0) } 131 let argv: *i64=sys_mmap(64) as *i64 132 argv[0]="buildroot/_build/nx_coedit.sov.elf\x00" as *u8 as i64 133 argv[1]="apply\x00" as *u8 as i64 134 argv[2]=inlog as i64 135 argv[3]=outtxt as i64 136 argv[4]=0 137 let envp: *i64=sys_mmap(16) as *i64; envp[0]=0 138 sys_execve("buildroot/_build/nx_coedit.sov.elf\x00" as *u8, argv, envp) 139 sys_exit(127) 140 } 141 let st: *i64=sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 142 return (st[0]>>8)&0xff 143} 144func lg_kill(pid: i64) -> i64 { return __syscall(129, pid, 9, 0, 0, 0, 0) } // rv64 kill=129 -- raw x86 62 is an RV64 KEY translated to lseek(8), the kill never happened (debt idx 2277) 145 146func lg_row(name: *u8, pass: i64) -> i64 { 147 lg_p("ROW " as *u8); lg_p(name); if pass==1 { lg_p(" PASS\n" as *u8) } else { lg_p(" FAIL\n" as *u8) } return pass 148} 149 150func main(argc: i64, argv: *i64) -> i64 { 151 lg_p("=== COEDIT LIVE GATE: 2 client PROCESSES concurrently edit ONE doc over real HTTP -> converge (BDAC) ===\n" as *u8) 152 if sys_openat_rd("buildroot/_build/nx_coedit.sov.elf\x00" as *u8)<0 { 153 lg_p(" instrument missing -> rebuilding\n" as *u8) 154 let pid0: i64=sys_fork() 155 if pid0==0 { 156 let argv0: *i64=sys_mmap(64) as *i64 157 argv0[0]="_offc/nx_sov_build_run.elf\x00" as *u8 as i64 158 argv0[1]="nx_coedit\x00" as *u8 as i64 159 argv0[2]="--build-only\x00" as *u8 as i64 160 argv0[3]=0 161 let envp0: *i64=sys_mmap(16) as *i64; envp0[0]=0 162 sys_execve("_offc/nx_sov_build_run.elf\x00" as *u8, argv0, envp0) 163 sys_exit(127) 164 } 165 let s0: *i64=sys_mmap(16) as *i64; sys_wait4(pid0, s0, 0) 166 } 167 168 // UNIQUE per-run port + canon path (collision-proof: repeated runs / a stray prior server never clash) 169 let now: i64 = sys_now_realtime_sec() 170 let PORT: i64 = 9100 + (now % 700) 171 let portstr: *u8 = sys_mmap(16) 172 var pso: i64 = lg_catn(portstr, 0, PORT); portstr[pso]=0 as u8 173 let canon: *u8 = sys_mmap(64) 174 var cxo: i64 = lg_cat(canon, 0, "/tmp/clv_canon_" as *u8); cxo=lg_catn(canon, cxo, now); cxo=lg_cat(canon, cxo, ".log" as *u8); canon[cxo]=0 as u8 175 lg_mkempty(canon) 176 177 // 1) server child: exec the REAL daemon, serve until a clean /shutdown request 178 let spid: i64=sys_fork() 179 if spid==0 { 180 let lfd: i64=sys_openat_wr("/tmp/clv_serve.log\x00" as *u8, 0x1a4) 181 if lfd>=0 { sys_dup3(lfd,1,0); sys_dup3(lfd,2,0) } 182 let argv: *i64=sys_mmap(64) as *i64 183 argv[0]="buildroot/_build/nx_coedit.sov.elf\x00" as *u8 as i64 184 argv[1]="serve\x00" as *u8 as i64 185 argv[2]=canon as i64 186 argv[3]=portstr as i64 187 argv[4]=0 188 let envp: *i64=sys_mmap(16) as *i64; envp[0]=0 189 sys_execve("buildroot/_build/nx_coedit.sov.elf\x00" as *u8, argv, envp) 190 sys_exit(127) 191 } 192 193 // 2) two concurrent client PROCESSES push their edits to the shared server (bounded => no hang) 194 let apid: i64=sys_fork() 195 if apid==0 { 196 let rc: i64=lg_push(PORT, "I 1 1 R A\nI 1 2 1:1 C\n" as *u8, "/tmp/clv_A.log\x00" as *u8) 197 sys_exit(rc); return rc 198 } 199 let bpid: i64=sys_fork() 200 if bpid==0 { 201 let rc: i64=lg_push(PORT, "I 2 1 R B\nI 2 2 2:1 D\n" as *u8, "/tmp/clv_B.log\x00" as *u8) 202 sys_exit(rc); return rc 203 } 204 205 let sa: *i64=sys_mmap(16) as *i64; sys_wait4(apid, sa, 0) 206 let acode: i64=(sa[0]>>8)&0xff 207 let sb: *i64=sys_mmap(16) as *i64; sys_wait4(bpid, sb, 0) 208 let bcode: i64=(sb[0]>>8)&0xff 209 210 // 3) BOTH clients have pushed -> the PARENT pulls the merged doc and materializes it: convergence proof. 211 let fresp: *u8=sys_mmap(1048576) 212 let fn: i64=lg_post(PORT, "\x00" as *u8, 0, fresp, 1048576) // empty-body pull = GET the union 213 if fn>0 { let bo: i64=lg_bodyoff(fresp, fn); lg_write("/tmp/clv_final.log\x00" as *u8, ((fresp as i64)+bo) as *u8, fn-bo) } 214 215 // clean shutdown: stop the accept loop, then reap (no SIGKILL race) 216 let sd: *u8=sys_mmap(4096) 217 lg_http(PORT, "GET /shutdown HTTP/1.1\r\nHost: c\r\nConnection: close\r\n\r\n" as *u8, 52, sd, 4096) 218 let ss: *i64=sys_mmap(16) as *i64; sys_wait4(spid, ss, 0) 219 220 lg_apply("/tmp/clv_final.log\x00" as *u8, "/tmp/clv_final.txt\x00" as *u8) 221 222 var pass: i64=0 223 var r: i64=0 224 225 // both independent client processes pushed successfully over HTTP 226 r=0; if acode==0 { if bcode==0 { r=1 } } 227 pass=pass+lg_row("two-processes-pushed-over-http" as *u8, r) 228 229 // the merged doc, pulled AFTER both concurrent pushes, materializes to the pinned converged text 230 r=lg_fileis("/tmp/clv_final.txt\x00" as *u8, "BDAC" as *u8) 231 pass=pass+lg_row("CONVERGED-live-to-BDAC" as *u8, r) 232 233 // canonical server log UNIONED all 4 ops from the two clients 234 r=0 235 if lg_filehas(canon, "I 1 1 R A" as *u8)==1 { if lg_filehas(canon, "I 1 2 1:1 C" as *u8)==1 { if lg_filehas(canon, "I 2 1 R B" as *u8)==1 { if lg_filehas(canon, "I 2 2 2:1 D" as *u8)==1 { r=1 } } } } 236 pass=pass+lg_row("server-log-unioned-4ops" as *u8, r) 237 238 // idempotent: each op appears EXACTLY once (dedup under the client's push + pull re-sends) 239 r=0 240 let sp: *i64=sys_mmap(16) as *i64 241 let cb: *u8=lg_readall(canon, sp) 242 if sp[0]>0 { if lg_count(cb, sp[0], "I 1 1 R A" as *u8)==1 { if lg_count(cb, sp[0], "I 2 1 R B" as *u8)==1 { r=1 } } } 243 pass=pass+lg_row("server-log-idempotent-no-dup" as *u8, r) 244 245 // the client's own pull also carried the response body (liveness of the sync endpoint) 246 r=0 247 if lg_filehas("/tmp/clv_A.log\x00" as *u8, "I 1 1 R A" as *u8)==1 { if lg_filehas("/tmp/clv_B.log\x00" as *u8, "I 2 1 R B" as *u8)==1 { r=1 } } 248 pass=pass+lg_row("client-pulls-carry-body" as *u8, r) 249 250 let permil: i64=(pass*1000)/5 251 let logfd: i64=sys_openat_append("knowledge/status/vizsla_gate.log\x00" as *u8, 0x1a4) 252 var fdi: i64=0 253 while fdi<2 { 254 var fd: i64=1; if fdi==1 { fd=logfd } 255 if fd>0 { 256 let line: *u8=sys_mmap(256); var o: i64=0 257 o=lg_cat(line,o,"COEDIT-LIVE-GATE epoch=" as *u8); o=lg_catn(line,o,sys_now_realtime_sec()) 258 o=lg_cat(line,o," rows=5 pass=" as *u8); o=lg_catn(line,o,pass) 259 o=lg_cat(line,o," permil=" as *u8); o=lg_catn(line,o,permil) 260 if pass==5 { o=lg_cat(line,o," verdict=GREEN\n" as *u8) } else { o=lg_cat(line,o," verdict=RED\n" as *u8) } 261 sys_write(fd, line, o) 262 } 263 fdi=fdi+1 264 } 265 if logfd>0 { sys_close(logfd) } 266 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 267 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 268 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 269 let ctr__dry: *i64 = gv_ctr() 270 ctr__dry[0] = pass 271 ctr__dry[1] = 5 272 let rc__dry: i64 = gv_verdict("COEDIT-LIVE-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 273 sys_exit(rc__dry) 274 return rc__dry 275}