code wiki / (root) / nx_legal_store.nx

nx_legal_store.nx source

↩ module page · 125 lines · 5373 B

1// nx_legal_store.nx -- LEGAL RUNG D7: durable persistence for the legal stores. 2// 3// module: nishi-core.legal.store 4// capability: LEGAL_DOC_PERSIST 5// 6// The logic cores (D1 vault / D2 envelope+recipients / D4 annotations) hold their 7// state in caller-allocated flat i64 record arrays -- correct, but lost on a 8// restart. This rung makes them DURABLE: serialize a flat array to a per-tenant 9// file with an integrity header, and load it back byte-identical so the portal 10// daemon survives a restart. 11// 12// FORMAT : [magic i64][stride i64][count i64] then count*stride i64 records. 13// INTEGRITY: load verifies the magic AND that the stored stride matches the 14// caller's expected stride -- so a vault file can never be misread as 15// an envelope file (different stride) -> silent corruption refused. 16// PER-TENANT: the path is <base><tenant>.<name>, tenant validated [A-Za-z0-9_-] 17// (nx_vault_valid_tid, the proven discipline) -> no cross-tenant 18// traversal; one tenant can never load another's store. 19// 20// HONEST scope: this proves the persistence CONTRACT (save/load/survive-restart/ 21// tenant-isolated/integrity-checked) over a plain per-tenant file. The production 22// path composes nx_seg_store (the same durable, concurrent-safe segmented store 23// the email mailbox already persists to) -- a pattern-covered refinement, not a 24// new primitive. 25// 26// Composes: nx_doc_vault (nx_vault_valid_tid tenant discipline). license_tier: ORIGINAL 27// lineage_id: nishi_legal_store_d7 28import "nx_syscalls.nx" 29import "nx_doc_vault.nx" 30 31const LS_MAGIC: i64 = 0x4E584C5331 // "NXLS1" -- recognizes a legal-store file 32const LS_OK: i64 = 0 33const LS_ERR_TENANT: i64 = 1 34const LS_ERR_OPEN: i64 = 2 35const LS_ERR_MAGIC: i64 = 3 36const LS_ERR_STRIDE: i64 = 4 37 38func ls_cat(out: *u8, o: i64, s: *u8) -> i64 { var k: i64 = 0; while s[k] != (0 as u8) { out[o] = s[k]; o = o + 1; k = k + 1 } return o } 39 40// build the per-tenant store path <base><tenant>.<name> (tenant validated). -1 on bad tenant. 41func ls_path(out: *u8, base: *u8, tenant: *u8, name: *u8) -> i64 { 42 if nx_vault_valid_tid(tenant) == 0 { return 0 - 1 } 43 var o: i64 = 0 44 o = ls_cat(out, o, base) 45 o = ls_cat(out, o, tenant) 46 out[o] = 46 as u8; o = o + 1 // '.' 47 o = ls_cat(out, o, name) 48 out[o] = 0 as u8 49 return o 50} 51 52// persist a flat record array (count records of `stride` i64 each) to path. Returns LS_OK / LS_ERR_*. 53func ls_save(flat: *i64, count: i64, stride: i64, path: *u8) -> i64 { 54 let fd: i64 = sys_openat_wr(path, 420) 55 if fd < 0 { return LS_ERR_OPEN } 56 let hdr: *i64 = sys_mmap(32) as *i64 57 hdr[0] = LS_MAGIC; hdr[1] = stride; hdr[2] = count 58 sys_write(fd, hdr as *u8, 24) 59 if count > 0 { sys_write(fd, flat as *u8, count * stride * 8) } 60 sys_close(fd) 61 return LS_OK 62} 63 64// load records from path into flat (capacity cap records, expected `stride`). Returns the record 65// count, or -1 if absent, or -LS_ERR_* on a corrupt/mismatched file (never silent garbage). 66func ls_load(flat: *i64, cap: i64, stride: i64, path: *u8) -> i64 { 67 let fd: i64 = sys_openat_rd(path) 68 if fd < 0 { return 0 - 1 } 69 let hdr: *i64 = sys_mmap(32) as *i64 70 let hr: i64 = sys_read(fd, hdr as *u8, 24) 71 if hr != 24 { sys_close(fd); return 0 - LS_ERR_MAGIC } 72 if hdr[0] != LS_MAGIC { sys_close(fd); return 0 - LS_ERR_MAGIC } 73 if hdr[1] != stride { sys_close(fd); return 0 - LS_ERR_STRIDE } 74 let count: i64 = hdr[2] 75 if count > cap { sys_close(fd); return 0 - LS_ERR_STRIDE } 76 if count > 0 { 77 let want: i64 = count * stride * 8 78 var got: i64 = 0 79 while got < want { 80 let r: i64 = sys_read(fd, (flat as i64 + got) as *u8, want - got) 81 if r <= 0 { sys_close(fd); return 0 - LS_ERR_MAGIC } 82 got = got + r 83 } 84 } 85 sys_close(fd) 86 return count 87} 88 89const LS_BMAGIC: i64 = 0x4E584C42 // "NXLB" -- recognizes a legal-blob (raw document bytes) file 90 91// persist a raw document BLOB (the actual bytes, not an i64 record array) to path. 92// [magic i64][len i64][bytes]. Returns LS_OK / LS_ERR_*. 93func ls_blob_save(bytes: *u8, len: i64, path: *u8) -> i64 { 94 let fd: i64 = sys_openat_wr(path, 420) 95 if fd < 0 { return LS_ERR_OPEN } 96 let hdr: *i64 = sys_mmap(32) as *i64 97 hdr[0] = LS_BMAGIC; hdr[1] = len 98 sys_write(fd, hdr as *u8, 16) 99 if len > 0 { sys_write(fd, bytes, len) } 100 sys_close(fd) 101 return LS_OK 102} 103 104// load a document blob from path into out (capacity cap). Returns the byte length, 105// or -1 if absent, or -LS_ERR_* on a corrupt/oversized file (never silent garbage). 106func ls_blob_load(out: *u8, cap: i64, path: *u8) -> i64 { 107 let fd: i64 = sys_openat_rd(path) 108 if fd < 0 { return 0 - 1 } 109 let hdr: *i64 = sys_mmap(32) as *i64 110 let hr: i64 = sys_read(fd, hdr as *u8, 16) 111 if hr != 16 { sys_close(fd); return 0 - LS_ERR_MAGIC } 112 if hdr[0] != LS_BMAGIC { sys_close(fd); return 0 - LS_ERR_MAGIC } 113 let len: i64 = hdr[1] 114 if len > cap { sys_close(fd); return 0 - LS_ERR_STRIDE } 115 if len > 0 { 116 var got: i64 = 0 117 while got < len { 118 let r: i64 = sys_read(fd, (out as i64 + got) as *u8, len - got) 119 if r <= 0 { sys_close(fd); return 0 - LS_ERR_MAGIC } 120 got = got + r 121 } 122 } 123 sys_close(fd) 124 return len 125}