code wiki / _hdl_build / nx_wiki_restore.nx

nx_wiki_restore.nx source

↩ module page · 115 lines · 6343 B

1// nx_wiki_restore.nx -- A4a: FULL RESTORE to ANY retained rollback point. 2// 3// THE OPERATOR PRECONDITION (verbatim intent): roll back "not just to another broken one but have FULL 4// RESTORE via MULTIPLE rollback points." restore(slug, v#) makes the LIVE page byte-exact the content of 5// version v# -- ANY v#, not just the last. The bytes come from the content-addressed archive by CID, so 6// what gets republished is GUARANTEED identical to what was captured (a CID names exactly one byte string). 7// 8// COMPOSITION (no storage/crypto reinvented): 9// nx_wiki_versioned_publish vp_cid_of_version (resolve v# -> CID from the durable version index) 10// nx_wiki_archive war_get_by_cid (no-loss retrieval of the EXACT bytes by CID) 11// nx_wiki_publish pub_publish_ex (A3 GUARDED re-publish -- the restore goes live ONLY if 12// the A2 guard re-ALLOWs it; a restore is itself a guarded publish) 13// nx_seg_store (transitively, via the archive store) 14// 15// ADDITIVE: a restore writes NOTHING destructive. It reads an immutable blob and re-publishes it; the 16// version index is unchanged (the restored content already has its own retained v#). It is also safe to 17// pair with vpub -- a caller that wants the restore itself recorded as a NEW head simply vpub's the 18// restored bytes afterward (the live-proof runner does exactly this is NOT required: restore re-pushes the 19// exact old bytes; re-capturing is optional and additive). 20// 21// REFUSAL (liar-kill): an unknown v# returns VR_NOT_FOUND and the push path is NEVER reached -- a restore 22// cannot silently push garbage for a version that was never retained. 23// 24// Pure NishiLang, NO sql/.sh/.py/.js, no new .tsv/.conf. license_tier: ORIGINAL 25import "nx_wiki_versioned_publish.nx" 26import "nx_wiki_archive.nx" 27import "nx_wiki_publish.nx" 28import "nx_syscalls.nx" 29 30// ===== sealed verdict surface (codes 4950-4959) =================================================== 31const VR_OK: i64 = 0 32const VR_BAD_INPUT: i64 = 0 - 4950 // null/empty slug, or v# < 1 33const VR_NOT_FOUND: i64 = 0 - 4951 // requested version# has no retained record (REFUSED, no push) 34const VR_BLOB_MISSING: i64 = 0 - 4952 // CID retained but its blob could not be resolved (store damage) 35const VR_WRITE_FAILED: i64 = 0 - 4953 // could not stage the restored bytes to a local file 36const VR_PUSH_REJECT: i64 = 0 - 4954 // A3 guard REJECTED the restored page (fail-closed) 37 38const VR_SCAN_CAP: i64 = 256 // segment-scan cap for war_get_by_cid / version-index lookups 39const VR_MODE_0644: i64 = 0x1a4 40 41func vr_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 42 43// write buf[0..n] to path (truncating prior content); 0 ok, <0 on open/short-write. 44func vr_write_file(path: *u8, buf: *u8, n: i64) -> i64 { 45 let fd: i64 = sys_openat_wr(path, VR_MODE_0644) 46 if fd < 0 { return 0 - 1 } 47 var off: i64 = 0 48 while off < n { 49 let wr: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off) 50 if wr <= 0 { sys_close(fd); return 0 - 2 } 51 off = off + wr 52 } 53 sys_close(fd) 54 return 0 55} 56 57// ===== restore_ex: resolve v# -> CID -> exact bytes -> stage -> guarded re-publish ================= 58// prefix = the archive/index store prefix (production: WAR_PREFIX). 59// slug = the page slug to restore. 60// version = the retained version# to restore TO (1-based; ANY retained point). 61// stage_path = a local file path the exact bytes are written to before pub_publish_ex reads them back. 62// cs_*/ncorpus= the corpus set the guard checks the page's /wiki links against. 63// do_push_flag= 1 to actually push live; 0 to exercise the byte-path hermetically (guard verdict still 64// enforced, nothing pushed). 65// out = receives the A3 push verdict. nout[0] = restored byte length (>=0). Returns VR_OK or a 66// negative VR_* code. On an unknown v#, returns VR_NOT_FOUND WITHOUT touching the push path. 67func restore_ex(prefix: *u8, slug: *u8, version: i64, stage_path: *u8, 68 cs_ptr: *i64, cs_len: *i64, ncorpus: i64, 69 do_push_flag: i64, out: *PubResult, nout: *i64) -> i64 { 70 nout[0] = 0 71 if (slug as i64) == 0 { return VR_BAD_INPUT } 72 if vr_len(slug) < 1 { return VR_BAD_INPUT } 73 if (stage_path as i64) == 0 { return VR_BAD_INPUT } 74 if version < 1 { return VR_BAD_INPUT } 75 76 // 1. resolve v# -> retained CID (REFUSE an unknown version BEFORE any push). 77 let cid: *u8 = sys_mmap(80) 78 let cl: i64 = vp_cid_of_version(prefix, slug, version, cid) 79 if cl <= 0 { return VR_NOT_FOUND } 80 81 // 2. pull the EXACT bytes by CID (content-addressed => byte-identical to what was captured). 82 let pp: *i64 = sys_mmap(16) as *i64 83 let blen: i64 = war_get_by_cid(prefix, cid, pp, VR_SCAN_CAP) 84 if blen <= 0 { return VR_BLOB_MISSING } 85 let body: *u8 = pp[0] as *u8 86 87 // 3. stage the exact bytes to a local file so the guarded publish reads them like a production page. 88 if vr_write_file(stage_path, body, blen) != 0 { return VR_WRITE_FAILED } 89 nout[0] = blen 90 91 // 4. GUARDED re-publish (A3): the restored page goes live ONLY if the A2 guard re-ALLOWs it. 92 let pubrc: i64 = pub_publish_ex(prefix, stage_path, slug, 93 cs_ptr, cs_len, ncorpus, do_push_flag, out) 94 if pubrc == PUB_REJECTED { return VR_PUSH_REJECT } 95 return VR_OK 96} 97 98// production convenience: prefix = WAR_PREFIX (live durable store). 99func restore(slug: *u8, version: i64, stage_path: *u8, 100 cs_ptr: *i64, cs_len: *i64, ncorpus: i64, 101 do_push_flag: i64, out: *PubResult, nout: *i64) -> i64 { 102 return restore_ex(WAR_PREFIX, slug, version, stage_path, cs_ptr, cs_len, ncorpus, 103 do_push_flag, out, nout) 104} 105 106// human-readable verdict name (for the live-proof runner / logs). 107func vr_status_name(s: i64) -> *u8 { 108 if s == VR_OK { return "VR_OK" as *u8 } 109 if s == VR_BAD_INPUT { return "VR_BAD_INPUT" as *u8 } 110 if s == VR_NOT_FOUND { return "VR_NOT_FOUND" as *u8 } 111 if s == VR_BLOB_MISSING { return "VR_BLOB_MISSING" as *u8 } 112 if s == VR_WRITE_FAILED { return "VR_WRITE_FAILED" as *u8 } 113 if s == VR_PUSH_REJECT { return "VR_PUSH_REJECT" as *u8 } 114 return "VR_UNKNOWN" as *u8 115}