code wiki / (root) / nx_hold_lib.nx

nx_hold_lib.nx source

↩ module page · 274 lines · 10705 B

1// nx_hold_lib.nx -- LEGAL HOLD / PRESERVATION (the EDRM LEFT SIDE) + the FRCP 37(e) sanction ladder. 2// nx_ediscovery screens what goes OUT (production is an allow-list). This is the other half, and it is 3// the half that ends cases: evidence destroyed before review ever happens. You cannot screen a document 4// you already deleted. 5// 6// THE DUTY (Zubulake / FRCP 37(e)): the obligation to preserve attaches when litigation is REASONABLY 7// ANTICIPATED -- not when a complaint is filed, not when counsel is retained. Anticipation is normally 8// the EARLIER date, so the duty runs from the MINIMUM of the two triggers. Anchoring preservation to 9// the filing date is the most common preservation error and it silently discards months of evidence. 10// 11// THE TOOTH THAT MATTERS MOST: issuing a hold notice is NOT preservation. Three things are separate and 12// all three are required -- the NOTICE went out, every custodian ACKNOWLEDGED it, and automated 13// DELETION was actually SUSPENDED on every system. A notice sent while the email retention policy keeps 14// purging on schedule is a hold that exists only on paper. Systems conflate the notice with the 15// suspension constantly, so they are counted independently and defensibility never rests on the notice. 16// 17// THE SAFE HARBOUR, ENCODED EXACTLY: FRCP 37(e) does NOT punish lost ESI as such. Sanctions require ALL 18// of -- ESI was lost, REASONABLE STEPS were not taken, and it cannot be restored or replaced. If 19// reasonable steps WERE taken the answer is no sanctions even though data is gone. Severe measures 20// (adverse-inference, dismissal) require an additional finding of INTENT TO DEPRIVE; negligence, 21// however gross, tops out at curative. Collapsing those tiers turns a discovery dispute into a lost case. 22// 23// STRUCTURE: a PURE DECISION CORE (rules, no I/O) plus REGISTRY ADAPTERS. The rules gate with zero 24// writes so a storage stall cannot make them unverifiable (debt 1785519597), and each rule exists once. 25// SCALE ENVELOPE (declared): the matter record is ONE packed canonical record; one record per custodian. 26// DRY: composes nx_matter_lib. license_tier: ORIGINAL LIB. 27 28import "nx_matter_lib.nx" 29 30const HD_UNSET: i64 = 0 - 2000000002 31 32const HD_NO_SANCTION: i64 = 0 33const HD_CURATIVE: i64 = 1 34const HD_SEVERE: i64 = 2 35 36const HD_RECBUF: i64 = 1024 37const HD_IDBUF: i64 = 256 38 39// ============================================================================ 40// PURE DECISION CORE 41// ============================================================================ 42 43func hd_is1(v: i64) -> i64 { 44 if v == 1 { return 1 } 45 return 0 46} 47 48// THE DUTY DATE = the EARLIER of reasonable anticipation and filing. 49func hd_duty_pure(anticipated: i64, filed: i64) -> i64 { 50 if anticipated == HD_UNSET { return filed } 51 if filed == HD_UNSET { return anticipated } 52 if anticipated < filed { return anticipated } 53 return filed 54} 55 56func hd_duty_attached_pure(duty: i64, asof: i64) -> i64 { 57 if duty == HD_UNSET { return 0 } 58 if asof == HD_UNSET { return 0 } 59 if asof >= duty { return 1 } 60 return 0 61} 62 63// systems still auto-deleting. An unknown total is UNSET, never silently zero; an unknown suspended 64// count means NONE are suspended, which is the fail-safe reading. 65func hd_unsuspended_pure(total: i64, suspended: i64) -> i64 { 66 if total == HD_UNSET { return HD_UNSET } 67 if suspended == HD_UNSET { return total } 68 if suspended >= total { return 0 } 69 return total - suspended 70} 71 72// TOTAL PRESERVATION EXPOSURE: unacknowledged custodians + systems still purging. 73func hd_exposure_pure(unacked: i64, unsuspended: i64) -> i64 { 74 if unsuspended == HD_UNSET { return HD_UNSET } 75 return unacked + unsuspended 76} 77 78// A DEFENSIBLE HOLD requires all four independently. Notice alone is a hold on paper only. 79func hd_defensible_pure(duty_attached: i64, notice_issued: i64, unacked: i64, unsuspended: i64) -> i64 { 80 if hd_is1(duty_attached) == 0 { return 0 } 81 if hd_is1(notice_issued) == 0 { return 0 } 82 if unacked != 0 { return 0 } 83 if unsuspended == HD_UNSET { return 0 } 84 if unsuspended != 0 { return 0 } 85 return 1 86} 87 88func hd_release_premature_pure(released: i64, duty: i64) -> i64 { 89 if released == HD_UNSET { return 0 } 90 if duty == HD_UNSET { return 1 } 91 if released < duty { return 1 } 92 return 0 93} 94 95// FRCP 37(e), EXACTLY. The safe harbour is checked FIRST and is real. 96func hd_sanction_tier(lost: i64, reasonable_steps: i64, restorable: i64, intent: i64) -> i64 { 97 if hd_is1(lost) == 0 { return HD_NO_SANCTION } 98 if hd_is1(reasonable_steps) == 1 { return HD_NO_SANCTION } 99 if hd_is1(restorable) == 1 { return HD_NO_SANCTION } 100 if hd_is1(intent) == 1 { return HD_SEVERE } 101 return HD_CURATIVE 102} 103 104func hd_sanction_label(t: i64, out: *u8) -> i64 { 105 if t == HD_SEVERE { mt_catcopy(out, 0, "SEVERE-37e2" as *u8); out[11] = 0 as u8; return 11 } 106 if t == HD_CURATIVE { mt_catcopy(out, 0, "CURATIVE-37e1" as *u8); out[13] = 0 as u8; return 13 } 107 mt_catcopy(out, 0, "NONE" as *u8) 108 out[4] = 0 as u8 109 return 4 110} 111 112// ============================================================================ 113// STORAGE LAYER + ADAPTERS 114// ============================================================================ 115 116func hd_atoi(s: *u8) -> i64 { 117 var i: i64 = 0 118 var neg: i64 = 0 119 if s[0] == (45 as u8) { neg = 1; i = 1 } 120 var v: i64 = 0 121 var any: i64 = 0 122 while s[i] != (0 as u8) { 123 let c: i64 = s[i] 124 if c < 48 { return HD_UNSET } 125 if c > 57 { return HD_UNSET } 126 v = (v * 10) + (c - 48) 127 any = 1 128 i = i + 1 129 } 130 if any == 0 { return HD_UNSET } 131 if neg == 1 { return 0 - v } 132 return v 133} 134 135func hd_itoa(v: i64, out: *u8) -> i64 { 136 var o: i64 = 0 137 if v < 0 { 138 out[0] = 45 as u8 139 o = mt_catn(out, 1, 0 - v) 140 } 141 if v >= 0 { o = mt_catn(out, 0, v) } 142 out[o] = 0 as u8 143 return o 144} 145 146func hd_put(prefix: *u8, matter: *u8, anticipated: i64, filed: i64, notice: i64, released: i64, sys_total: i64, sys_susp: i64) -> i64 { 147 let ba: *u8 = sys_mmap(64) 148 let bf: *u8 = sys_mmap(64) 149 let bn: *u8 = sys_mmap(64) 150 let br: *u8 = sys_mmap(64) 151 let bt: *u8 = sys_mmap(64) 152 let bs: *u8 = sys_mmap(64) 153 hd_itoa(anticipated, ba) 154 hd_itoa(filed, bf) 155 hd_itoa(notice, bn) 156 hd_itoa(released, br) 157 hd_itoa(sys_total, bt) 158 hd_itoa(sys_susp, bs) 159 let k: *i64 = sys_mmap(8 * 6) as *i64 160 let v: *i64 = sys_mmap(8 * 6) as *i64 161 k[0] = ("anticipated" as *u8) as i64 162 v[0] = ba as i64 163 k[1] = ("filed" as *u8) as i64 164 v[1] = bf as i64 165 k[2] = ("notice" as *u8) as i64 166 v[2] = bn as i64 167 k[3] = ("released" as *u8) as i64 168 v[3] = br as i64 169 k[4] = ("sys_total" as *u8) as i64 170 v[4] = bt as i64 171 k[5] = ("sys_susp" as *u8) as i64 172 v[5] = bs as i64 173 let rec: *u8 = sys_mmap(HD_RECBUF) 174 let rl: i64 = canon_encode(k, v, 6, rec) 175 return reg_put(prefix, "hd:" as *u8, "hd:__idx__" as *u8, matter, rec, rl) 176} 177 178func hd_field(prefix: *u8, matter: *u8, field: *u8, flen: i64) -> i64 { 179 let po: *i64 = sys_mmap(16) as *i64 180 let lo: *i64 = sys_mmap(16) as *i64 181 if reg_get(prefix, "hd:" as *u8, matter, po, lo) != 1 { return HD_UNSET } 182 let b: *u8 = sys_mmap(HD_IDBUF) 183 if mt_field(po[0] as *u8, lo[0], field, flen, b) < 0 { return HD_UNSET } 184 return hd_atoi(b) 185} 186 187func hd_duty_day(prefix: *u8, matter: *u8) -> i64 { 188 return hd_duty_pure(hd_field(prefix, matter, "anticipated" as *u8, 11), hd_field(prefix, matter, "filed" as *u8, 5)) 189} 190 191func hd_duty_attached(prefix: *u8, matter: *u8, asof: i64) -> i64 { 192 return hd_duty_attached_pure(hd_duty_day(prefix, matter), asof) 193} 194 195func hd_notice_issued(prefix: *u8, matter: *u8) -> i64 { 196 if hd_field(prefix, matter, "notice" as *u8, 6) == HD_UNSET { return 0 } 197 return 1 198} 199 200func hd_notice_lag(prefix: *u8, matter: *u8) -> i64 { 201 let d: i64 = hd_duty_day(prefix, matter) 202 let n: i64 = hd_field(prefix, matter, "notice" as *u8, 6) 203 if d == HD_UNSET { return HD_UNSET } 204 if n == HD_UNSET { return HD_UNSET } 205 return n - d 206} 207 208func hd_systems_unsuspended(prefix: *u8, matter: *u8) -> i64 { 209 return hd_unsuspended_pure(hd_field(prefix, matter, "sys_total" as *u8, 9), hd_field(prefix, matter, "sys_susp" as *u8, 8)) 210} 211 212func hd_cust_key(matter: *u8, cid: *u8, out: *u8) -> i64 { 213 var o: i64 = mt_catcopy(out, 0, matter) 214 out[o] = 124 as u8 215 o = o + 1 216 o = mt_catcopy(out, o, cid) 217 out[o] = 0 as u8 218 return o 219} 220 221func hd_cust_put(prefix: *u8, matter: *u8, cid: *u8, notified: *u8, acknowledged: *u8) -> i64 { 222 let id: *u8 = sys_mmap(HD_IDBUF) 223 hd_cust_key(matter, cid, id) 224 let k: *i64 = sys_mmap(8 * 2) as *i64 225 let v: *i64 = sys_mmap(8 * 2) as *i64 226 k[0] = ("notified" as *u8) as i64 227 v[0] = notified as i64 228 k[1] = ("acknowledged" as *u8) as i64 229 v[1] = acknowledged as i64 230 let rec: *u8 = sys_mmap(HD_RECBUF) 231 let rl: i64 = canon_encode(k, v, 2, rec) 232 return reg_put(prefix, "hc:" as *u8, "hc:__idx__" as *u8, id, rec, rl) 233} 234 235// fail-closed: only the exact string "yes" is an acknowledgement. Unrecorded, unnotified, and 236// notified-but-silent custodians are all UNPRESERVED alike. 237func hd_cust_acked(prefix: *u8, matter: *u8, cid: *u8) -> i64 { 238 let id: *u8 = sys_mmap(HD_IDBUF) 239 hd_cust_key(matter, cid, id) 240 let po: *i64 = sys_mmap(16) as *i64 241 let lo: *i64 = sys_mmap(16) as *i64 242 if reg_get(prefix, "hc:" as *u8, id, po, lo) != 1 { return 0 } 243 let b: *u8 = sys_mmap(HD_IDBUF) 244 if mt_field(po[0] as *u8, lo[0], "acknowledged" as *u8, 12, b) < 0 { return 0 } 245 if mt_streq(b, "yes" as *u8) == 1 { return 1 } 246 return 0 247} 248 249func hd_cust_unacked(prefix: *u8, matter: *u8, cids: *i64, n: i64) -> i64 { 250 var miss: i64 = 0 251 var i: i64 = 0 252 while i < n { 253 if hd_cust_acked(prefix, matter, cids[i] as *u8) == 0 { miss = miss + 1 } 254 i = i + 1 255 } 256 return miss 257} 258 259func hd_exposure(prefix: *u8, matter: *u8, cids: *i64, n: i64) -> i64 { 260 return hd_exposure_pure(hd_cust_unacked(prefix, matter, cids, n), hd_systems_unsuspended(prefix, matter)) 261} 262 263func hd_defensible(prefix: *u8, matter: *u8, cids: *i64, n: i64, asof: i64) -> i64 { 264 return hd_defensible_pure(hd_duty_attached(prefix, matter, asof), hd_notice_issued(prefix, matter), hd_cust_unacked(prefix, matter, cids, n), hd_systems_unsuspended(prefix, matter)) 265} 266 267func hd_released(prefix: *u8, matter: *u8) -> i64 { 268 if hd_field(prefix, matter, "released" as *u8, 8) == HD_UNSET { return 0 } 269 return 1 270} 271 272func hd_release_premature(prefix: *u8, matter: *u8) -> i64 { 273 return hd_release_premature_pure(hd_field(prefix, matter, "released" as *u8, 8), hd_duty_day(prefix, matter)) 274}