code wiki / _hdl_build / nx_dr_retention.nx

nx_dr_retention.nx source

↩ module page · 95 lines · 7315 B

1// nx_dr_retention.nx -- CAP-DR-RETENTION (sovereign-format): versioned snapshot history + keep-policy on the 2// SOVEREIGN nx_seg_store (.docs append-only + .keys binary-search) -- NO TSV. A snapshot = a point-in-time 3// fingerprint of the archive = sha256(receipt.docs). Stored as key "snap-<id>" -> "<id> <epoch> <fp>", with a 4// "count" key so `policy` iterates snap-1..count via point lookups. ADDITIVE (append-only; never deletes -- history 5// sacred; prune is an explicit operator step). restic-`forget`-style keep-last-N. 6// nx_dr_retention snapshot <archive-dir> 7// nx_dr_retention policy <archive-dir> <keepN> 8// license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_sha256.nx" 11import "nx_seg_store.nx" 12const K_MAGIC_1536: i64 = 1536 13const K_MAGIC_4194304: i64 = 4194304 14 15func rt_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 16func rt_wn(v: i64) -> i64 { var t: i64=v; if t<0{sys_write(1,"-" as *u8,1);t=0-t} let tm:*u8=sys_mmap(24); var k:i64=0; if t==0{tm[0]=48 as u8;k=1} while t>0{tm[k]=(48+(t%10)) as u8;t=t/10;k=k+1} let b:*u8=sys_mmap(24); var j:i64=0; while j<k{b[j]=tm[k-1-j];j=j+1} sys_write(1,b,k); return 0 } 17func rt_read(path: *u8, out: *u8, cap: i64) -> i64 { 18 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 - 1 } 19 var total: i64 = 0; var go: i64 = 1 20 while go == 1 { let nr: i64 = sys_read(fd, (out as i64 + total) as *u8, cap - total); if nr <= 0 { go = 0 } if nr > 0 { total = total + nr } if total >= cap { go = 0 } } 21 sys_close(fd); return total 22} 23func rt_join(out: *u8, dir: *u8, name: *u8) -> i64 { var o: i64=0; var i: i64=0; while dir[i]!=(0 as u8){out[o]=dir[i];o=o+1;i=i+1} out[o]=47 as u8; o=o+1; i=0; while name[i]!=(0 as u8){out[o]=name[i];o=o+1;i=i+1} out[o]=0 as u8; return o } 24func rt_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 0 } return 1 } 25func rt_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8) { if s[i]>=(48 as u8) { if s[i]<=(57 as u8) { v=v*10+((s[i] as i64)-48) } } i=i+1 } return v } 26func rt_itoa(v: i64, out: *u8) -> i64 { var t: i64=v; if t<0{t=0-t} let tm:*u8=sys_mmap(24); var k:i64=0; if t==0{tm[0]=48 as u8;k=1} while t>0{tm[k]=(48+(t%10)) as u8;t=t/10;k=k+1} var i:i64=0; while i<k{out[i]=tm[k-1-i];i=i+1} out[k]=0 as u8; return k } 27func rt_is_keep(idx: i64, total: i64, keepN: i64) -> i64 { if idx >= total - keepN { return 1 } return 0 } 28// key "snap-<id>" (NUL-terminated); returns length. 29func rt_snapkey(id: i64, out: *u8) -> i64 { out[0]=115 as u8; out[1]=110 as u8; out[2]=97 as u8; out[3]=112 as u8; out[4]=45 as u8; let l: i64=rt_itoa(id, (out as i64 + 5) as *u8); return 5 + l } 30func rt_getcount(sprefix: *u8) -> i64 { 31 let pq: *i64=sys_mmap(16) as *i64; let lq: *i64=sys_mmap(16) as *i64 32 if ss_get(sprefix, "count" as *u8, pq, lq) == 1 { let v: *u8=pq[0] as *u8; let vl: i64=lq[0]; let b: *u8=sys_mmap(32); var i: i64=0; while i<vl { if i<31 { b[i]=v[i] } i=i+1 } b[vl]=0 as u8; return rt_atoi(b) } 33 return 0 34} 35 36func main(argc: i64, argv: *i64) -> i64 { 37 if argc < 3 { rt_w("usage: nx_dr_retention snapshot <archive-dir> | policy <archive-dir> <keepN>\n" as *u8); sys_exit(2); return 2 } 38 let mode: *u8 = argv[1] as *u8 39 let adir: *u8 = argv[2] as *u8 40 let sprefix: *u8 = sys_mmap(K_MAGIC_1536); rt_join(sprefix, adir, "snapshots" as *u8) 41 42 if rt_streq(mode, "snapshot" as *u8) == 1 { 43 // fingerprint = sha256(latest receipt segment) -- sovereign receipt (nx_seg_store; segment name via ss_segname) 44 let rprefix: *u8 = sys_mmap(K_MAGIC_1536); rt_join(rprefix, adir, "receipt" as *u8) 45 let rsegs: *i64 = sys_mmap(8*260) as *i64; let rnext: i64 = ss_manifest(rprefix, rsegs) 46 if rnext <= 0 { rt_w(" FATAL: no sovereign receipt (run nx_dr_archive first)\n" as *u8); sys_exit(3); return 3 } 47 let docp: *u8 = sys_mmap(K_MAGIC_1536); ss_segname(rprefix, rnext - 1, 0, docp) 48 let buf: *u8 = sys_mmap(K_MAGIC_4194304); let n: i64 = rt_read(docp, buf, K_MAGIC_4194304) 49 if n < 0 { rt_w(" FATAL: cannot read receipt segment\n" as *u8); sys_exit(3); return 3 } 50 let dig: *u8 = sys_mmap(64); sha256_digest(buf, n, dig) 51 let hx: *u8 = "0123456789abcdef" as *u8; let fp: *u8 = sys_mmap(40) 52 var h: i64=0; while h<8 { let by: i64=(dig[h] as i64)&0xff; fp[h*2]=hx[(by>>4)&15]; fp[h*2+1]=hx[by&15]; h=h+1 } fp[16]=0 as u8 53 let cnt: i64 = rt_getcount(sprefix); let id: i64 = cnt + 1 54 let epoch: i64 = sys_now_ms() / 1000 55 // record = "<id> <epoch> <fp16>" 56 let rec: *u8 = sys_mmap(128); var p: i64=0 57 p = p + rt_itoa(id, (rec as i64 + p) as *u8); rec[p]=32 as u8; p=p+1 58 p = p + rt_itoa(epoch, (rec as i64 + p) as *u8); rec[p]=32 as u8; p=p+1 59 var f: i64=0; while f<16 { rec[p]=fp[f]; p=p+1; f=f+1 } 60 let skey: *u8 = sys_mmap(32); let skl: i64 = rt_snapkey(id, skey) 61 let idstr: *u8 = sys_mmap(32); let idl: i64 = rt_itoa(id, idstr) 62 // ROOT FIX (count-as-segid, id 1785519700): ss_manifest RETURNS THE ROW COUNT, not an id; past 63 // SS_MANIFEST_LEGACY_CAP (256) it PINS and every later write reuses id 257, OVERWRITING the prior 64 // segment (data loss by CLOBBER). NOTE the sibling call at :43 is NOT this bug and must NOT be 65 // converted -- rnext there is a genuine COUNT (if rnext <= 0; ss_segname(..., rnext - 1, ...) = 66 // address the newest segment by ordinal). Same call shape, opposite meaning: read the USE, not the call. 67 let segid: i64 = ss_next_segid(sprefix) 68 let w: *i64 = ss_begin() 69 ss_add(w, 1, skey, rec, p) 70 ss_add(w, 1, "count" as *u8, idstr, idl) 71 ss_commit(sprefix, w, segid) 72 rt_w(" SNAPSHOT id=" as *u8); rt_wn(id); rt_w(" receipt-fp=" as *u8); sys_write(1, fp, 16); rt_w(".. -> snapshots.docs/.keys (sovereign, append-only, NO TSV)\n" as *u8) 73 sys_exit(0); return 0 74 } 75 if rt_streq(mode, "policy" as *u8) == 1 { 76 if argc < 4 { rt_w("usage: nx_dr_retention policy <archive-dir> <keepN>\n" as *u8); sys_exit(2); return 2 } 77 let keepN: i64 = rt_atoi(argv[3] as *u8) 78 let total: i64 = rt_getcount(sprefix) 79 rt_w("=== retention policy: keep newest " as *u8); rt_wn(keepN); rt_w(" of " as *u8); rt_wn(total); rt_w(" snapshots (ADDITIVE -- report only, no delete) ===\n" as *u8) 80 let pq: *i64=sys_mmap(16) as *i64; let lq: *i64=sys_mmap(16) as *i64; let skey: *u8=sys_mmap(32) 81 var id: i64=1; var kept: i64=0; var prune: i64=0 82 while id <= total { 83 rt_snapkey(id, skey) 84 if ss_get(sprefix, skey, pq, lq) == 1 { 85 let k: i64 = rt_is_keep(id - 1, total, keepN) 86 rt_w(" " as *u8); if k==1 { rt_w("KEEP " as *u8); kept=kept+1 } else { rt_w("prune-eligible " as *u8); prune=prune+1 } 87 sys_write(1, pq[0] as *u8, lq[0]); rt_w("\n" as *u8) 88 } 89 id = id + 1 90 } 91 rt_w(" ---- keep=" as *u8); rt_wn(kept); rt_w(" prune-eligible=" as *u8); rt_wn(prune); rt_w(" (prune is an explicit operator step -- history sacred)\n" as *u8) 92 sys_exit(0); return 0 93 } 94 rt_w("nx_dr_retention: unknown mode (snapshot|policy)\n" as *u8); sys_exit(2); return 2 95}