code wiki / (root) / nx_obligation_lib.nx

nx_obligation_lib.nx source

↩ module page · 112 lines · 5541 B

1// nx_obligation_lib.nx -- POST-SIGNATURE OBLIGATION MANAGEMENT (the DocuSign IAM / Ironclad Jurist class). 2// nx_clm catches the auto-renewal trap BEFORE a term ends. This is the other 95% of a contract's life: 3// the obligations it created. Most contract value leaks after signature, because the executed document 4// goes into a folder and the duties inside it are tracked by memory. 5// 6// u2605THE TOOTH MOST TRACKERS MISS -- THE CURE PERIOD. An obligation past its due date is NOT automatically 7// a breach. Commercial contracts routinely grant a cure window (often 10-30 days after written notice), 8// and during it the counterparty can still perform. A tracker that flags overdue AS breach manufactures 9// disputes that do not exist; one that ignores the cure period entirely misses the moment the right to 10// terminate actually vests. Both are expensive, so OVERDUE and BREACH are separate states here and the 11// cure window sits explicitly between them. 12// 13// u2605THE SECOND TOOTH -- NOTICE STARTS THE CLOCK, NOT THE DUE DATE. A cure period typically runs from 14// written notice of the failure, not from the missed deadline. An obligation that is overdue but where 15// notice was NEVER given has an unexpired (indeed unstarted) cure window, so it cannot be a breach yet. 16// 17// u2605FAIL-CLOSED: an obligation with no due date is OBL_UNKNOWN -- never "satisfied", never "fine". A 18// duty nobody dated is a duty nobody is tracking, which is precisely the failure mode. 19// 20// STRUCTURE: pure decision core, zero I/O, per [[feedback-pure-core-not-hostage-to-storage-2026-07-31]]. 21// license_tier: ORIGINAL LIB. 22 23import "nx_matter_lib.nx" 24 25const OBL_UNKNOWN: i64 = 0 - 1 26const OBL_PENDING: i64 = 0 27const OBL_DUE: i64 = 1 28const OBL_OVERDUE: i64 = 2 29const OBL_BREACH: i64 = 3 30const OBL_SATISFIED: i64 = 4 31const OBL_WAIVED: i64 = 5 32 33const OBL_UNSET: i64 = 0 - 2000000002 34 35func obl_is1(v: i64) -> i64 { 36 if v == 1 { return 1 } 37 return 0 38} 39 40// u2605THE STATE MACHINE, resolved in priority order. Waiver and satisfaction are terminal and beat every 41// timing consideration; only then does the calendar matter. 42// notice_day = day written notice of failure was given (OBL_UNSET if never), cure_days = the contractual 43// window that runs FROM that notice. 44func obl_status_pure(due: i64, asof: i64, satisfied: i64, waived: i64, notice_day: i64, cure_days: i64) -> i64 { 45 if obl_is1(waived) == 1 { return OBL_WAIVED } 46 if obl_is1(satisfied) == 1 { return OBL_SATISFIED } 47 if due == OBL_UNSET { return OBL_UNKNOWN } 48 if asof == OBL_UNSET { return OBL_UNKNOWN } 49 if asof < due { return OBL_PENDING } 50 if asof == due { return OBL_DUE } 51 // past due. Breach requires that notice was given AND its cure window has expired. 52 if notice_day == OBL_UNSET { return OBL_OVERDUE } 53 if cure_days == OBL_UNSET { return OBL_OVERDUE } 54 if asof > (notice_day + cure_days) { return OBL_BREACH } 55 return OBL_OVERDUE 56} 57 58// Days remaining in the cure window; OBL_UNSET when no notice has started it. 59func obl_cure_remaining_pure(asof: i64, notice_day: i64, cure_days: i64) -> i64 { 60 if notice_day == OBL_UNSET { return OBL_UNSET } 61 if cure_days == OBL_UNSET { return OBL_UNSET } 62 if asof == OBL_UNSET { return OBL_UNSET } 63 return (notice_day + cure_days) - asof 64} 65 66// 1 only when the right to terminate has actually vested -- i.e. a true breach, not merely overdue. 67func obl_termination_right_pure(status: i64) -> i64 { 68 if status == OBL_BREACH { return 1 } 69 return 0 70} 71 72// An obligation performed AFTER its due date is still satisfied, but it was LATE. Tracking that 73// separately is what makes a pattern of lateness visible before it becomes a termination event. 74func obl_late_pure(due: i64, performed: i64) -> i64 { 75 if due == OBL_UNSET { return 0 } 76 if performed == OBL_UNSET { return 0 } 77 if performed > due { return 1 } 78 return 0 79} 80 81// Days late; 0 when on time, OBL_UNSET when undated. 82func obl_days_late_pure(due: i64, performed: i64) -> i64 { 83 if due == OBL_UNSET { return OBL_UNSET } 84 if performed == OBL_UNSET { return OBL_UNSET } 85 if performed <= due { return 0 } 86 return performed - due 87} 88 89// u2605PORTFOLIO EXPOSURE: only true breaches carry termination risk, but overdue items are the leading 90// indicator. Reported separately so a dashboard cannot blur one into the other. 91func obl_exposure_pure(overdue_count: i64, breach_count: i64, unknown_count: i64) -> i64 { 92 return overdue_count + breach_count + unknown_count 93} 94 95// A portfolio is CLEAN only when nothing is overdue, in breach, or undated. Undated counts against it 96// on purpose -- an untracked obligation is not a safe obligation. 97func obl_portfolio_clean_pure(overdue_count: i64, breach_count: i64, unknown_count: i64) -> i64 { 98 if obl_exposure_pure(overdue_count, breach_count, unknown_count) == 0 { return 1 } 99 return 0 100} 101 102func obl_status_label(s: i64, out: *u8) -> i64 { 103 if s == OBL_WAIVED { mt_catcopy(out, 0, "WAIVED" as *u8); out[6] = 0 as u8; return 6 } 104 if s == OBL_SATISFIED { mt_catcopy(out, 0, "SATISFIED" as *u8); out[9] = 0 as u8; return 9 } 105 if s == OBL_BREACH { mt_catcopy(out, 0, "BREACH" as *u8); out[6] = 0 as u8; return 6 } 106 if s == OBL_OVERDUE { mt_catcopy(out, 0, "OVERDUE" as *u8); out[7] = 0 as u8; return 7 } 107 if s == OBL_DUE { mt_catcopy(out, 0, "DUE" as *u8); out[3] = 0 as u8; return 3 } 108 if s == OBL_PENDING { mt_catcopy(out, 0, "PENDING" as *u8); out[7] = 0 as u8; return 7 } 109 mt_catcopy(out, 0, "UNKNOWN" as *u8) 110 out[7] = 0 as u8 111 return 7 112}