nx_clm_lib.nx source
↩ module page · 117 lines · 5219 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"
17const CLM_MAGIC_1048576: i64 = 1048576
18
19const CLM_OPEN: i64 = 0 // notice window still open -- you can still cancel
20const CLM_MISSED: i64 = 1 // past the notice deadline, before expiry -- it WILL auto-renew
21const CLM_RENEWED: i64 = 2 // past expiry -- already auto-renewed into a new term
22const CLM_UNKNOWN: i64 = 0 - 1 // insufficient data -- FLAG FOR REVIEW (fail-closed), never "safe"
23const CLM_COLON: i64 = 58
24
25// the LAST day on which cancellation notice may be given.
26func clm_notice_deadline(expiry_day: i64, notice_days: i64) -> i64 {
27 return expiry_day - notice_days
28}
29
30// ★evaluate the auto-renewal trap as of `today_day`.
31// FAIL-CLOSED: an unknown expiry (<=0) or negative notice period -> CLM_UNKNOWN, not OPEN.
32func clm_renewal_state(expiry_day: i64, notice_days: i64, today_day: i64) -> i64 {
33 if expiry_day <= 0 { return CLM_UNKNOWN }
34 if notice_days < 0 { return CLM_UNKNOWN }
35 let deadline: i64 = expiry_day - notice_days
36 if today_day <= deadline { return CLM_OPEN }
37 if today_day <= expiry_day { return CLM_MISSED }
38 return CLM_RENEWED
39}
40
41// days remaining to give notice; NEGATIVE means the window is already missed.
42func clm_days_to_act(expiry_day: i64, notice_days: i64, today_day: i64) -> i64 {
43 return (expiry_day - notice_days) - today_day
44}
45
46// 1 only if it is still SAFE (window open). Missed, renewed, AND unknown all return 0.
47func clm_is_safe(expiry_day: i64, notice_days: i64, today_day: i64) -> i64 {
48 if clm_renewal_state(expiry_day, notice_days, today_day) == CLM_OPEN { return 1 }
49 return 0
50}
51
52// record a contract obligation with its due day. key = "<contract>:<obl>".
53func clm_obligation_put(prefix: *u8, contract_id: *u8, obl_id: *u8, due_day: i64, otype: *u8) -> i64 {
54 let id: *u8 = sys_mmap(256)
55 var o: i64 = mt_catcopy(id, 0, contract_id)
56 id[o] = CLM_COLON as u8
57 o = o + 1
58 o = mt_catcopy(id, o, obl_id)
59 id[o] = 0 as u8
60 let db: *u8 = sys_mmap(32)
61 var doo: i64 = mt_catn(db, 0, due_day)
62 db[doo] = 0 as u8
63 let k: *i64 = sys_mmap(8 * 2) as *i64
64 let v: *i64 = sys_mmap(8 * 2) as *i64
65 k[0] = ("due" as *u8) as i64
66 v[0] = db as i64
67 k[1] = ("type" as *u8) as i64
68 v[1] = otype as i64
69 let rec: *u8 = sys_mmap(512)
70 let rl: i64 = canon_encode(k, v, 2, rec)
71 return reg_put(prefix, "clm:" as *u8, "clm:__idx__" as *u8, id, rec, rl)
72}
73
74// count a contract's obligations that are OVERDUE as of today_day (due_day < today_day).
75func clm_overdue(prefix: *u8, contract_id: *u8, today_day: i64) -> i64 {
76 let idx: *u8 = sys_mmap(CLM_MAGIC_1048576)
77 let ilen: i64 = reg_index(prefix, "clm:__idx__" as *u8, idx, CLM_MAGIC_1048576)
78 let cp: *u8 = sys_mmap(256)
79 var cpl: i64 = mt_catcopy(cp, 0, contract_id)
80 cp[cpl] = CLM_COLON as u8
81 cpl = cpl + 1
82 cp[cpl] = 0 as u8
83 let idb: *u8 = sys_mmap(256)
84 let po: *i64 = sys_mmap(16) as *i64
85 let lo: *i64 = sys_mmap(16) as *i64
86 let df: *u8 = sys_mmap(32)
87 var overdue: i64 = 0
88 var ls: i64 = 0
89 var i: i64 = 0
90 while i <= ilen {
91 var eol: i64 = 0
92 if i == ilen { eol = 1 }
93 if i < ilen { if idx[i] == 10 as u8 { eol = 1 } }
94 if eol == 1 {
95 if i > ls {
96 var c: i64 = 0
97 while ls + c < i { idb[c] = idx[ls + c]; c = c + 1 }
98 idb[c] = 0 as u8
99 var mtch: i64 = 1
100 var x: i64 = 0
101 while cp[x] != (0 as u8) { if idb[x] != cp[x] { mtch = 0 } x = x + 1 }
102 if mtch == 1 {
103 if reg_get(prefix, "clm:" as *u8, idb, po, lo) == 1 {
104 mt_field(po[0] as *u8, lo[0], "due" as *u8, 3, df)
105 var due: i64 = 0
106 var y: i64 = 0
107 while df[y] != (0 as u8) { due = due * 10 + (df[y] - 48); y = y + 1 }
108 if due < today_day { overdue = overdue + 1 }
109 }
110 }
111 }
112 ls = i + 1
113 }
114 i = i + 1
115 }
116 return overdue
117}