code wiki / (root) / nx_doc_vault.nx

nx_doc_vault.nx source

↩ module page · 177 lines · 7012 B

1// nx_doc_vault.nx -- LEGAL RUNG D1: per-tenant document vault (retention core). 2// 3// module: nishi-core.legal.doc_vault 4// capability: LEGAL_DOC_VAULT 5// 6// The storage + audit home for D5 seals, encoding the registry's retention 7// requirements as invariants BY CONSTRUCTION: 8// ADDITIVE-ONLY (Rule 13) -- a new version never overwrites or 9// removes an old one; soft-delete only 10// demotes is_current. History is sacred. 11// SINGLE AUTHORITATIVE COPY -- exactly one is_current=1 per doc 12// (UETA Section 16) = the authoritative copy. 13// VERSIONED + TAMPER-EVIDENT -- each version links prev_hash == 14// (UETA Section 12) prior version's content_hash, so any 15// alteration of a non-final version 16// breaks the chain (the I/O layer adds 17// recompute-SHA256-vs-stored for the 18// final version). 19// PER-TENANT ISOLATION -- vault index lives at the tenant's 20// (MRPC 1.6 confidentiality) private prefix <base><tid>.docvault; 21// tenant-id validated [A-Za-z0-9_-] only 22// (no '.'/'/' -> no cross-tenant traversal), 23// the proven nx_vizsla_tenant discipline. 24// 25// Representation: a flat i64 record array (caller-allocated; substrate scale- 26// agnostic, no internal allocation) -- record r, field f at flat[r*VF_STRIDE+f]. 27// content_hash is an i64 in this logic core; the I/O layer stores the full 28// SHA-256 digest and the chain comparison is identical. 29// 30// Composes: nx_syscalls (tenant-id validation). Distinct from nx_vizsla_tenant 31// because: that shards CONTACTS/relate data; this shards VERSIONED DOCUMENTS 32// with a tamper-evident chain. license_tier: ORIGINAL 33// lineage_id: nishi_doc_vault_d1 34import "nx_syscalls.nx" 35 36// ---- status codes ---- 37const VAULT_OK: i64 = 0 38const VAULT_CHAIN_BROKEN: i64 = 1 39const VAULT_NOT_FOUND: i64 = 2 40const VAULT_FULL: i64 = 3 41 42// ---- record field layout (flat[r*VF_STRIDE + VF_*]) ---- 43const VF_DOC: i64 = 0 // document id (logical doc, e.g. a name hash) 44const VF_VER: i64 = 1 // version number (1,2,3...) 45const VF_HASH: i64 = 2 // content hash (SHA-256 in the I/O layer) 46const VF_SIZE: i64 = 3 // content size in bytes 47const VF_CUR: i64 = 4 // 1 = current authoritative copy, 0 = superseded/retired 48const VF_TS: i64 = 5 // timestamp 49const VF_PREV: i64 = 6 // prev version's content hash (0 for v1) -- the chain link 50const VF_SEAL: i64 = 7 // 1 = a D5 e-sign seal is attached to this version 51const VF_STRIDE: i64 = 8 52 53// ---- add a new version of doc_id (additive: demote prior current, never remove). ---- 54// Returns the new record count, or -VAULT_FULL. 55func nx_vault_add(flat: *i64, count: i64, cap: i64, doc_id: i64, chash: i64, size: i64, ts: i64) -> i64 { 56 if count >= cap { return 0 - VAULT_FULL } 57 var prev_ver: i64 = 0 58 var prev_h: i64 = 0 59 var i: i64 = 0 60 while i < count { 61 let b: i64 = i * VF_STRIDE 62 if flat[b + VF_DOC] == doc_id { 63 if flat[b + VF_CUR] == 1 { 64 flat[b + VF_CUR] = 0 // demote the prior authoritative copy (kept, not removed) 65 prev_ver = flat[b + VF_VER] 66 prev_h = flat[b + VF_HASH] 67 } 68 } 69 i = i + 1 70 } 71 let nb: i64 = count * VF_STRIDE 72 flat[nb + VF_DOC] = doc_id 73 flat[nb + VF_VER] = prev_ver + 1 74 flat[nb + VF_HASH] = chash 75 flat[nb + VF_SIZE] = size 76 flat[nb + VF_CUR] = 1 77 flat[nb + VF_TS] = ts 78 flat[nb + VF_PREV] = prev_h 79 flat[nb + VF_SEAL] = 0 80 return count + 1 81} 82 83// ---- index of the current (authoritative) version of doc_id, or -1. ---- 84func nx_vault_current_idx(flat: *i64, count: i64, doc_id: i64) -> i64 { 85 var i: i64 = 0 86 while i < count { 87 let b: i64 = i * VF_STRIDE 88 if flat[b + VF_DOC] == doc_id { if flat[b + VF_CUR] == 1 { return i } } 89 i = i + 1 90 } 91 return 0 - 1 92} 93 94// ---- how many versions of doc_id exist (history depth -- additive proof). ---- 95func nx_vault_version_count(flat: *i64, count: i64, doc_id: i64) -> i64 { 96 var n: i64 = 0 97 var i: i64 = 0 98 while i < count { 99 if flat[i * VF_STRIDE + VF_DOC] == doc_id { n = n + 1 } 100 i = i + 1 101 } 102 return n 103} 104 105// ---- how many CURRENT versions of doc_id (must be exactly 1 -- single-authoritative). ---- 106func nx_vault_current_count(flat: *i64, count: i64, doc_id: i64) -> i64 { 107 var n: i64 = 0 108 var i: i64 = 0 109 while i < count { 110 let b: i64 = i * VF_STRIDE 111 if flat[b + VF_DOC] == doc_id { if flat[b + VF_CUR] == 1 { n = n + 1 } } 112 i = i + 1 113 } 114 return n 115} 116 117// ---- verify the tamper-evident chain for doc_id (each prev_hash links). ---- 118func nx_vault_verify_chain(flat: *i64, count: i64, doc_id: i64) -> i64 { 119 var expect_ver: i64 = 1 120 var prev_h: i64 = 0 121 var go: i64 = 1 122 while go == 1 { 123 var idx: i64 = 0 - 1 124 var i: i64 = 0 125 while i < count { 126 let b: i64 = i * VF_STRIDE 127 if flat[b + VF_DOC] == doc_id { if flat[b + VF_VER] == expect_ver { idx = i } } 128 i = i + 1 129 } 130 if idx < 0 { 131 go = 0 132 } else { 133 let b: i64 = idx * VF_STRIDE 134 if flat[b + VF_PREV] != prev_h { return VAULT_CHAIN_BROKEN } 135 prev_h = flat[b + VF_HASH] 136 expect_ver = expect_ver + 1 137 } 138 } 139 if expect_ver == 1 { return VAULT_NOT_FOUND } 140 return VAULT_OK 141} 142 143// ---- soft-delete: demote the current version (additive -- history retained). ---- 144func nx_vault_soft_delete(flat: *i64, count: i64, doc_id: i64) -> i64 { 145 let idx: i64 = nx_vault_current_idx(flat, count, doc_id) 146 if idx < 0 { return VAULT_NOT_FOUND } 147 flat[idx * VF_STRIDE + VF_CUR] = 0 148 return VAULT_OK 149} 150 151// ---- attach a D5 seal to the current version of doc_id. ---- 152func nx_vault_attach_seal(flat: *i64, count: i64, doc_id: i64) -> i64 { 153 let idx: i64 = nx_vault_current_idx(flat, count, doc_id) 154 if idx < 0 { return VAULT_NOT_FOUND } 155 flat[idx * VF_STRIDE + VF_SEAL] = 1 156 return VAULT_OK 157} 158 159// ---- tenant-id validation: nonempty, [A-Za-z0-9_-] only (no '.' or '/'). ---- 160// The nx_vizsla_tenant discipline: a tenant can never path-traverse into 161// another tenant's vault. 162func nx_vault_valid_tid(s: *u8) -> i64 { 163 if s[0] == (0 as u8) { return 0 } 164 var i: i64 = 0 165 while s[i] != (0 as u8) { 166 let c: i64 = s[i] as i64 167 var ok: i64 = 0 168 if c >= 48 { if c <= 57 { ok = 1 } } 169 if c >= 65 { if c <= 90 { ok = 1 } } 170 if c >= 97 { if c <= 122 { ok = 1 } } 171 if c == 45 { ok = 1 } 172 if c == 95 { ok = 1 } 173 if ok == 0 { return 0 } 174 i = i + 1 175 } 176 return 1 177}