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}