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}