nx_acquire_license_lib.nx source
↩ module page · 153 lines · 6093 B
1// nx_acquire_license_lib.nx -- the LICENCE-URL DERIVATION as a LIBRARY.
2//
3// WHY A LIB, and it is the same unblock nx_licgate_lib records: an organ with main() CANNOT be
4// imported (double-main), so a gate over a CLI can only fork the deployed binary. That fork is a
5// boundary the mutation harness is blind across -- every mutant comes back NOT-REACHED and the gate
6// returns a clean GREEN that reads exactly like a proof while proving nothing. Splitting the pure
7// core out is what makes the logic mutation-testable at all.
8//
9// FAIL-CLOSED ON AN UNKNOWN HOST. Guessing a licence location for a host whose layout we have not
10// established would fetch some other file and pin its digest as though it were a licence: a
11// fabricated fact wearing a cryptographic pin, which is worse than no licence at all. Unknown -> -1.
12//
13// DERIVING A URL IS NOT EVIDENCE. This produces CANDIDATES. A row reaches evidence=READ only once
14// those bytes are fetched, stored and pinned.
15// license_tier: ORIGINAL
16import "nx_syscalls.nx"
17
18const AL_URLCAP: i64 = 512
19const AL_SLASH: i64 = 47
20const AL_NUL: i64 = 0
21const AL_CAND_N: i64 = 4
22
23const AL_HOST_UNKNOWN: i64 = 0
24const AL_HOST_GITHUB: i64 = 1
25const AL_HOST_HF: i64 = 2
26const AL_HOST_HF_DATASET: i64 = 3
27
28func al_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
29
30func al_cat(d: *u8, o: i64, s: *u8) -> i64 {
31 var p: i64 = o
32 var i: i64 = 0
33 while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 }
34 return p
35}
36
37func al_starts(s: *u8, p: *u8) -> i64 {
38 var i: i64 = 0
39 var ok: i64 = 1
40 while p[i] != (0 as u8) {
41 if s[i] != p[i] { ok = 0 }
42 if s[i] == (0 as u8) { ok = 0 }
43 i = i + 1
44 }
45 return ok
46}
47
48func al_streq(a: *u8, b: *u8) -> i64 {
49 var i: i64 = 0
50 while 1 == 1 {
51 let ca: i64 = a[i] as i64
52 let cb: i64 = b[i] as i64
53 if ca != cb { return 0 }
54 if ca == 0 { return 1 }
55 i = i + 1
56 }
57 return 1
58}
59
60// candidate licence filenames, in the order a repository is conventionally searched.
61func al_cand(i: i64) -> *u8 {
62 if i == 0 { return "LICENSE" as *u8 }
63 if i == 1 { return "LICENSE.md" as *u8 }
64 if i == 2 { return "LICENSE.txt" as *u8 }
65 return "COPYING" as *u8
66}
67
68// ORDER IS LOad-BEARING: the dataset form is a PREFIX-EXTENSION of the plain hub form, so it must be
69// tested FIRST. Reversed, every dataset row classifies as a model row and its licence is fetched from
70// a path that does not exist -- a 404 that would read as "this work has no licence".
71func al_host(url: *u8) -> i64 {
72 if al_starts(url, "https://huggingface.co/datasets/" as *u8) == 1 { return AL_HOST_HF_DATASET }
73 if al_starts(url, "https://huggingface.co/" as *u8) == 1 { return AL_HOST_HF }
74 if al_starts(url, "https://github.com/" as *u8) == 1 { return AL_HOST_GITHUB }
75 return AL_HOST_UNKNOWN
76}
77
78// copy the first `want` path segments following `skip` bytes of url into out.
79// Returns segments actually copied -- the CALLER checks it, because a URL with fewer segments than
80// required is a malformed row, not an owner/repo pair, and must never be silently used.
81func al_seg(url: *u8, skip: i64, want: i64, out: *u8, cap: i64) -> i64 {
82 var i: i64 = skip
83 var o: i64 = 0
84 var seg: i64 = 0
85 var stop: i64 = 0
86 while stop == 0 {
87 let c: i64 = url[i] as i64
88 if c == AL_NUL { stop = 1 }
89 else {
90 if c == AL_SLASH {
91 seg = seg + 1
92 if seg >= want { stop = 1 }
93 else { if o < cap - 1 { out[o] = AL_SLASH as u8; o = o + 1 } }
94 } else {
95 if o < cap - 1 { out[o] = url[i]; o = o + 1 }
96 }
97 if stop == 0 { i = i + 1 }
98 }
99 }
100 out[o] = 0 as u8
101 // a URL ending without a trailing slash closes its last segment at NUL, so count it complete.
102 if o > 0 { if seg < want { seg = seg + 1 } }
103 return seg
104}
105
106// THE PURE CORE. url + candidate index -> raw licence URL, or -1 for a host we cannot address.
107// cand is ignored for both HuggingFace forms: a hub repo carries its licence in the YAML frontmatter
108// of README.md, not in a LICENSE file, so four filename candidates there would be four requests for
109// files that do not exist. HEAD (not main) for github, so a repo on master or one that renames its
110// default branch later still resolves.
111func aq_license_url(url: *u8, cand: i64, out: *u8, cap: i64) -> i64 {
112 let h: i64 = al_host(url)
113 if h == AL_HOST_UNKNOWN { out[0] = 0 as u8; return 0 - 1 }
114 let seg: *u8 = sys_mmap(AL_URLCAP)
115
116 if h == AL_HOST_GITHUB {
117 if cand < 0 { out[0] = 0 as u8; return 0 - 1 }
118 if cand >= AL_CAND_N { out[0] = 0 as u8; return 0 - 1 }
119 let n: i64 = al_seg(url, al_len("https://github.com/" as *u8), 2, seg, AL_URLCAP)
120 if n < 2 { out[0] = 0 as u8; return 0 - 1 }
121 var o: i64 = al_cat(out, 0, "https://raw.githubusercontent.com/" as *u8)
122 o = al_cat(out, o, seg)
123 o = al_cat(out, o, "/HEAD/" as *u8)
124 o = al_cat(out, o, al_cand(cand))
125 out[o] = 0 as u8
126 return o
127 }
128
129 if h == AL_HOST_HF_DATASET {
130 let n: i64 = al_seg(url, al_len("https://huggingface.co/datasets/" as *u8), 2, seg, AL_URLCAP)
131 if n < 2 { out[0] = 0 as u8; return 0 - 1 }
132 var o: i64 = al_cat(out, 0, "https://huggingface.co/datasets/" as *u8)
133 o = al_cat(out, o, seg)
134 o = al_cat(out, o, "/raw/main/README.md" as *u8)
135 out[o] = 0 as u8
136 return o
137 }
138
139 let n: i64 = al_seg(url, al_len("https://huggingface.co/" as *u8), 2, seg, AL_URLCAP)
140 if n < 2 { out[0] = 0 as u8; return 0 - 1 }
141 var o: i64 = al_cat(out, 0, "https://huggingface.co/" as *u8)
142 o = al_cat(out, o, seg)
143 o = al_cat(out, o, "/raw/main/README.md" as *u8)
144 out[o] = 0 as u8
145 return o
146}
147
148func al_host_name(h: i64) -> *u8 {
149 if h == AL_HOST_GITHUB { return "github" as *u8 }
150 if h == AL_HOST_HF_DATASET { return "huggingface-dataset" as *u8 }
151 if h == AL_HOST_HF { return "huggingface" as *u8 }
152 return "UNKNOWN" as *u8
153}