code wiki / (root) / nx_privacy_lib.nx

nx_privacy_lib.nx source

↩ module page · 122 lines · 5578 B

1// nx_privacy_lib.nx -- PRIVACY COMPLIANCE: GDPR / CCPA subject-request clocks and breach notification. 2// The OneTrust-class category we had at zero. Two clocks decide almost all exposure here, and both are 3// routinely started from the wrong event. 4// 5// u2605THE 72-HOUR CLOCK RUNS FROM AWARENESS, NOT FROM THE BREACH (GDPR Art 33(1)). A controller that 6// discovers on day 30 an intrusion that began on day 1 has 72 hours from DAY 30. Teams anchor to the 7// incident date, conclude they are already hopelessly late, and then delay further -- or anchor to the 8// forensic-conclusion date and report far too late. Awareness is the anchor; the incident date is 9// evidence, not a deadline input. 10// 11// u2605AN EXTENSION MUST BE CLAIMED INSIDE THE ORIGINAL WINDOW. GDPR Art 12(3) allows a two-month extension 12// on a subject access request, and CCPA allows a further 45 days -- but only if the data subject is 13// informed WITHIN the original period. Notice given on day 31 of a 30-day window does not buy 60 more 14// days; it is simply a late response. Encoded so a late claim can never extend anything. 15// 16// u2605ENCRYPTION IS A DEFENCE TO SUBJECT NOTIFICATION, NOT TO REGULATOR NOTIFICATION (Art 34(3)(a) vs 17// Art 33). Rendering the data unintelligible excuses telling the individuals; it does NOT excuse telling 18// the supervisory authority. Conflating the two is a real and expensive error, so they are separate. 19// 20// All periods are DATA (rule 11). Time is in HOURS for the breach clock and DAYS for request clocks -- 21// deliberately different units because the statutes use different units, and silently converting is how 22// 72 hours becomes "3 business days". 23// STRUCTURE: pure decision core, zero I/O. license_tier: ORIGINAL LIB. 24 25import "nx_matter_lib.nx" 26 27const PRV_UNSET: i64 = 0 - 2000000002 28const PRV_UNKNOWN: i64 = 0 - 1 29 30const PRV_ONTIME: i64 = 0 31const PRV_LATE: i64 = 1 32 33const PRV_GDPR_DSAR_DAYS: i64 = 30 34const PRV_GDPR_EXT_DAYS: i64 = 60 35const PRV_CCPA_DSAR_DAYS: i64 = 45 36const PRV_CCPA_EXT_DAYS: i64 = 45 37const PRV_BREACH_HOURS: i64 = 72 38 39func prv_is1(v: i64) -> i64 { 40 if v == 1 { return 1 } 41 return 0 42} 43 44// u2605THE EXTENSION RULE: an extension counts ONLY if notice of it was given within the original window. 45// base_days is the statutory period; ext_days the permitted extension. 46func prv_deadline_pure(received: i64, base_days: i64, ext_claimed: i64, ext_notice_day: i64, ext_days: i64) -> i64 { 47 if received == PRV_UNSET { return PRV_UNKNOWN } 48 let base: i64 = received + base_days 49 if prv_is1(ext_claimed) == 0 { return base } 50 if ext_notice_day == PRV_UNSET { return base } 51 if ext_notice_day > base { return base } 52 return base + ext_days 53} 54 55func prv_gdpr_dsar_pure(received: i64, ext_claimed: i64, ext_notice_day: i64) -> i64 { 56 return prv_deadline_pure(received, PRV_GDPR_DSAR_DAYS, ext_claimed, ext_notice_day, PRV_GDPR_EXT_DAYS) 57} 58 59func prv_ccpa_dsar_pure(received: i64, ext_claimed: i64, ext_notice_day: i64) -> i64 { 60 return prv_deadline_pure(received, PRV_CCPA_DSAR_DAYS, ext_claimed, ext_notice_day, PRV_CCPA_EXT_DAYS) 61} 62 63// responded on/before the deadline is ON TIME; unknown dates never resolve to on-time by default. 64func prv_response_status_pure(deadline: i64, responded: i64) -> i64 { 65 if deadline == PRV_UNKNOWN { return PRV_UNKNOWN } 66 if responded == PRV_UNSET { return PRV_UNKNOWN } 67 if responded <= deadline { return PRV_ONTIME } 68 return PRV_LATE 69} 70 71func prv_days_remaining_pure(deadline: i64, asof: i64) -> i64 { 72 if deadline == PRV_UNKNOWN { return PRV_UNSET } 73 if asof == PRV_UNSET { return PRV_UNSET } 74 return deadline - asof 75} 76 77// ---- breach notification ---- 78 79// u2605the regulator deadline is 72 hours from AWARENESS. The incident time is deliberately NOT an input: 80// there is no way to accidentally anchor to it. 81func prv_breach_deadline_pure(aware_hour: i64) -> i64 { 82 if aware_hour == PRV_UNSET { return PRV_UNKNOWN } 83 return aware_hour + PRV_BREACH_HOURS 84} 85 86func prv_breach_status_pure(aware_hour: i64, reported_hour: i64) -> i64 { 87 let dl: i64 = prv_breach_deadline_pure(aware_hour) 88 if dl == PRV_UNKNOWN { return PRV_UNKNOWN } 89 if reported_hour == PRV_UNSET { return PRV_UNKNOWN } 90 if reported_hour <= dl { return PRV_ONTIME } 91 return PRV_LATE 92} 93 94// u2605Art 33: the SUPERVISORY AUTHORITY must be told about any personal-data breach that poses a risk. 95// Encryption does NOT excuse this. 96func prv_notify_authority_pure(is_personal_data: i64, risk_to_rights: i64) -> i64 { 97 if prv_is1(is_personal_data) == 0 { return 0 } 98 if prv_is1(risk_to_rights) == 0 { return 0 } 99 return 1 100} 101 102// u2605Art 34: DATA SUBJECTS must be told only on HIGH risk -- and rendering the data unintelligible 103// (encryption) is a defence HERE and only here. 104func prv_notify_subjects_pure(is_personal_data: i64, high_risk: i64, encrypted: i64) -> i64 { 105 if prv_is1(is_personal_data) == 0 { return 0 } 106 if prv_is1(high_risk) == 0 { return 0 } 107 if prv_is1(encrypted) == 1 { return 0 } 108 return 1 109} 110 111// total open exposure: late responses + unreported breaches past their window + unknown-status items. 112func prv_exposure_pure(late_responses: i64, late_breaches: i64, unknown: i64) -> i64 { 113 return late_responses + late_breaches + unknown 114} 115 116func prv_status_label(s: i64, out: *u8) -> i64 { 117 if s == PRV_ONTIME { mt_catcopy(out, 0, "ON-TIME" as *u8); out[7] = 0 as u8; return 7 } 118 if s == PRV_LATE { mt_catcopy(out, 0, "LATE" as *u8); out[4] = 0 as u8; return 4 } 119 mt_catcopy(out, 0, "UNKNOWN" as *u8) 120 out[7] = 0 as u8 121 return 7 122}