code wiki / _hdl_build / _wiki_edit_gate.nx
_wiki_edit_gate.nx source
↩ module page · 258 lines · 12876 B
1// _wiki_edit_gate.nx -- unit gate PROVING the wiki R1 auth-gated page EDIT/SAVE
2// backbone: an AUTHED save persists + renders + is an immutable revision, and
3// an UNAUTH save is REFUSED with the store UNCHANGED (the liar-kill).
4//
5// NO mocks, NO live socket: drives the REAL save core (nx_wiki_page_save) +
6// REAL doc store + the REAL reused auth (armed NxAuthContext: persistent key
7// bundle + OPAQUE account store + argon2id KSF -- the exact production auth the
8// wiki login uses) + the REAL append-only seg_store. Mirrors the proven shapes
9// in nx_wiki_login_live_gate.nx (armed ctx + register + login -> real token) and
10// _wiki_engine_gate.nx (doc-store serve as ground truth).
11//
12// Rows:
13// save_ok : AUTHED nx_wiki_page_save(slug,"new content X",VALID token)
14// -> ground truth = seg_store wikicur:<slug> reads back
15// "new content X" AND the doc store serves "new content X".
16// unauth_refused : nx_wiki_page_save(slug,"evil",EMPTY/INVALID token)
17// -> returns REFUSED AND seg_store wikicur:<slug> is STILL
18// "new content X" (the evil write never landed).
19// rev_ok : a wikirev:<slug>:* revision row exists for the authed save.
20// readback_ok : the wikicur:<slug> readback equaled the authed content.
21//
22// JUDGED by the printed WIKIEDIT marker (verdict=GREEN only if EVERY assertion
23// holds), NOT by $?. Per the native-codegen desync landmine, every assertion is
24// judged on a GROUND-TRUTH observable -- the BYTES read back out of the
25// seg_store / served from the doc store -- never a bare returned status code.
26//
27// A UNIQUE slug per run (epoch-suffixed) keeps the shared append-only
28// knowledge/store/wikipage- prefix from letting a PRIOR run's bytes answer for
29// this one (the store is additive; latest-put wins per slug).
30//
31// Import set: nx_syscalls + wiki/nx_wiki_edit_handler (transitively pulls the
32// save core -> the reused auth flow -> uas/opaque/bip39, base64, html_escape,
33// index_builder, seg_store). Each base module splices exactly ONCE (path-dedup)
34// to avoid the double-import nxasm rc6 trap.
35// license_tier: ORIGINAL
36import "nx_syscalls.nx"
37import "wiki/nx_wiki_edit_handler.nx"
38import "wiki/nx_wiki_doc_handler.nx"
39
40// ----- tiny io helpers (write to a fd) -----
41func gw(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
42func gn(fd: i64, v: i64) -> i64 {
43 let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
44 let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 }
45 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
46 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
47 sys_write(fd, bb, k); return 0
48}
49// write to BOTH stdout and the durable log fd (lfd<0 = stdout only)
50func w2(lfd: i64, s: *u8) -> i64 { gw(1, s); if lfd >= 0 { gw(lfd, s) } return 0 }
51func n2(lfd: i64, v: i64) -> i64 { gn(1, v); if lfd >= 0 { gn(lfd, v) } return 0 }
52
53// counted byte-equality
54func bytes_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
55 if an != bn { return 0 }
56 var i: i64 = 0
57 while i < an { if a[i] != b[i] { return 0 } i = i + 1 }
58 return 1
59}
60
61// does NUL-terminated haystack contain counted-length needle? (substring)
62func contains(hay: *u8, hay_n: i64, needle: *u8, needle_n: i64) -> i64 {
63 if needle_n <= 0 { return 0 }
64 if hay_n < needle_n { return 0 }
65 var i: i64 = 0
66 while i + needle_n <= hay_n {
67 var k: i64 = 0
68 var hit: i64 = 1
69 while k < needle_n { if hay[i + k] != needle[k] { hit = 0; k = needle_n } else { k = k + 1 } }
70 if hit == 1 { return 1 }
71 i = i + 1
72 }
73 return 0
74}
75
76// Serve "/wiki/<slug>" through the REAL doc handler and confirm the response is
77// HTTP 200 AND contains `need` (ground truth = bytes served). Returns 1/0.
78func serves_with(store: *NxWikiDocStore, slug: *u8, slug_n: i64,
79 need: *u8, need_n: i64) -> i64 {
80 let url: *u8 = sys_mmap(NX_WPS_URL_CAP)
81 let url_n: i64 = nx_wps_url(slug, slug_n, url)
82 let resp: *u8 = sys_mmap(1048576)
83 let rn: *i64 = sys_mmap(8) as *i64
84 rn[0] = 0
85 nx_wiki_doc_handle(store, 0 as *NxArtifactStore, url, url_n, resp, 1048576, rn)
86 let n: i64 = rn[0]
87 if n <= 0 { return 0 }
88 if contains(resp, n, "HTTP/1.1 200 OK" as *u8, 15) != 1 { return 0 }
89 if contains(resp, n, need, need_n) != 1 { return 0 }
90 return 1
91}
92
93// Does a wikirev:<slug>:* revision row exist (any epoch)? Ground truth: scan the
94// seg_store for the cur key's sibling rev prefix by walking ss_scan over a
95// REBUILT exact rev key is impossible (epoch unknown), so we read the rev via a
96// known-epoch key the caller passes (the epoch used for the authed save).
97func rev_exists(slug: *u8, slug_n: i64, epoch: i64) -> i64 {
98 let revk: *u8 = sys_mmap(NX_WPS_KEY_CAP)
99 nx_wps_rev_key(slug, slug_n, epoch, revk)
100 let h: *i64 = ss_open(nx_wps_prefix())
101 if (h as i64) == 0 { return 0 }
102 let pp: *i64 = sys_mmap(16) as *i64
103 let pl: *i64 = sys_mmap(16) as *i64
104 if ss_hget(h, revk, pp, pl) != 1 { return 0 }
105 // value must start with "author=" and carry the content tail
106 let v: *u8 = pp[0] as *u8
107 let vn: i64 = pl[0]
108 if contains(v, vn, "author=" as *u8, 7) != 1 { return 0 }
109 return 1
110}
111
112func main() -> i64 {
113 gw(1, "WIKIEDIT-GATE: start (authed save -> persist+render+revision; unauth -> refuse, store unchanged)\n" as *u8)
114
115 // ===== armed auth context (REAL key bundle + account store; /tmp = fresh) =====
116 let keys_path: *u8 = "/tmp/nishi_wiki_edit_gate_keys.log" as *u8
117 let store_path: *u8 = "/tmp/nishi_wiki_edit_gate_store.log" as *u8
118 // truncate any prior account store so register is clean each run
119 let fz: i64 = sys_openat_wr(store_path, 0x180)
120 if fz >= 0 { sys_close(fz) }
121
122 let oprf_seed: *u8 = sys_mmap(32)
123 let akp: *u8 = sys_mmap(32)
124 let akb: *u8 = sys_mmap(33)
125 let edp: *u8 = sys_mmap(32)
126 let edb: *u8 = sys_mmap(32)
127 if nx_uas_server_keys_load_or_init(keys_path, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK {
128 gw(1, "WIKIEDIT save_ok=0 unauth_refused=0 rev_ok=0 readback_ok=0 verdict=RED (keys init failed)\n" as *u8)
129 sys_exit(1); return 1
130 }
131 let realm: *u8 = "Nishi Wiki" as *u8
132 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext
133 nx_auth_context_init(ctx, realm, 10, realm, 10,
134 store_path as i64, oprf_seed, edp, edb,
135 900, 8192, 1, 1, 5, 1)
136
137 // register the admin, then LOGIN to mint a REAL session token
138 let handle: *u8 = "elderwesto" as *u8
139 let pw: *u8 = "correct horse+battery!" as *u8 // 22 bytes
140 let mn: *u8 = sys_mmap(512)
141 let mn_n: *i64 = sys_mmap(16) as *i64
142 nx_modern_auth_register(ctx, handle, 10, pw, 22, mn, 512, mn_n)
143
144 let tok: *u8 = sys_mmap(NX_MAUTH_SESSION_TOKEN_BYTES + 8)
145 let tok_n: *i64 = sys_mmap(8) as *i64
146 tok_n[0] = 0
147 let login_rc: i64 = nx_modern_auth_login(ctx, handle, 10, pw, 22,
148 tok, NX_MAUTH_SESSION_TOKEN_BYTES, tok_n)
149 // ground-truth token validity: a freshly minted token must validate NOW
150 let now_s: i64 = sys_now_realtime_sec()
151 var token_valid: i64 = 0
152 if login_rc == NX_MAUTH_OK {
153 if tok_n[0] == NX_MAUTH_SESSION_TOKEN_BYTES {
154 if nx_modern_auth_validate_session(ctx, tok, tok_n[0], now_s,
155 (0 as i64) as *u8, 0, (0 as i64) as *i64) == NX_MAUTH_OK {
156 token_valid = 1
157 }
158 }
159 }
160 gw(1, " minted+validated real session token: "); gn(1, token_valid); gw(1, "\n" as *u8)
161
162 // ===== seed a fresh doc store + a UNIQUE slug for this run =====
163 let doc_store: *NxWikiDocStore = sys_mmap(256) as *NxWikiDocStore
164 if nx_wiki_doc_store_init(doc_store, 100, 65536, 65536, 4194304) != NX_WIB_OK {
165 gw(1, "WIKIEDIT save_ok=0 unauth_refused=0 rev_ok=0 readback_ok=0 verdict=RED (doc store init failed)\n" as *u8)
166 sys_exit(1); return 1
167 }
168 // slug = "edit-gate-<epoch>" (unique per run; keeps the shared store clean)
169 let slug: *u8 = sys_mmap(64)
170 var so: i64 = 0
171 let sp: *u8 = "edit-gate-" as *u8
172 while sp[so] != (0 as u8) { slug[so] = sp[so]; so = so + 1 }
173 // append epoch decimal
174 var ev: i64 = now_s
175 let et: *u8 = sys_mmap(28); var ek: i64 = 0
176 if ev == 0 { et[0] = 48 as u8; ek = 1 }
177 while ev > 0 { et[ek] = (48 + (ev % 10)) as u8; ev = ev / 10; ek = ek + 1 }
178 var ei: i64 = 0
179 while ei < ek { slug[so + ei] = et[ek - 1 - ei]; ei = ei + 1 }
180 let slug_n: i64 = so + ek
181
182 // seed the page into the doc store (url /wiki/<slug>) with placeholder body
183 let url: *u8 = sys_mmap(NX_WPS_URL_CAP)
184 let url_n: i64 = nx_wps_url(slug, slug_n, url)
185 nx_wiki_doc_store_add(doc_store, slug, slug_n, url, url_n, "# seed\nold body\n" as *u8, 16)
186
187 // ===== ROW save_ok + readback_ok: AUTHED save -> persist + render =====
188 let content: *u8 = "new content X" as *u8
189 let content_n: i64 = 13
190 nx_wiki_page_save(doc_store, ctx, slug, slug_n, content, content_n,
191 tok, tok_n[0], now_s)
192
193 // ground truth #1: read wikicur:<slug> straight back out of the seg_store
194 let rb: *u8 = sys_mmap(NX_WPS_MAX_CONTENT_LEN + 16)
195 let rb_n: i64 = nx_wiki_page_load_current(slug, slug_n, rb, NX_WPS_MAX_CONTENT_LEN)
196 var readback_ok: i64 = 0
197 if rb_n >= 0 { if bytes_eq(rb, rb_n, content, content_n) == 1 { readback_ok = 1 } }
198 gw(1, " seg_store wikicur readback len="); gn(1, rb_n); gw(1, " eq='new content X': "); gn(1, readback_ok); gw(1, "\n" as *u8)
199
200 // ground truth #2: the doc store now SERVES "new content X"
201 var render_ok: i64 = serves_with(doc_store, slug, slug_n, content, content_n)
202 gw(1, " doc store serves 'new content X': "); gn(1, render_ok); gw(1, "\n" as *u8)
203
204 var save_ok: i64 = 0
205 if readback_ok == 1 { if render_ok == 1 { save_ok = 1 } }
206
207 // ===== ROW rev_ok: an immutable revision row exists for the authed save =====
208 var rev_ok: i64 = rev_exists(slug, slug_n, now_s)
209 gw(1, " wikirev:<slug>:<epoch> exists: "); gn(1, rev_ok); gw(1, "\n" as *u8)
210
211 // ===== ROW unauth_refused: UNAUTH save -> REFUSED + store UNCHANGED =====
212 // empty token (token_n=0) is the simplest forged credential.
213 let evil: *u8 = "evil" as *u8
214 let bad_tok: *u8 = sys_mmap(NX_MAUTH_SESSION_TOKEN_BYTES + 8)
215 let unauth_rc: i64 = nx_wiki_page_save(doc_store, ctx, slug, slug_n,
216 evil, 4, bad_tok, 0, now_s)
217 // ground truth: the verdict is REFUSED ...
218 var refused_code: i64 = 0
219 if unauth_rc == 0 - NX_WPS_REFUSED { refused_code = 1 }
220 // ... AND (the load-bearing check) the persisted current bytes are STILL
221 // "new content X" -- the evil write never touched the store.
222 let rb2: *u8 = sys_mmap(NX_WPS_MAX_CONTENT_LEN + 16)
223 let rb2_n: i64 = nx_wiki_page_load_current(slug, slug_n, rb2, NX_WPS_MAX_CONTENT_LEN)
224 var unchanged: i64 = 0
225 if rb2_n >= 0 { if bytes_eq(rb2, rb2_n, content, content_n) == 1 { unchanged = 1 } }
226 // ALSO confirm a second invalid token (garbage 152 bytes) is refused
227 let garb: *u8 = sys_mmap(NX_MAUTH_SESSION_TOKEN_BYTES + 8)
228 var gi: i64 = 0
229 while gi < NX_MAUTH_SESSION_TOKEN_BYTES { garb[gi] = 65 as u8; gi = gi + 1 }
230 let unauth_rc2: i64 = nx_wiki_page_save(doc_store, ctx, slug, slug_n,
231 evil, 4, garb, NX_MAUTH_SESSION_TOKEN_BYTES, now_s)
232 var refused_code2: i64 = 0
233 if unauth_rc2 == 0 - NX_WPS_REFUSED { refused_code2 = 1 }
234 var unauth_refused: i64 = 0
235 if refused_code == 1 { if refused_code2 == 1 { if unchanged == 1 { unauth_refused = 1 } } }
236 gw(1, " unauth refused(empty)="); gn(1, refused_code)
237 gw(1, " refused(garbage)="); gn(1, refused_code2)
238 gw(1, " store_unchanged="); gn(1, unchanged); gw(1, "\n" as *u8)
239
240 // ===== verdict + marker line =====
241 var green: i64 = 1
242 if token_valid != 1 { green = 0 }
243 if save_ok != 1 { green = 0 }
244 if unauth_refused != 1 { green = 0 }
245 if rev_ok != 1 { green = 0 }
246 if readback_ok != 1 { green = 0 }
247
248 let lfd: i64 = sys_openat_append("knowledge/status/wiki_edit_gate.log" as *u8, 420)
249 w2(lfd, "WIKIEDIT save_ok=" as *u8); n2(lfd, save_ok)
250 w2(lfd, " unauth_refused=" as *u8); n2(lfd, unauth_refused)
251 w2(lfd, " rev_ok=" as *u8); n2(lfd, rev_ok)
252 w2(lfd, " readback_ok=" as *u8); n2(lfd, readback_ok)
253 if green == 1 { w2(lfd, " verdict=GREEN\n" as *u8) } else { w2(lfd, " verdict=RED\n" as *u8) }
254 if lfd >= 0 { sys_close(lfd) }
255
256 if green == 1 { sys_exit(0); return 0 }
257 sys_exit(1); return 1
258}