code wiki / _hdl_build / nx_wiki_versioned_publish_gate.nx
nx_wiki_versioned_publish_gate.nx source
↩ module page · 287 lines · 16097 B
1// nx_wiki_versioned_publish_gate.nx -- TEETH for A4a (versioned-publish + multi-point rollback).
2//
3// HERMETIC: a fresh per-run archive/index store prefix in /tmp; EVERY publish/restore uses do_push_flag=0
4// so the gate NEVER touches the network/vault/SSH -- but it DOES exercise the archive + version-index +
5// restore BYTE-PATH fully (read blob back by CID, write to a local file, re-read). No fake-green: exit 0
6// iff every assertion passes; a PASS line is appended to knowledge/status/ only on all-pass.
7//
8// WHAT IS PROVEN (each asserts EXACT bytes/identities, not merely "ok"):
9// C1 publish v1,v2,v3 (3 DISTINCT contents) -> each vpub returns VP_OK and assigns v#=1,2,3.
10// C2 MULTI-POINT: vp_list(slug) returns exactly 3 retained rollback points with 3 DISTINCT CIDs
11// (not just the latest) -- the core "MULTIPLE rollback points" guarantee.
12// C3 restore -> v1 yields the EXACT v1 bytes (byte-for-byte) live-staged.
13// C4 restore -> the MIDDLE v2 yields the EXACT v2 bytes (proves rollback to ANY point, not just last).
14// C5 restore -> an unknown v# (99) is REFUSED (VR_NOT_FOUND) and push is NOT invoked (fail-closed).
15// C6 APPEND-ONLY: after v2 AND v3 were published, v1's retained record (its CID and the bytes that CID
16// resolves to) is BYTE-IDENTICAL to what it was right after v1 -- a new publish never mutates/deletes
17// an older version.
18// C7 the 3 distinct contents are pairwise distinct (sanity: distinct CIDs require distinct bytes).
19// license_tier: ORIGINAL
20import "nx_wiki_versioned_publish.nx"
21import "nx_wiki_restore.nx"
22import "nx_syscalls.nx"
23
24func g_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
25func g_num(v: i64) -> i64 {
26 let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) }
27 let t: *u8 = sys_mmap(28); var k: i64 = 0
28 if m == 0 { t[0] = 48 as u8; k = 1 }
29 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
30 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
31 sys_write(1, bb, k); return 0
32}
33func g_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64 = off; var k: i64 = 0; while s[k] != (0 as u8) { dst[o] = s[k]; o = o + 1; k = k + 1 } return o }
34func g_catnum(dst: *u8, off: i64, v: i64) -> i64 {
35 var o: i64 = off; let t: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m }
36 var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 }
37 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
38 var i: i64 = 0; while i < k { dst[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } return o
39}
40func g_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
41
42// byte-equal two buffers of given lengths.
43func g_byteq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
44 if an != bn { return 0 }
45 var i: i64 = 0
46 while i < an { if a[i] != b[i] { return 0 } i = i + 1 }
47 return 1
48}
49// NUL-terminated string equal.
50func g_streq(a: *u8, b: *u8) -> i64 {
51 var i: i64 = 0
52 while 1 == 1 {
53 if a[i] != b[i] { return 0 }
54 if a[i] == (0 as u8) { return 1 }
55 i = i + 1
56 }
57 return 1
58}
59
60// write buf to a per-run /tmp file so vpub/restore read it like a production page.
61func g_stage(path: *u8, buf: *u8, n: i64) -> i64 {
62 let fd: i64 = sys_openat_wr(path, 0x1a4)
63 if fd < 0 { return 0 - 1 }
64 sys_write(fd, buf, n)
65 sys_close(fd)
66 return 0
67}
68
69// emit a PASS/FAIL row; returns 1 if ok.
70func g_row(id: i64, ok: i64, what: *u8) -> i64 {
71 g_w("VPROW " as *u8); g_num(id); g_w(" " as *u8)
72 if ok == 1 { g_w("PASS " as *u8) } else { g_w("FAIL " as *u8) }
73 g_w(what); g_w("\n" as *u8)
74 return ok
75}
76
77// build a guard-PASSING page body into buf: freshness stamp + only-corpus /wiki link + a resolvable
78// [[cite:<cid>]] + a distinguishing marker string. Returns the byte length.
79func g_build_page(buf: *u8, marker: *u8, goodcid: *u8) -> i64 {
80 var o: i64 = 0
81 o = g_cat(buf, o, "<h1>Versioned</h1><p class=\"fresh\">epoch=1781730256</p>" as *u8)
82 o = g_cat(buf, o, "<p>See <a href=\"/wiki/charter.html\">the charter</a>.</p>" as *u8)
83 o = g_cat(buf, o, "<p>marker=" as *u8)
84 o = g_cat(buf, o, marker)
85 o = g_cat(buf, o, "</p><p>Evidence: [[cite:" as *u8)
86 o = g_cat(buf, o, goodcid)
87 o = g_cat(buf, o, "]]</p>" as *u8)
88 return o
89}
90
91func main() -> i64 {
92 // ---- a fresh per-run archive/index store prefix (flat files in /tmp; no mkdir) ----
93 let prefix: *u8 = sys_mmap(128)
94 var po: i64 = 0
95 po = g_cat(prefix, po, "/tmp/vpds" as *u8)
96 po = g_catnum(prefix, po, sys_now_us())
97 po = g_cat(prefix, po, "-" as *u8)
98 prefix[po] = 0 as u8
99
100 // ---- PLANT a resolvable citation source so the guard's cite-resolution passes ----
101 let srcbody: *u8 = "NIST SP 800-207 zero-trust: evidence-cited, versioned publishing with rollback." as *u8
102 let srcn: i64 = g_slen(srcbody)
103 let goodcid: *u8 = sys_mmap(80)
104 let w0: *i64 = ss_begin()
105 if war_archive_page(w0, "src_a4a" as *u8, srcbody, srcn, goodcid) != 0 { g_w("VPGATE FATAL: src archive failed\n" as *u8); sys_exit(2); return 2 }
106 if ss_commit(prefix, w0, 0) != 0 { g_w("VPGATE FATAL: src commit failed\n" as *u8); sys_exit(2); return 2 }
107 let chkpp: *i64 = sys_mmap(16) as *i64
108 if war_get_by_cid(prefix, goodcid, chkpp, VR_SCAN_CAP) <= 0 { g_w("VPGATE FATAL: planted cite did not resolve\n" as *u8); sys_exit(2); return 2 }
109
110 // ---- corpus set (only these /wiki links are allowed by the guard) ----
111 let cs_ptr: *i64 = sys_mmap(8 * 8) as *i64
112 let cs_len: *i64 = sys_mmap(8 * 8) as *i64
113 cs_ptr[0] = "start" as *u8 as i64; cs_len[0] = 5
114 cs_ptr[1] = "charter" as *u8 as i64; cs_len[1] = 7
115 let ncorpus: i64 = 2
116
117 let slug: *u8 = "vptest" as *u8
118
119 // ---- build the three DISTINCT page bodies and stage them ----
120 let b1: *u8 = sys_mmap(4096)
121 let b2: *u8 = sys_mmap(4096)
122 let b3: *u8 = sys_mmap(4096)
123 let n1: i64 = g_build_page(b1, "ALPHA-v1" as *u8, goodcid)
124 let n2: i64 = g_build_page(b2, "BRAVO-v2-extra-bytes" as *u8, goodcid)
125 let n3: i64 = g_build_page(b3, "CHARLIE-v3" as *u8, goodcid)
126 let f1: *u8 = "/tmp/vp_v1.html" as *u8
127 let f2: *u8 = "/tmp/vp_v2.html" as *u8
128 let f3: *u8 = "/tmp/vp_v3.html" as *u8
129 if g_stage(f1, b1, n1) != 0 { g_w("VPGATE FATAL: stage v1\n" as *u8); sys_exit(2); return 2 }
130 if g_stage(f2, b2, n2) != 0 { g_w("VPGATE FATAL: stage v2\n" as *u8); sys_exit(2); return 2 }
131 if g_stage(f3, b3, n3) != 0 { g_w("VPGATE FATAL: stage v3\n" as *u8); sys_exit(2); return 2 }
132
133 var pass: i64 = 0
134 var rows: i64 = 0
135 let res: *PubResult = sys_mmap(64) as *PubResult
136 let vout: *i64 = sys_mmap(16) as *i64
137
138 // ============================================================================================
139 // C1: publish v1,v2,v3 (do_push_flag=0). Each VP_OK, version# 1,2,3.
140 // ============================================================================================
141 let r1: i64 = vpub_ex(prefix, slug, f1, cs_ptr, cs_len, ncorpus, 1781730256, 0, res, vout)
142 let v1n: i64 = vout[0]
143 // capture v1's retained CID RIGHT NOW (tiny durable identity) for the append-only proof later.
144 // We deliberately do NOT keep a RAM byte-snapshot across the later publishes/restores: the CID IS a
145 // sha256 of v1's bytes, so "CID unchanged AFTER v2,v3 + the blob still resolves to the ORIGINAL v1
146 // source bytes" is a cryptographically COMPLETE append-only proof (a mutated blob could not keep the
147 // same CID). This proof reads from the DURABLE store, not from process memory.
148 let v1cid_then: *u8 = sys_mmap(80)
149 vp_cid_of_version(prefix, slug, 1, v1cid_then)
150
151 let r2: i64 = vpub_ex(prefix, slug, f2, cs_ptr, cs_len, ncorpus, 1781730256, 0, res, vout)
152 let v2n: i64 = vout[0]
153 let r3: i64 = vpub_ex(prefix, slug, f3, cs_ptr, cs_len, ncorpus, 1781730256, 0, res, vout)
154 let v3n: i64 = vout[0]
155
156 var c1ok: i64 = 1
157 if r1 != VP_OK { c1ok = 0 }
158 if r2 != VP_OK { c1ok = 0 }
159 if r3 != VP_OK { c1ok = 0 }
160 if v1n != 1 { c1ok = 0 }
161 if v2n != 2 { c1ok = 0 }
162 if v3n != 3 { c1ok = 0 }
163 rows = rows + 1
164 g_w(" v1rc=" as *u8); g_w(vp_status_name(r1)); g_w(" v#=" as *u8); g_num(v1n)
165 g_w(" | v2rc=" as *u8); g_w(vp_status_name(r2)); g_w(" v#=" as *u8); g_num(v2n)
166 g_w(" | v3rc=" as *u8); g_w(vp_status_name(r3)); g_w(" v#=" as *u8); g_num(v3n); g_w("\n" as *u8)
167 pass = pass + g_row(1, c1ok, "publish v1,v2,v3 -> VP_OK, version#=1,2,3" as *u8)
168
169 // ============================================================================================
170 // C2: MULTI-POINT -- vp_list returns exactly 3 retained points with 3 DISTINCT CIDs.
171 // ============================================================================================
172 let vers: *i64 = sys_mmap(8 * 64) as *i64
173 let cidbuf: *u8 = sys_mmap(72 * 64)
174 let nlist: i64 = vp_list_ex(prefix, slug, vers, cidbuf, 64)
175 let cidA: *u8 = (cidbuf as i64 + 0 * 72) as *u8
176 let cidB: *u8 = (cidbuf as i64 + 1 * 72) as *u8
177 let cidC: *u8 = (cidbuf as i64 + 2 * 72) as *u8
178 var c2ok: i64 = 1
179 if nlist != 3 { c2ok = 0 }
180 if vers[0] != 1 { c2ok = 0 }
181 if vers[1] != 2 { c2ok = 0 }
182 if vers[2] != 3 { c2ok = 0 }
183 // distinct CIDs (distinct contents => distinct content addresses)
184 if g_streq(cidA, cidB) == 1 { c2ok = 0 }
185 if g_streq(cidA, cidC) == 1 { c2ok = 0 }
186 if g_streq(cidB, cidC) == 1 { c2ok = 0 }
187 rows = rows + 1
188 g_w(" vp_list count=" as *u8); g_num(nlist); g_w(" versions=[" as *u8)
189 g_num(vers[0]); g_w("," as *u8); g_num(vers[1]); g_w("," as *u8); g_num(vers[2]); g_w("]\n" as *u8)
190 g_w(" v1 cid=" as *u8); g_w(cidA); g_w("\n" as *u8)
191 g_w(" v2 cid=" as *u8); g_w(cidB); g_w("\n" as *u8)
192 g_w(" v3 cid=" as *u8); g_w(cidC); g_w("\n" as *u8)
193 pass = pass + g_row(2, c2ok, "vp_list = 3 DISTINCT retained rollback points (multi-point)" as *u8)
194
195 // ============================================================================================
196 // C3: restore -> v1 yields EXACT v1 bytes.
197 // ============================================================================================
198 let stage: *u8 = "/tmp/vp_restore.html" as *u8
199 let nout: *i64 = sys_mmap(16) as *i64
200 let rr1: i64 = restore_ex(prefix, slug, 1, stage, cs_ptr, cs_len, ncorpus, 0, res, nout)
201 // read back what was staged live and compare to the ORIGINAL v1 bytes.
202 let sbox: *i64 = sys_mmap(16) as *i64
203 let sback1: *u8 = sys_read_file(stage, sbox)
204 var c3ok: i64 = 1
205 if rr1 != VR_OK { c3ok = 0 }
206 if g_byteq(sback1, sbox[0], b1, n1) != 1 { c3ok = 0 }
207 rows = rows + 1
208 g_w(" restore(v1) rc=" as *u8); g_w(vr_status_name(rr1)); g_w(" staged_bytes=" as *u8); g_num(sbox[0]); g_w(" orig_v1=" as *u8); g_num(n1); g_w("\n" as *u8)
209 pass = pass + g_row(3, c3ok, "restore->v1 is byte-exact v1" as *u8)
210
211 // ============================================================================================
212 // C4: restore -> the MIDDLE v2 yields EXACT v2 bytes (roll back to ANY point, not just last).
213 // ============================================================================================
214 let rr2: i64 = restore_ex(prefix, slug, 2, stage, cs_ptr, cs_len, ncorpus, 0, res, nout)
215 let sback2: *u8 = sys_read_file(stage, sbox)
216 var c4ok: i64 = 1
217 if rr2 != VR_OK { c4ok = 0 }
218 if g_byteq(sback2, sbox[0], b2, n2) != 1 { c4ok = 0 }
219 // also assert it is NOT v1 or v3 (proves true mid-point selection)
220 if g_byteq(sback2, sbox[0], b1, n1) == 1 { c4ok = 0 }
221 if g_byteq(sback2, sbox[0], b3, n3) == 1 { c4ok = 0 }
222 rows = rows + 1
223 g_w(" restore(v2 MIDDLE) rc=" as *u8); g_w(vr_status_name(rr2)); g_w(" staged_bytes=" as *u8); g_num(sbox[0]); g_w(" orig_v2=" as *u8); g_num(n2); g_w("\n" as *u8)
224 pass = pass + g_row(4, c4ok, "restore->MIDDLE v2 is byte-exact v2 (any-point rollback)" as *u8)
225
226 // ============================================================================================
227 // C5: restore -> unknown v# (99) is REFUSED (VR_NOT_FOUND), push NOT invoked.
228 // ============================================================================================
229 // pre-set res to a sentinel so we can confirm restore did NOT invoke the push path.
230 res.push_invoked = 0 - 7
231 let rr99: i64 = restore_ex(prefix, slug, 99, stage, cs_ptr, cs_len, ncorpus, 0, res, nout)
232 var c5ok: i64 = 1
233 if rr99 != VR_NOT_FOUND { c5ok = 0 }
234 if res.push_invoked != (0 - 7) { c5ok = 0 } // unchanged sentinel => pub_publish was never reached
235 rows = rows + 1
236 g_w(" restore(v99 unknown) rc=" as *u8); g_w(vr_status_name(rr99)); g_w(" (push path NOT reached: sentinel intact=" as *u8)
237 if res.push_invoked == (0 - 7) { g_w("yes" as *u8) } else { g_w("NO" as *u8) }
238 g_w(")\n" as *u8)
239 pass = pass + g_row(5, c5ok, "restore->unknown v# REFUSED (VR_NOT_FOUND, no push)" as *u8)
240
241 // ============================================================================================
242 // C6: APPEND-ONLY -- v1's record is byte-identical AFTER v2 and v3 were published.
243 // ============================================================================================
244 let v1cid_now: *u8 = sys_mmap(80)
245 vp_cid_of_version(prefix, slug, 1, v1cid_now)
246 let v1pp2: *i64 = sys_mmap(16) as *i64
247 let v1blen_now: i64 = war_get_by_cid(prefix, v1cid_now, v1pp2, VR_SCAN_CAP)
248 var c6ok: i64 = 1
249 // (a) the retained CID for v1 is byte-identical to the CID captured right after v1 (index untouched).
250 let c6a: i64 = g_streq(v1cid_then, v1cid_now)
251 if c6a != 1 { c6ok = 0 }
252 // (b) the blob that CID resolves to AFTER v2,v3 is byte-identical to the ORIGINAL v1 source bytes.
253 // (a)+(b): CID unchanged AND blob == original content == a complete content-addressed proof that
254 // v1's record was neither mutated nor deleted by the later publishes.
255 let c6b: i64 = g_byteq(v1pp2[0] as *u8, v1blen_now, b1, n1)
256 if c6b != 1 { c6ok = 0 }
257 g_w(" C6 subchecks a(cid-stable)=" as *u8); g_num(c6a); g_w(" b(blob==orig-v1)=" as *u8); g_num(c6b); g_w("\n" as *u8)
258 rows = rows + 1
259 g_w(" v1 cid then=" as *u8); g_w(v1cid_then); g_w("\n now =" as *u8); g_w(v1cid_now); g_w("\n" as *u8)
260 g_w(" v1 blob now=" as *u8); g_num(v1blen_now); g_w(" orig=" as *u8); g_num(n1); g_w("\n" as *u8)
261 pass = pass + g_row(6, c6ok, "APPEND-ONLY: v1 record (CID+blob) byte-identical after v2,v3 (no mutate/delete)" as *u8)
262
263 // ============================================================================================
264 // C7: the three source contents are pairwise distinct (sanity for the distinct-CID claim).
265 // ============================================================================================
266 var c7ok: i64 = 1
267 if g_byteq(b1, n1, b2, n2) == 1 { c7ok = 0 }
268 if g_byteq(b1, n1, b3, n3) == 1 { c7ok = 0 }
269 if g_byteq(b2, n2, b3, n3) == 1 { c7ok = 0 }
270 rows = rows + 1
271 pass = pass + g_row(7, c7ok, "the 3 published contents are pairwise distinct" as *u8)
272
273 // ---- verdict ----
274 g_w("NX-WIKI-VERSIONED-PUBLISH-GATE rows=" as *u8); g_num(rows); g_w(" pass=" as *u8); g_num(pass); g_w("\n" as *u8)
275 if pass == rows {
276 let line: *u8 = sys_mmap(256)
277 var off: i64 = g_cat(line, 0, "WIKIVERSIONEDPUBLISH row=nx_wiki_versioned_publish multi-point-rollback rows=" as *u8)
278 off = g_catnum(line, off, rows); off = g_cat(line, off, " pass=" as *u8); off = g_catnum(line, off, pass)
279 off = g_cat(line, off, " evidence=3-retained+restore-middle+append-only verdict=PASS\n" as *u8)
280 let gf: i64 = sys_openat_append("knowledge/status/wiki_versioned_publish_gate.log" as *u8, 0x1a4)
281 if gf >= 0 { sys_write(gf, line, off); sys_close(gf) }
282 g_w("NX-WIKI-VERSIONED-PUBLISH-GATE verdict=PASS\n" as *u8)
283 sys_exit(0); return 0
284 }
285 g_w("NX-WIKI-VERSIONED-PUBLISH-GATE verdict=FAIL -- NOT recorded (no fake-green)\n" as *u8)
286 sys_exit(1); return 1
287}