nx_clm_lib.nx source
↩ module page · 120 lines · 5433 B
1// nx_clm_lib.nx -- F996: CONTRACT LIFECYCLE -- obligation tracking + auto-renewal-trap detection.
2//
3// The expensive, silent contract failure is the EVERGREEN AUTO-RENEWAL TRAP: a contract renews for
4// another (often costly) term unless you give written notice N days before expiry. Miss that window by
5// a day and you are locked in. Legal teams miss it constantly because nothing computes the deadline and
6// nothing flags the day it passes. This organ does both, and it is FAIL-CLOSED: a contract whose expiry
7// or notice period is unknown is flagged FOR REVIEW, never silently reported safe -- because "no action
8// needed" on a contract you could not evaluate is exactly how you get trapped.
9//
10// Dates are integer DAY-NUMBERS (deterministic, no float). Converting a YYYYMMDD to a day-number is the
11// docketing engine's job (nx_docket); this takes day-numbers so the lifecycle math is exact and testable.
12//
13// SCALE ENVELOPE (declared): clm_overdue scans the obligation index for a contract's key-prefix (the
14// proven per-key scan). DRY: composes nx_matter_lib (reg_*/mt_*/canon_encode). license_tier: ORIGINAL LIB.
15
16import "nx_matter_lib.nx"
17
18const CLM_OPEN: i64 = 0 // notice window still open -- you can still cancel
19const CLM_MISSED: i64 = 1 // past the notice deadline, before expiry -- it WILL auto-renew
20const CLM_RENEWED: i64 = 2 // past expiry -- already auto-renewed into a new term
21const CLM_UNKNOWN: i64 = 0 - 1 // insufficient data -- FLAG FOR REVIEW (fail-closed), never "safe"
22const CLM_COLON: i64 = 58
23
24// the LAST day on which cancellation notice may be given.
25func clm_notice_deadline(expiry_day: i64, notice_days: i64) -> i64 {
26 return expiry_day - notice_days
27}
28
29// ★evaluate the auto-renewal trap as of `today_day`.
30// FAIL-CLOSED: an unknown expiry (<=0) or negative notice period -> CLM_UNKNOWN, not OPEN.
31func clm_renewal_state(expiry_day: i64, notice_days: i64, today_day: i64) -> i64 {
32 if expiry_day <= 0 { return CLM_UNKNOWN }
33 if notice_days < 0 { return CLM_UNKNOWN }
34 let deadline: i64 = expiry_day - notice_days
35 if today_day <= deadline { return CLM_OPEN }
36 if today_day <= expiry_day { return CLM_MISSED }
37 return CLM_RENEWED
38}
39
40// days remaining to give notice; NEGATIVE means the window is already missed.
41func clm_days_to_act(expiry_day: i64, notice_days: i64, today_day: i64) -> i64 {
42 return (expiry_day - notice_days) - today_day
43}
44
45// 1 only if it is still SAFE (window open). Missed, renewed, AND unknown all return 0.
46func clm_is_safe(expiry_day: i64, notice_days: i64, today_day: i64) -> i64 {
47 if clm_renewal_state(expiry_day, notice_days, today_day) == CLM_OPEN { return 1 }
48 return 0
49}
50
51// record a contract obligation with its due day. key = "<contract>:<obl>".
52func clm_obligation_put(prefix: *u8, contract_id: *u8, obl_id: *u8, due_day: i64, otype: *u8) -> i64 {
53 let id: *u8 = sys_mmap(256)
54 var o: i64 = mt_catcopy(id, 0, contract_id)
55 id[o] = CLM_COLON as u8
56 o = o + 1
57 o = mt_catcopy(id, o, obl_id)
58 id[o] = 0 as u8
59 let db: *u8 = sys_mmap(32)
60 var doo: i64 = mt_catn(db, 0, due_day)
61 db[doo] = 0 as u8
62 let k: *i64 = sys_mmap(8 * 2) as *i64
63 let v: *i64 = sys_mmap(8 * 2) as *i64
64 k[0] = ("due" as *u8) as i64
65 v[0] = db as i64
66 k[1] = ("type" as *u8) as i64
67 v[1] = otype as i64
68 let rec: *u8 = sys_mmap(512)
69 let rl: i64 = canon_encode(k, v, 2, rec)
70 return reg_put(prefix, "clm:" as *u8, "clm:__idx__" as *u8, id, rec, rl)
71}
72
73// count a contract's obligations that are OVERDUE as of today_day (due_day < today_day).
74func clm_overdue(prefix: *u8, contract_id: *u8, today_day: i64) -> i64 {
75 // SIZE-TO-NEED: buffer derived from the index itself. The pair this replaces
76 // returned -1 once the contract index passed 1 MiB, and -1 skips the walk
77 // below -- an overdue check would have reported NOTHING OVERDUE.
78 let idxbox: *i64 = sys_mmap(16) as *i64
79 let ilen: i64 = reg_index_read(prefix, "clm:__idx__" as *u8, idxbox)
80 let idx: *u8 = idxbox[0] as *u8
81 let cp: *u8 = sys_mmap(256)
82 var cpl: i64 = mt_catcopy(cp, 0, contract_id)
83 cp[cpl] = CLM_COLON as u8
84 cpl = cpl + 1
85 cp[cpl] = 0 as u8
86 let idb: *u8 = sys_mmap(256)
87 let po: *i64 = sys_mmap(16) as *i64
88 let lo: *i64 = sys_mmap(16) as *i64
89 let df: *u8 = sys_mmap(32)
90 var overdue: i64 = 0
91 var ls: i64 = 0
92 var i: i64 = 0
93 while i <= ilen {
94 var eol: i64 = 0
95 if i == ilen { eol = 1 }
96 if i < ilen { if idx[i] == 10 as u8 { eol = 1 } }
97 if eol == 1 {
98 if i > ls {
99 var c: i64 = 0
100 while ls + c < i { idb[c] = idx[ls + c]; c = c + 1 }
101 idb[c] = 0 as u8
102 var mtch: i64 = 1
103 var x: i64 = 0
104 while cp[x] != (0 as u8) { if idb[x] != cp[x] { mtch = 0 } x = x + 1 }
105 if mtch == 1 {
106 if reg_get(prefix, "clm:" as *u8, idb, po, lo) == 1 {
107 mt_field(po[0] as *u8, lo[0], "due" as *u8, 3, df)
108 var due: i64 = 0
109 var y: i64 = 0
110 while df[y] != (0 as u8) { due = due * 10 + (df[y] - 48); y = y + 1 }
111 if due < today_day { overdue = overdue + 1 }
112 }
113 }
114 }
115 ls = i + 1
116 }
117 i = i + 1
118 }
119 return overdue
120}