nx_licdetect_lib.nx source
↩ module page · 228 lines · 9446 B
1// nx_licdetect_lib.nx -- WHICH LICENCE IS THIS TEXT? Read from the bytes, never from a declaration.
2//
3// THE MEASUREMENT THAT JUSTIFIES A TEXT DETECTOR AT ALL. Across 124,278 licence chains surveyed in the
4// field, 96.5% of datasets and 95.8% of models LACK the required licence text, and only 2.3% / 3.2%
5// satisfy both the licence-text and copyright requirements. The survey's conclusion is this lib's
6// design rule: LICENCE FILES AND NOTICES, NOT METADATA, ARE THE SOURCE OF LEGAL TRUTH.
7// It is also what the librarian measured directly: of 26 NAVER releases, 2 carried any licence string
8// on the listing, and the one we then FETCHED turned out to be 483 bytes off the canonical Apache text
9// because it adds a copyright preamble and drops the appendix. A declaration is a claim; bytes are not.
10//
11// THREE OUTCOMES, AND ONLY ONE OF THEM IS AN ANSWER:
12// DETECTED exactly one licence's REQUIRE markers all matched and none of its EXCLUDE markers did
13// UNKNOWN nothing matched -- fail-closed, the artifact does NOT advance to evidence=READ
14// AMBIGUOUS two or more matched -- ALSO fail-closed. A text matching two licences is not a decision,
15// and taking the first would be a coin flip wearing a verdict.
16//
17// MARKERS ARE DATA (knowledge/license_markers.conf), so a new licence costs a row and never a rebuild,
18// and every marker set is auditable by a human reading the conf rather than the binary.
19//
20// NO GUESSED CAPS: every array here is sized from the CONF'S OWN LINE COUNT, counted before allocation.
21// A marker table cannot overflow a bound derived from the file that defines it.
22// license_tier: ORIGINAL
23import "nx_syscalls.nx"
24
25const LD_DETECTED: i64 = 0
26const LD_UNKNOWN: i64 = 1
27const LD_AMBIGUOUS: i64 = 2
28
29const LD_TAB: i64 = 9
30const LD_NL: i64 = 10
31const LD_HASH: i64 = 35
32const LD_M: i64 = 77 // 'M' marker row
33const LD_V: i64 = 86 // 'V' verified-against row
34const LD_WORD: i64 = 8
35const LD_ROW_STRIDE: i64 = 5 // lic_off, lic_len, kind, mk_off, mk_len
36const LD_KIND_REQUIRE: i64 = 1
37const LD_KIND_EXCLUDE: i64 = 2
38const LD_CTX_SLOTS: i64 = 8 // buf, n, rows, nrows, vrows, nv
39
40func ld_streq_n(a: *u8, ao: i64, an: i64, b: *u8, bo: i64, bn: i64) -> i64 {
41 if an != bn { return 0 }
42 var i: i64 = 0
43 while i < an { if a[ao + i] != b[bo + i] { return 0 } i = i + 1 }
44 return 1
45}
46
47// literal substring search. Returns 1 if needle occurs in text[0,n).
48func ld_contains(text: *u8, n: i64, buf: *u8, mo: i64, mn: i64) -> i64 {
49 if mn <= 0 { return 0 }
50 if mn > n { return 0 }
51 var i: i64 = 0
52 let last: i64 = n - mn
53 while i <= last {
54 var j: i64 = 0
55 var ok: i64 = 1
56 while j < mn {
57 if text[i + j] != buf[mo + j] { ok = 0; j = mn } else { j = j + 1 }
58 }
59 if ok == 1 { return 1 }
60 i = i + 1
61 }
62 return 0
63}
64
65// TAB field [idx] of row [s,e) -> box[0]=off box[1]=len ; len -1 when the row has too few fields.
66func ld_field(buf: *u8, s: i64, e: i64, idx: i64, box: *i64) -> i64 {
67 var f: i64 = 0
68 var i: i64 = s
69 while f < idx {
70 if i >= e { box[0] = 0 - 1; box[1] = 0 - 1; return 0 - 1 }
71 if buf[i] == (LD_TAB as u8) { f = f + 1 }
72 i = i + 1
73 }
74 var j: i64 = i
75 while j < e { if buf[j] == (LD_TAB as u8) { break } j = j + 1 }
76 box[0] = i
77 box[1] = j - i
78 return j - i
79}
80
81// ctx[0]=buf ctx[1]=n ctx[2]=rows ctx[3]=nrows ctx[4]=vrows ctx[5]=nv ; ctx[3] = -1 means unreadable.
82func ld_ctx() -> *i64 {
83 let ctx: *i64 = sys_mmap(LD_CTX_SLOTS * LD_WORD) as *i64
84 let lp: *i64 = sys_mmap(LD_WORD * 2) as *i64
85 let buf: *u8 = sys_read_file("knowledge/license_markers.conf" as *u8, lp)
86 ctx[3] = 0 - 1
87 ctx[5] = 0
88 if (buf as i64) == 0 { return ctx }
89 let n: i64 = lp[0]
90 ctx[0] = buf as i64
91 ctx[1] = n
92 if n <= 0 { return ctx }
93
94 // CAP DERIVED FROM THE DATA: a marker row cannot outnumber the conf's own lines.
95 var lines: i64 = 1
96 var c: i64 = 0
97 while c < n { if buf[c] == (LD_NL as u8) { lines = lines + 1 } c = c + 1 }
98 let rows: *i64 = sys_mmap(lines * LD_ROW_STRIDE * LD_WORD) as *i64
99 let vrows: *i64 = sys_mmap(lines * 2 * LD_WORD) as *i64
100 let box: *i64 = sys_mmap(32) as *i64
101
102 var nrows: i64 = 0
103 var nv: i64 = 0
104 var p: i64 = 0
105 while p < n {
106 var q: i64 = p
107 var scan: i64 = 1
108 while scan == 1 {
109 if q >= n { scan = 0 }
110 else { if buf[q] == (LD_NL as u8) { scan = 0 } else { q = q + 1 } }
111 }
112 if q > p + 1 {
113 if buf[p + 1] == (LD_TAB as u8) {
114 if buf[p] == (LD_M as u8) {
115 if ld_field(buf, p, q, 1, box) >= 0 {
116 let lo: i64 = box[0]
117 let ll: i64 = box[1]
118 if ld_field(buf, p, q, 2, box) >= 0 {
119 // REQUIRE and EXCLUDE are both 7 characters, so the LENGTH admits the
120 // field and the FIRST BYTE discriminates ('R'=82, 'E'=69). A row whose
121 // kind word is neither keeps kind 0 and is therefore matched by no branch
122 // in ld_detect -- an unrecognised kind is INERT, never silently REQUIRE.
123 var kind: i64 = 0
124 let ko: i64 = box[0]
125 let kl: i64 = box[1]
126 if kl == 7 {
127 if buf[ko] == (82 as u8) { kind = LD_KIND_REQUIRE }
128 if buf[ko] == (69 as u8) { kind = LD_KIND_EXCLUDE }
129 }
130 if ld_field(buf, p, q, 3, box) >= 0 {
131 rows[nrows * LD_ROW_STRIDE + 0] = lo
132 rows[nrows * LD_ROW_STRIDE + 1] = ll
133 rows[nrows * LD_ROW_STRIDE + 2] = kind
134 rows[nrows * LD_ROW_STRIDE + 3] = box[0]
135 rows[nrows * LD_ROW_STRIDE + 4] = box[1]
136 nrows = nrows + 1
137 }
138 }
139 }
140 }
141 if buf[p] == (LD_V as u8) {
142 if ld_field(buf, p, q, 1, box) >= 0 {
143 vrows[nv * 2 + 0] = box[0]
144 vrows[nv * 2 + 1] = box[1]
145 nv = nv + 1
146 }
147 }
148 }
149 }
150 p = q + 1
151 }
152 ctx[2] = rows as i64
153 ctx[3] = nrows
154 ctx[4] = vrows as i64
155 ctx[5] = nv
156 return ctx
157}
158
159// is this licence's marker set PROVEN against a licence text we actually hold?
160func ld_marker_set_verified(ctx: *i64, lo: i64, ll: i64) -> i64 {
161 let buf: *u8 = ctx[0] as *u8
162 let vrows: *i64 = ctx[4] as *i64
163 let nv: i64 = ctx[5]
164 var i: i64 = 0
165 while i < nv {
166 if ld_streq_n(buf, lo, ll, buf, vrows[i * 2 + 0], vrows[i * 2 + 1]) == 1 { return 1 }
167 i = i + 1
168 }
169 return 0
170}
171
172// THE DETECTION. Fills out_lo/out_ll with the winning licence id's offset+len in the conf buffer.
173// Returns LD_DETECTED / LD_UNKNOWN / LD_AMBIGUOUS.
174func ld_detect(ctx: *i64, text: *u8, n: i64, out: *i64) -> i64 {
175 out[0] = 0 - 1
176 out[1] = 0 - 1
177 if ctx[3] < 0 { return LD_UNKNOWN }
178 let buf: *u8 = ctx[0] as *u8
179 let rows: *i64 = ctx[2] as *i64
180 let nrows: i64 = ctx[3]
181 var hits: i64 = 0
182 var i: i64 = 0
183 while i < nrows {
184 let lo: i64 = rows[i * LD_ROW_STRIDE + 0]
185 let ll: i64 = rows[i * LD_ROW_STRIDE + 1]
186 // only evaluate a licence the FIRST time its id appears, so a licence with k markers is not
187 // scored k times -- that would make the ambiguity count meaningless.
188 var first: i64 = 1
189 var b: i64 = 0
190 while b < i {
191 if ld_streq_n(buf, lo, ll, buf, rows[b * LD_ROW_STRIDE + 0], rows[b * LD_ROW_STRIDE + 1]) == 1 { first = 0; b = i }
192 else { b = b + 1 }
193 }
194 if first == 1 {
195 var all_req: i64 = 1
196 var any_exc: i64 = 0
197 var j: i64 = 0
198 while j < nrows {
199 if ld_streq_n(buf, lo, ll, buf, rows[j * LD_ROW_STRIDE + 0], rows[j * LD_ROW_STRIDE + 1]) == 1 {
200 let kind: i64 = rows[j * LD_ROW_STRIDE + 2]
201 let present: i64 = ld_contains(text, n, buf, rows[j * LD_ROW_STRIDE + 3], rows[j * LD_ROW_STRIDE + 4])
202 if kind == LD_KIND_REQUIRE { if present == 0 { all_req = 0 } }
203 if kind == LD_KIND_EXCLUDE { if present == 1 { any_exc = 1 } }
204 }
205 j = j + 1
206 }
207 if all_req == 1 {
208 if any_exc == 0 {
209 hits = hits + 1
210 out[0] = lo
211 out[1] = ll
212 }
213 }
214 }
215 i = i + 1
216 }
217 if hits == 0 { out[0] = 0 - 1; out[1] = 0 - 1; return LD_UNKNOWN }
218 // TWO MATCHES IS NOT A TIE TO BREAK, IT IS A REFUSAL. Clearing the output makes it impossible for a
219 // caller to read a winner out of an ambiguous result by ignoring the return code.
220 if hits > 1 { out[0] = 0 - 1; out[1] = 0 - 1; return LD_AMBIGUOUS }
221 return LD_DETECTED
222}
223
224func ld_result_name(r: i64) -> *u8 {
225 if r == LD_DETECTED { return "DETECTED" as *u8 }
226 if r == LD_AMBIGUOUS { return "AMBIGUOUS" as *u8 }
227 return "UNKNOWN" as *u8
228}