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}