code wiki / _hdl_build / nx_reader_sync_gate.nx
nx_reader_sync_gate.nx source
↩ module page · 205 lines · 11255 B
1import "nx_gate_grow.nx"
2import "nx_gate_gn.nx"
3import "nx_gate_read.nx"
4// nx_reader_sync_gate.nx -- SOVEREIGN in-process referee for the R1 SYNC SPINE (nx_reader_sync). No socket/shell:
5// chdir /tmp, build the fixture book world (reader/sgate/ with a cbx.json marker), drive the handlers with
6// crafted request bytes + a FILE fd as cfd, assert response bytes AND on-disk state. Per-user isolation is the
7// core Whispersync property under test.
8// T0 ukey: deterministic + sanitize-collision-safe ("Elder!" vs "Elder_" -> DIFFERENT keys) + path-safe
9// T1 PUT then GET round-trip (user A): stored verbatim, auto empty
10// T2 auto-record from a chapter fetch path -> GET shows auto=chap007.txt + the exact epoch
11// T3 USER ISOLATION: user B sees pos:null + auto:"" for the same book
12// T4 NEG traversal slug -> 403 (GET and PUT)
13// T5 NEG oversized body (> cap) -> 413 + nothing stored ; PUT to a nonexistent book -> 404
14// T6 LWW: second PUT overwrites; GET returns v2; no .new temp left behind
15// GREEN iff 7/7. license_tier: ORIGINAL expect_exit: 0
16import "nx_syscalls.nx"
17import "nx_reader_sync.nx" // sy_* under test (+ zs_* transitively)
18
19func gw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
20func g_has(hay: *u8, n: i64, needle: *u8) -> i64 {
21 var nn: i64 = 0; while needle[nn] != (0 as u8) { nn = nn + 1 }
22 if nn == 0 { return 0 }
23 var i: i64 = 0
24 while i + nn <= n { var m: i64 = 1; var j: i64 = 0; while j < nn { if (hay[i + j] as i64) != (needle[j] as i64) { m = 0; j = nn } else { j = j + 1 } } if m == 1 { return 1 } i = i + 1 }
25 return 0
26}
27func g_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 }
28func g_mkdir(path: *u8) -> i64 { __syscall(258, 0 - 100, path as i64, 0x1ed, 0, 0, 0); return 0 }
29func g_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
30func g_unlink(path: *u8) -> i64 { __syscall(263, 0 - 100, path, 0, 0, 0, 0); return 0 }
31func g_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] == (0 as u8) { return 1 } return 0 }
32
33// run a GET handler call; response captured via a file fd
34func g_get(p: *u8, ukey: *u8, out: *u8, cap: i64) -> i64 {
35 let rp: *u8 = "sy_rsp.tmp" as *u8
36 g_unlink(rp)
37 let fd: i64 = sys_openat_wr("sy_rsp.tmp" as *u8, 0x1a4)
38 if fd < 0 { return 0 - 1 }
39 sy_handle_pos_get(fd, p, g_slen(p), ukey)
40 return g_read("sy_rsp.tmp" as *u8, out, cap)
41}
42// run a PUT handler call with body
43func g_put(p: *u8, body: *u8, blen: i64, ukey: *u8, out: *u8, cap: i64) -> i64 {
44 let rp: *u8 = "sy_rsp.tmp" as *u8
45 g_unlink(rp)
46 let fd: i64 = sys_openat_wr("sy_rsp.tmp" as *u8, 0x1a4)
47 if fd < 0 { return 0 - 1 }
48 sy_handle_pos_put(fd, p, g_slen(p), body, 0, blen, ukey)
49 return g_read("sy_rsp.tmp" as *u8, out, cap)
50}
51// annotation variants
52func g_ann_get(p: *u8, ukey: *u8, out: *u8, cap: i64) -> i64 {
53 let rp: *u8 = "sy_rsp.tmp" as *u8
54 g_unlink(rp)
55 let fd: i64 = sys_openat_wr("sy_rsp.tmp" as *u8, 0x1a4)
56 if fd < 0 { return 0 - 1 }
57 sy_handle_ann_get(fd, p, g_slen(p), ukey)
58 return g_read("sy_rsp.tmp" as *u8, out, cap)
59}
60func g_ann_put(p: *u8, body: *u8, blen: i64, ukey: *u8, out: *u8, cap: i64) -> i64 {
61 let rp: *u8 = "sy_rsp.tmp" as *u8
62 g_unlink(rp)
63 let fd: i64 = sys_openat_wr("sy_rsp.tmp" as *u8, 0x1a4)
64 if fd < 0 { return 0 - 1 }
65 sy_handle_ann_put(fd, p, g_slen(p), body, 0, blen, ukey)
66 return g_read("sy_rsp.tmp" as *u8, out, cap)
67}
68
69func main() -> i64 {
70 gw("reader-sync SOVEREIGN in-process gate (fixture world -> per-user position spine asserts)\n" as *u8)
71 if sys_chdir("/tmp" as *u8) != 0 { gw("CHDIR /tmp FAIL\n" as *u8); sys_exit(1) }
72
73 // fixture: book dir with a cbx.json marker (the PUT existence probe)
74 g_mkdir("knowledge" as *u8)
75 g_mkdir("knowledge/staging" as *u8)
76 g_mkdir("knowledge/staging/media" as *u8)
77 g_mkdir("knowledge/staging/media/reader" as *u8)
78 g_mkdir("knowledge/staging/media/reader/sgate" as *u8)
79 let mf: i64 = sys_openat_wr("knowledge/staging/media/reader/sgate/cbx.json" as *u8, 0x1a4)
80 if mf >= 0 { sys_write(mf, "{\"pages\":[]}" as *u8, 12); sys_close(mf) }
81
82 var pass: i64 = 0
83 let rsp: *u8 = sys_mmap(65536)
84
85 // ---- T0 ukey determinism + sanitize-collision safety ----
86 let kA: *u8 = sys_mmap(64); let kA2: *u8 = sys_mmap(64); let kB: *u8 = sys_mmap(64); let kC: *u8 = sys_mmap(64)
87 sy_user_key("Elder!" as *u8, 6, kA)
88 sy_user_key("Elder!" as *u8, 6, kA2)
89 sy_user_key("Elder_" as *u8, 6, kB)
90 sy_user_key("familymember" as *u8, 12, kC)
91 var t0: i64 = 1
92 if g_streq(kA, kA2) == 0 { t0 = 0 } // deterministic
93 if g_streq(kA, kB) == 1 { t0 = 0 } // "Elder!" vs "Elder_" MUST differ (fnv suffix disambiguates the sanitize collision)
94 if g_streq(kA, kC) == 1 { t0 = 0 }
95 gw(" ukeyA=" as *u8); gw(kA); gw(" ukeyB=" as *u8); gw(kB); gw("\n" as *u8)
96 pass = pass + grow("T0 ukey deterministic + collision-safe after sanitize\x00" as *u8, t0)
97
98 // fixture cleanliness: wipe any prior run's per-user state so T1 exercises the COLD path deterministically
99 let clp: *u8 = sys_mmap(1152)
100 sy_path("sgate" as *u8, "pos" as *u8, kA, ".json" as *u8, clp); g_unlink(clp)
101 sy_path("sgate" as *u8, "auto" as *u8, kA, ".txt" as *u8, clp); g_unlink(clp)
102 sy_path("sgate" as *u8, "pos" as *u8, kC, ".json" as *u8, clp); g_unlink(clp)
103 sy_path("sgate" as *u8, "auto" as *u8, kC, ".txt" as *u8, clp); g_unlink(clp)
104 sy_path("sgate" as *u8, "ann" as *u8, kA, ".json" as *u8, clp); g_unlink(clp)
105 sy_path("sgate" as *u8, "ann" as *u8, kC, ".json" as *u8, clp); g_unlink(clp)
106
107 // ---- T1 PUT -> GET round-trip (user A) ----
108 var rn: i64 = g_put("/api/pos?slug=sgate" as *u8, "{\"page\":3,\"ts\":111}" as *u8, 19, kA, rsp, 65536)
109 var t1: i64 = 0
110 if rn > 0 { if g_has(rsp, rn, "HTTP/1.1 200" as *u8) == 1 { if g_has(rsp, rn, "\"ok\":1" as *u8) == 1 {
111 rn = g_get("/api/pos?slug=sgate" as *u8, kA, rsp, 65536)
112 if g_has(rsp, rn, "HTTP/1.1 200" as *u8) == 1 { if g_has(rsp, rn, "\"pos\":{\"page\":3,\"ts\":111}" as *u8) == 1 { if g_has(rsp, rn, "\"auto\":\"\"" as *u8) == 1 { t1 = 1 } } }
113 } } }
114 pass = pass + grow("T1 PUT->GET round-trip verbatim (user A) + auto empty\x00" as *u8, t1)
115
116 // ---- T2 auto-record from a chapter fetch ----
117 let ar: i64 = sy_record_auto("/api/book?p=reader/sgate/chap007.txt" as *u8, 36, kA, 1234567)
118 rn = g_get("/api/pos?slug=sgate" as *u8, kA, rsp, 65536)
119 var t2: i64 = 0
120 if ar == 1 { if g_has(rsp, rn, "\"auto\":\"chap007.txt\"" as *u8) == 1 { if g_has(rsp, rn, "\"auto_ts\":1234567" as *u8) == 1 { t2 = 1 } } }
121 pass = pass + grow("T2 chapter fetch auto-records -> auto=chap007.txt ts=1234567 (zero-JS sync)\x00" as *u8, t2)
122
123 // ---- T3 user isolation ----
124 rn = g_get("/api/pos?slug=sgate" as *u8, kC, rsp, 65536)
125 var t3: i64 = 0
126 if g_has(rsp, rn, "\"pos\":null" as *u8) == 1 { if g_has(rsp, rn, "\"auto\":\"\"" as *u8) == 1 { t3 = 1 } }
127 pass = pass + grow("T3 USER ISOLATION: user C sees pos:null + auto:'' on the same book\x00" as *u8, t3)
128
129 // ---- T4 NEG traversal slug ----
130 rn = g_get("/api/pos?slug=..sgate" as *u8, kA, rsp, 65536)
131 var t4: i64 = 0
132 if g_has(rsp, rn, "HTTP/1.1 403" as *u8) == 1 {
133 rn = g_put("/api/pos?slug=..sgate" as *u8, "{}" as *u8, 2, kA, rsp, 65536)
134 if g_has(rsp, rn, "HTTP/1.1 403" as *u8) == 1 { t4 = 1 }
135 }
136 pass = pass + grow("T4 NEG traversal slug -> 403 on GET and PUT\x00" as *u8, t4)
137
138 // ---- T5 NEG oversize body -> 413 + nothing stored ; nonexistent book -> 404 ----
139 let big: *u8 = sys_mmap(4096)
140 var bi: i64 = 0
141 while bi < 2100 { big[bi] = 120 as u8; bi = bi + 1 } // 'x' * 2100 > cap 2048
142 big[2100] = 0 as u8
143 rn = g_put("/api/pos?slug=sgate" as *u8, big, 2100, kC, rsp, 65536)
144 var t5: i64 = 0
145 if g_has(rsp, rn, "HTTP/1.1 413" as *u8) == 1 {
146 let cp: *u8 = sys_mmap(1152)
147 sy_path("sgate" as *u8, "pos" as *u8, kC, ".json" as *u8, cp)
148 if g_exists(cp) == 0 {
149 rn = g_put("/api/pos?slug=nobook" as *u8, "{}" as *u8, 2, kA, rsp, 65536)
150 if g_has(rsp, rn, "HTTP/1.1 404" as *u8) == 1 { t5 = 1 }
151 }
152 }
153 pass = pass + grow("T5 NEG oversize -> 413 + not stored ; nonexistent book -> 404\x00" as *u8, t5)
154
155 // ---- T6 LWW overwrite + no temp left ----
156 g_put("/api/pos?slug=sgate" as *u8, "{\"page\":9,\"ts\":222}" as *u8, 19, kA, rsp, 65536)
157 rn = g_get("/api/pos?slug=sgate" as *u8, kA, rsp, 65536)
158 var t6: i64 = 0
159 if g_has(rsp, rn, "\"pos\":{\"page\":9,\"ts\":222}" as *u8) == 1 {
160 let pp: *u8 = sys_mmap(1152)
161 let pl: i64 = sy_path("sgate" as *u8, "pos" as *u8, kA, ".json" as *u8, pp)
162 let tp: *u8 = sys_mmap(1200)
163 var vo: i64 = 0
164 while vo < pl { tp[vo] = pp[vo]; vo = vo + 1 }
165 vo = 0
166 let sfx: *u8 = ".new" as *u8
167 while sfx[vo] != (0 as u8) { tp[pl + vo] = sfx[vo]; vo = vo + 1 }
168 tp[pl + vo] = 0 as u8
169 if g_exists(tp) == 0 { t6 = 1 }
170 }
171 pass = pass + grow("T6 LWW: second PUT wins on GET + no .new temp left (atomic)\x00" as *u8, t6)
172
173 // ---- T7 R2 ANNOTATIONS: PUT -> GET verbatim + user isolation + GET-as-export ----
174 let abody: *u8 = "[{\"page\":2,\"text\":\"great panel\",\"anchor\":\"page002\",\"ts\":333}]" as *u8
175 rn = g_ann_put("/api/ann?slug=sgate" as *u8, abody, g_slen(abody), kA, rsp, 65536)
176 var t7: i64 = 0
177 if g_has(rsp, rn, "HTTP/1.1 200" as *u8) == 1 { if g_has(rsp, rn, "\"ok\":1" as *u8) == 1 {
178 rn = g_ann_get("/api/ann?slug=sgate" as *u8, kA, rsp, 65536)
179 if g_has(rsp, rn, "\"ann\":[{\"page\":2,\"text\":\"great panel\",\"anchor\":\"page002\",\"ts\":333}]" as *u8) == 1 {
180 rn = g_ann_get("/api/ann?slug=sgate" as *u8, kC, rsp, 65536)
181 if g_has(rsp, rn, "\"ann\":null" as *u8) == 1 { t7 = 1 }
182 }
183 } }
184 pass = pass + grow("T7 annotations PUT->GET verbatim (export-by-construction) + user isolation\x00" as *u8, t7)
185
186 // ---- T8 R2 NEG: traversal 403 + oversize 413 + not stored ----
187 var t8: i64 = 0
188 rn = g_ann_put("/api/ann?slug=..sgate" as *u8, "[]" as *u8, 2, kA, rsp, 65536)
189 if g_has(rsp, rn, "HTTP/1.1 403" as *u8) == 1 {
190 let hb: *u8 = sys_mmap(70000)
191 var hbi: i64 = 0
192 while hbi < 66000 { hb[hbi] = 121 as u8; hbi = hbi + 1 } // 'y' * 66000 > 65536
193 rn = g_ann_put("/api/ann?slug=sgate" as *u8, hb, 66000, kC, rsp, 65536)
194 if g_has(rsp, rn, "HTTP/1.1 413" as *u8) == 1 {
195 let cp2: *u8 = sys_mmap(1152)
196 sy_path("sgate" as *u8, "ann" as *u8, kC, ".json" as *u8, cp2)
197 if g_exists(cp2) == 0 { t8 = 1 }
198 }
199 }
200 pass = pass + grow("T8 NEG annotations: traversal 403 + oversize 413 + not stored\x00" as *u8, t8)
201
202 gw("pass=" as *u8); gn(pass); gw("/9\n" as *u8)
203 if pass == 9 { gw("verdict=GREEN (R1+R2 sync spine: per-user position + annotations, verbatim + isolated + fail-closed, all atomic)\n" as *u8); sys_exit(0); return 0 }
204 gw("verdict=RED\n" as *u8); sys_exit(1); return 1
205}