nx_cuad_lib.nx source
↩ module page · 275 lines · 10555 B
1// nx_cuad_lib.nx -- THE CUAD CLAUSE TAXONOMY (41 types) as a typed, benchmark-aligned playbook.
2//
3// WHY (July 2026 SOTA, researched): CUAD -- 13,000+ expert labels over 510 commercial contracts from
4// EDGAR, covering 41 clause types -- is the benchmark essentially every contract-AI product trains or
5// evaluates against. Our clause layer had a TWO-clause playbook, so "retrieval works" was true and
6// meaningless. This enumerates the real target so coverage becomes a number with a denominator.
7//
8// u2605u2605THE HONESTY MECHANISM, BUILT INTO THE ORGAN: declaring 41 constants would let us claim 41 types of
9// coverage while only a handful can actually be detected -- exactly the coverage-gaming this house has
10// been burned by. So the taxonomy is split in two and the split is enforced in code:
11// DECLARED -- all 41 types exist as named constants (the complete, greppable denominator)
12// CUED -- the subset with real discriminative detection cues
13// cuad_coverage_permille scores against CUED types ONLY. A type that is merely declared can NEVER
14// contribute to a coverage number. The organ literally cannot flatter itself; adding a constant does
15// nothing until you add cues to go with it.
16//
17// u2605THE CONTRADICTION TOOTH: "Uncapped Liability" and "Cap On Liability" are SEPARATE CUAD types, and a
18// contract asserting BOTH is internally inconsistent -- one clause caps exposure while another blows the
19// cap open. Reviewers miss it because the two live pages apart and each looks fine alone. Detected here
20// as a first-class contradiction rather than as two independent findings.
21//
22// u2605FAIL-CLOSED: unrecognised text is CUAD_UNKNOWN, never the nearest type. In diligence, "I could not
23// classify this" is actionable and "probably a notices clause" is how a change-of-control provision
24// gets skipped.
25//
26// license_tier: ORIGINAL LIB.
27
28import "nx_clause_lib.nx"
29
30const CUAD_UNKNOWN: i64 = 0 - 1
31const CUAD_DECLARED: i64 = 41
32
33// ---- the 41 declared types ----
34const CU_DOCUMENT_NAME: i64 = 0
35const CU_PARTIES: i64 = 1
36const CU_AGREEMENT_DATE: i64 = 2
37const CU_EFFECTIVE_DATE: i64 = 3
38const CU_EXPIRATION_DATE: i64 = 4
39const CU_RENEWAL_TERM: i64 = 5
40const CU_NOTICE_TO_TERMINATE_RENEWAL: i64 = 6
41const CU_GOVERNING_LAW: i64 = 7
42const CU_MOST_FAVORED_NATION: i64 = 8
43const CU_NON_COMPETE: i64 = 9
44const CU_EXCLUSIVITY: i64 = 10
45const CU_NO_SOLICIT_CUSTOMERS: i64 = 11
46const CU_COMPETITIVE_RESTRICTION_EXCEPTION: i64 = 12
47const CU_NO_SOLICIT_EMPLOYEES: i64 = 13
48const CU_NON_DISPARAGEMENT: i64 = 14
49const CU_TERMINATION_FOR_CONVENIENCE: i64 = 15
50const CU_ROFR: i64 = 16
51const CU_CHANGE_OF_CONTROL: i64 = 17
52const CU_ANTI_ASSIGNMENT: i64 = 18
53const CU_REVENUE_PROFIT_SHARING: i64 = 19
54const CU_PRICE_RESTRICTIONS: i64 = 20
55const CU_MINIMUM_COMMITMENT: i64 = 21
56const CU_VOLUME_RESTRICTION: i64 = 22
57const CU_IP_OWNERSHIP_ASSIGNMENT: i64 = 23
58const CU_JOINT_IP_OWNERSHIP: i64 = 24
59const CU_LICENSE_GRANT: i64 = 25
60const CU_NON_TRANSFERABLE_LICENSE: i64 = 26
61const CU_AFFILIATE_LICENSE_LICENSOR: i64 = 27
62const CU_AFFILIATE_LICENSE_LICENSEE: i64 = 28
63const CU_UNLIMITED_LICENSE: i64 = 29
64const CU_PERPETUAL_LICENSE: i64 = 30
65const CU_SOURCE_CODE_ESCROW: i64 = 31
66const CU_POST_TERMINATION_SERVICES: i64 = 32
67const CU_AUDIT_RIGHTS: i64 = 33
68const CU_UNCAPPED_LIABILITY: i64 = 34
69const CU_CAP_ON_LIABILITY: i64 = 35
70const CU_LIQUIDATED_DAMAGES: i64 = 36
71const CU_WARRANTY_DURATION: i64 = 37
72const CU_INSURANCE: i64 = 38
73const CU_COVENANT_NOT_TO_SUE: i64 = 39
74const CU_THIRD_PARTY_BENEFICIARY: i64 = 40
75
76// u2605A type is CUED only if cuad_cue_hit knows how to recognise it. Keep this list and the cue function
77// in lockstep -- adding one without the other is the exact self-flattery this design forbids.
78func cuad_is_cued(t: i64) -> i64 {
79 if t == CU_GOVERNING_LAW { return 1 }
80 if t == CU_MOST_FAVORED_NATION { return 1 }
81 if t == CU_NON_COMPETE { return 1 }
82 if t == CU_EXCLUSIVITY { return 1 }
83 if t == CU_NO_SOLICIT_EMPLOYEES { return 1 }
84 if t == CU_NON_DISPARAGEMENT { return 1 }
85 if t == CU_TERMINATION_FOR_CONVENIENCE { return 1 }
86 if t == CU_ROFR { return 1 }
87 if t == CU_CHANGE_OF_CONTROL { return 1 }
88 if t == CU_ANTI_ASSIGNMENT { return 1 }
89 if t == CU_MINIMUM_COMMITMENT { return 1 }
90 if t == CU_IP_OWNERSHIP_ASSIGNMENT { return 1 }
91 if t == CU_LICENSE_GRANT { return 1 }
92 if t == CU_SOURCE_CODE_ESCROW { return 1 }
93 if t == CU_AUDIT_RIGHTS { return 1 }
94 if t == CU_UNCAPPED_LIABILITY { return 1 }
95 if t == CU_CAP_ON_LIABILITY { return 1 }
96 if t == CU_LIQUIDATED_DAMAGES { return 1 }
97 if t == CU_INSURANCE { return 1 }
98 if t == CU_THIRD_PARTY_BENEFICIARY { return 1 }
99 return 0
100}
101
102func cuad_cued_count() -> i64 {
103 var n: i64 = 0
104 var t: i64 = 0
105 while t < CUAD_DECLARED {
106 n = n + cuad_is_cued(t)
107 t = t + 1
108 }
109 return n
110}
111
112// u2605discriminative cues. Each requires a DISTINCTIVE token, not a common legal word: "escrow" not
113// "agreement", "disparage" not "party". A cue that fires on boilerplate is worse than no cue.
114func cuad_cue_hit(text: *u8, t: i64) -> i64 {
115 if t == CU_GOVERNING_LAW {
116 if cl_has_word(text, "governed" as *u8) == 1 { return 1 }
117 if cl_has_word(text, "jurisdiction" as *u8) == 1 { return 1 }
118 return 0
119 }
120 if t == CU_MOST_FAVORED_NATION {
121 if cl_has_word(text, "favored" as *u8) == 1 { return 1 }
122 if cl_has_word(text, "favoured" as *u8) == 1 { return 1 }
123 return 0
124 }
125 if t == CU_NON_COMPETE {
126 if cl_has_word(text, "compete" as *u8) == 1 { return 1 }
127 if cl_has_word(text, "noncompete" as *u8) == 1 { return 1 }
128 return 0
129 }
130 if t == CU_EXCLUSIVITY {
131 if cl_has_word(text, "exclusive" as *u8) == 1 { return 1 }
132 if cl_has_word(text, "exclusivity" as *u8) == 1 { return 1 }
133 return 0
134 }
135 if t == CU_NO_SOLICIT_EMPLOYEES {
136 if cl_has_word(text, "solicit" as *u8) == 1 { return 1 }
137 return 0
138 }
139 if t == CU_NON_DISPARAGEMENT {
140 if cl_has_word(text, "disparage" as *u8) == 1 { return 1 }
141 if cl_has_word(text, "disparagement" as *u8) == 1 { return 1 }
142 return 0
143 }
144 if t == CU_TERMINATION_FOR_CONVENIENCE {
145 if cl_has_word(text, "convenience" as *u8) == 1 { return 1 }
146 return 0
147 }
148 if t == CU_ROFR {
149 if cl_has_word(text, "refusal" as *u8) == 1 { return 1 }
150 return 0
151 }
152 if t == CU_CHANGE_OF_CONTROL {
153 if cl_has_word(text, "merger" as *u8) == 1 { return 1 }
154 if cl_has_word(text, "acquisition" as *u8) == 1 { return 1 }
155 return 0
156 }
157 if t == CU_ANTI_ASSIGNMENT {
158 if cl_has_word(text, "assign" as *u8) == 1 { return 1 }
159 if cl_has_word(text, "assignment" as *u8) == 1 { return 1 }
160 return 0
161 }
162 if t == CU_MINIMUM_COMMITMENT {
163 if cl_has_word(text, "minimum" as *u8) == 1 { return 1 }
164 return 0
165 }
166 if t == CU_IP_OWNERSHIP_ASSIGNMENT {
167 if cl_has_word(text, "inventions" as *u8) == 1 { return 1 }
168 if cl_has_word(text, "workmade" as *u8) == 1 { return 1 }
169 return 0
170 }
171 if t == CU_LICENSE_GRANT {
172 if cl_has_word(text, "license" as *u8) == 1 { return 1 }
173 if cl_has_word(text, "licence" as *u8) == 1 { return 1 }
174 return 0
175 }
176 if t == CU_SOURCE_CODE_ESCROW {
177 if cl_has_word(text, "escrow" as *u8) == 1 { return 1 }
178 return 0
179 }
180 if t == CU_AUDIT_RIGHTS {
181 if cl_has_word(text, "audit" as *u8) == 1 { return 1 }
182 return 0
183 }
184 if t == CU_UNCAPPED_LIABILITY {
185 if cl_has_word(text, "unlimited" as *u8) == 1 { return 1 }
186 if cl_has_word(text, "uncapped" as *u8) == 1 { return 1 }
187 return 0
188 }
189 if t == CU_CAP_ON_LIABILITY {
190 if cl_has_word(text, "capped" as *u8) == 1 { return 1 }
191 if cl_has_word(text, "aggregate" as *u8) == 1 { return 1 }
192 return 0
193 }
194 if t == CU_LIQUIDATED_DAMAGES {
195 if cl_has_word(text, "liquidated" as *u8) == 1 { return 1 }
196 return 0
197 }
198 if t == CU_INSURANCE {
199 if cl_has_word(text, "insurance" as *u8) == 1 { return 1 }
200 if cl_has_word(text, "insured" as *u8) == 1 { return 1 }
201 return 0
202 }
203 if t == CU_THIRD_PARTY_BENEFICIARY {
204 if cl_has_word(text, "beneficiary" as *u8) == 1 { return 1 }
205 return 0
206 }
207 return 0
208}
209
210// u2605classify a clause. Returns the FIRST cued type whose cue fires, or CUAD_UNKNOWN. Never guesses.
211func cuad_classify(text: *u8) -> i64 {
212 var t: i64 = 0
213 while t < CUAD_DECLARED {
214 if cuad_is_cued(t) == 1 {
215 if cuad_cue_hit(text, t) == 1 { return t }
216 }
217 t = t + 1
218 }
219 return CUAD_UNKNOWN
220}
221
222// how many CUED types a whole contract text addresses
223func cuad_types_present(text: *u8) -> i64 {
224 var n: i64 = 0
225 var t: i64 = 0
226 while t < CUAD_DECLARED {
227 if cuad_is_cued(t) == 1 {
228 n = n + cuad_cue_hit(text, t)
229 }
230 t = t + 1
231 }
232 return n
233}
234
235// u2605u2605COVERAGE IS SCORED AGAINST CUED TYPES ONLY -- a merely-declared type can never inflate it.
236func cuad_coverage_permille(text: *u8) -> i64 {
237 let c: i64 = cuad_cued_count()
238 if c <= 0 { return 0 }
239 return (cuad_types_present(text) * 1000) / c
240}
241
242// u2605u2605the CONTRADICTION: a contract cannot both cap and uncap the same exposure.
243func cuad_liability_contradiction(text: *u8) -> i64 {
244 if cuad_cue_hit(text, CU_UNCAPPED_LIABILITY) == 0 { return 0 }
245 if cuad_cue_hit(text, CU_CAP_ON_LIABILITY) == 0 { return 0 }
246 return 1
247}
248
249// u2605the diligence high-risk subset: types that change the economics or control of the deal.
250func cuad_is_high_risk(t: i64) -> i64 {
251 if t == CU_UNCAPPED_LIABILITY { return 1 }
252 if t == CU_MOST_FAVORED_NATION { return 1 }
253 if t == CU_CHANGE_OF_CONTROL { return 1 }
254 if t == CU_NON_COMPETE { return 1 }
255 if t == CU_IP_OWNERSHIP_ASSIGNMENT { return 1 }
256 if t == CU_EXCLUSIVITY { return 1 }
257 if t == CU_SOURCE_CODE_ESCROW { return 1 }
258 return 0
259}
260
261func cuad_high_risk_present(text: *u8) -> i64 {
262 var n: i64 = 0
263 var t: i64 = 0
264 while t < CUAD_DECLARED {
265 if cuad_is_cued(t) == 1 {
266 if cuad_is_high_risk(t) == 1 { n = n + cuad_cue_hit(text, t) }
267 }
268 t = t + 1
269 }
270 return n
271}
272
273// honest self-report: declared vs cued, so a caller can never mistake the denominator.
274func cuad_declared_count() -> i64 { return CUAD_DECLARED }
275func cuad_uncued_count() -> i64 { return CUAD_DECLARED - cuad_cued_count() }