nx_provenance.nx source
↩ module page · 255 lines · 8700 B
1// nx_provenance.nx -- license-class enforcement gate.
2//
3// Per user directive 2026-05-14: "i dont want any wolfram or other
4// polluters that will try to sue i want academic sources that are
5// open etc but i dont want to have to deal with agl or apl or mit
6// or whatever licenses".
7//
8// This primitive reads nxc2/specs/nx_source_allowlist.txt (TSV:
9// source_id <TAB> license_class <TAB> year <TAB> description) and
10// exposes a yes/no gate for any caller that wants to attach a
11// source_id to a record before emit.
12//
13// Allowed classes (emit proceeds):
14// pd_classical, pd_explicit, pd_govdoc, academic_open, expired_patent
15// Refused classes (emit blocked):
16// forbidden, unknown (default for missing source_id)
17//
18// Compositionally fits between source-bridges (lean / coq / etc.) and
19// the ingest_runner: the bridge attaches a source_id; the runner asks
20// the provenance gate; if allowed, the row goes to the shard.
21//
22// genealogy_id: substrate_provenance_gate_2026_05_14
23// lineage_id: license_class_filter
24
25// nx_safety_envelope:
26// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
27// sil_target: SIL1
28// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
29// verdict: NOT_YET_EVALUATED
30
31import "nx_syscalls.nx"
32import "nx_runtime.nx"
33import "nx_tier.nx"
34import "nx_str.nx"
35
36// ===== license class codes (substrate-internal enum) ================
37
38const NX_LC_UNKNOWN: nx_int = 0
39const NX_LC_FORBIDDEN: nx_int = 1
40const NX_LC_PD_CLASSICAL: nx_int = 2
41const NX_LC_PD_EXPLICIT: nx_int = 3
42const NX_LC_PD_GOVDOC: nx_int = 4
43const NX_LC_ACADEMIC_OPEN: nx_int = 5
44const NX_LC_EXPIRED_PATENT: nx_int = 6
45
46// Allowed lower bound: any class >= NX_LC_PD_CLASSICAL (2). Below that
47// (UNKNOWN=0 / FORBIDDEN=1) is refused. Hardcoded as the literal value
48// here because NishiLang's const initialisers don't accept other consts.
49const NX_LC_ALLOW_MIN: nx_int = 2
50
51// ===== struct =======================================================
52
53struct NxSourceRecord {
54 source_id: *u8, // null-terminated owned copy
55 license_class: nx_int,
56 year: nx_int,
57}
58
59const NX_PROV_REC_BYTES: nx_size = 24
60const NX_PROV_MAX_SRCS: nx_int = 256
61const NX_PROV_SRC_ID_CAP: nx_size = 96
62
63struct NxProvenanceRegistry {
64 records: *NxSourceRecord,
65 n: nx_int,
66 capacity: nx_int,
67}
68
69// ===== string -> license_class mapping =============================
70
71func nx_lc_from_str(s: *u8) -> nx_int {
72 if nx_str_eq(s, "pd_classical" as *u8) == 1 { return NX_LC_PD_CLASSICAL }
73 if nx_str_eq(s, "pd_explicit" as *u8) == 1 { return NX_LC_PD_EXPLICIT }
74 if nx_str_eq(s, "pd_govdoc" as *u8) == 1 { return NX_LC_PD_GOVDOC }
75 if nx_str_eq(s, "academic_open" as *u8) == 1 { return NX_LC_ACADEMIC_OPEN }
76 if nx_str_eq(s, "expired_patent" as *u8) == 1 { return NX_LC_EXPIRED_PATENT }
77 if nx_str_eq(s, "forbidden" as *u8) == 1 { return NX_LC_FORBIDDEN }
78 return NX_LC_UNKNOWN
79}
80
81// ===== TSV line parser =============================================
82
83// One TSV row -> populate `out`. Returns 1 on success, 0 on malformed.
84// Field separators: \t. Recognized comment lead: '#'.
85func nx_prov_parse_line(buf: *u8, lo: nx_int, hi: nx_int,
86 id_buf: *u8, lc_buf: *u8, out: *NxSourceRecord) -> nx_int {
87 // Skip leading whitespace
88 var p: nx_int = lo
89 while p < hi {
90 let c: nx_int = buf[p] as nx_int
91 if c != 32 {
92 if c != 9 { p = hi + 1 }
93 if c == 9 { p = p + 1 }
94 }
95 if c == 32 { p = p + 1 }
96 }
97 p = lo
98 var ws_done: nx_int = 0
99 while ws_done == 0 {
100 if p >= hi { ws_done = 1 }
101 if ws_done == 0 {
102 let c: nx_int = buf[p] as nx_int
103 if c == 32 { p = p + 1 }
104 if c != 32 {
105 if c == 9 { p = p + 1 }
106 if c != 9 { ws_done = 1 }
107 }
108 }
109 }
110 if p >= hi { return 0 }
111 if buf[p] == 35 { return 0 } // '#' comment
112 if buf[p] == 10 { return 0 } // blank line
113
114 // Field 1: source_id (up to TAB or NL)
115 var f1_end: nx_int = p
116 var d1: nx_int = 0
117 while d1 == 0 {
118 if f1_end >= hi { d1 = 1 }
119 if d1 == 0 {
120 let c: nx_int = buf[f1_end] as nx_int
121 if c == 9 { d1 = 1 }
122 if c == 10 { d1 = 1 }
123 if d1 == 0 { f1_end = f1_end + 1 }
124 }
125 }
126 if f1_end >= hi { return 0 }
127 if buf[f1_end] == 10 { return 0 } // no separator -> malformed
128 let id_len: nx_size = (f1_end - p) as nx_size
129 if id_len == 0 { return 0 }
130 if id_len >= NX_PROV_SRC_ID_CAP { return 0 }
131 var i: nx_int = 0
132 while i < (id_len as nx_int) {
133 id_buf[i] = buf[p + i]
134 i = i + 1
135 }
136 id_buf[id_len] = 0
137
138 // Field 2: license_class
139 p = f1_end + 1
140 var f2_end: nx_int = p
141 var d2: nx_int = 0
142 while d2 == 0 {
143 if f2_end >= hi { d2 = 1 }
144 if d2 == 0 {
145 let c: nx_int = buf[f2_end] as nx_int
146 if c == 9 { d2 = 1 }
147 if c == 10 { d2 = 1 }
148 if d2 == 0 { f2_end = f2_end + 1 }
149 }
150 }
151 let lc_len: nx_size = (f2_end - p) as nx_size
152 if lc_len == 0 { return 0 }
153 if lc_len >= 32 { return 0 }
154 i = 0
155 while i < (lc_len as nx_int) {
156 lc_buf[i] = buf[p + i]
157 i = i + 1
158 }
159 lc_buf[lc_len] = 0
160
161 let lc: nx_int = nx_lc_from_str(lc_buf)
162
163 // Copy id into owned buffer
164 let owned_id: *u8 = sys_mmap(id_len + 1)
165 nx_str_cpy(owned_id, id_buf)
166
167 out.source_id = owned_id
168 out.license_class = lc
169 out.year = 0 // ignored for now
170 return 1
171}
172
173// ===== registry construction ========================================
174
175// Load the manifest file from disk into a fresh registry.
176func nx_provenance_load(path: *u8) -> *NxProvenanceRegistry {
177 let raw: *u8 = sys_mmap(24)
178 let reg: *NxProvenanceRegistry = raw as *NxProvenanceRegistry
179 reg.records = (sys_mmap((NX_PROV_MAX_SRCS as nx_size) * NX_PROV_REC_BYTES)) as *NxSourceRecord
180 reg.n = 0
181 reg.capacity = NX_PROV_MAX_SRCS
182
183 let len_p: *nx_size = (sys_mmap(NX_SIZEOF_NX_SIZE)) as *nx_size
184 len_p[0] = 0
185 let buf: *u8 = sys_read_file(path, len_p)
186 if (buf as nx_size) == 0 { return reg } // empty registry is "deny all"
187 let n: nx_int = len_p[0] as nx_int
188
189 let id_buf: *u8 = sys_mmap(NX_PROV_SRC_ID_CAP)
190 let lc_buf: *u8 = sys_mmap(32)
191 var p: nx_int = 0
192 while p < n {
193 // Find end of current line
194 var eol: nx_int = p
195 var de: nx_int = 0
196 while de == 0 {
197 if eol >= n { de = 1 }
198 if de == 0 {
199 if buf[eol] == 10 { de = 1 }
200 if de == 0 { eol = eol + 1 }
201 }
202 }
203 // Parse it
204 if reg.n < reg.capacity {
205 let raw_rec: *u8 = ((reg.records as nx_size) + (reg.n as nx_size) * NX_PROV_REC_BYTES) as *u8
206 let rec: *NxSourceRecord = raw_rec as *NxSourceRecord
207 let parsed: nx_int = nx_prov_parse_line(buf, p, eol, id_buf, lc_buf, rec)
208 if parsed == 1 { reg.n = reg.n + 1 }
209 }
210 p = eol + 1
211 }
212 return reg
213}
214
215// ===== queries =====================================================
216
217// Find a record by source_id. Returns the license class, or
218// NX_LC_UNKNOWN if not in the registry.
219func nx_provenance_check(reg: *NxProvenanceRegistry, source_id: *u8) -> nx_int {
220 var i: nx_int = 0
221 while i < reg.n {
222 let raw_rec: *u8 = ((reg.records as nx_size) + (i as nx_size) * NX_PROV_REC_BYTES) as *u8
223 let rec: *NxSourceRecord = raw_rec as *NxSourceRecord
224 if nx_str_eq(rec.source_id, source_id) == 1 {
225 return rec.license_class
226 }
227 i = i + 1
228 }
229 return NX_LC_UNKNOWN
230}
231
232// Returns 1 if source_id is allowed for emit; 0 if blocked.
233func nx_provenance_is_allowed(reg: *NxProvenanceRegistry, source_id: *u8) -> nx_int {
234 let lc: nx_int = nx_provenance_check(reg, source_id)
235 if lc >= NX_LC_ALLOW_MIN { return 1 }
236 return 0
237}
238
239// Observability: count records by class (sum 0..n for now).
240func nx_provenance_n_records(reg: *NxProvenanceRegistry) -> nx_int {
241 return reg.n
242}
243
244// Count records by class -- caller passes a class constant.
245func nx_provenance_n_by_class(reg: *NxProvenanceRegistry, lc: nx_int) -> nx_int {
246 var c: nx_int = 0
247 var i: nx_int = 0
248 while i < reg.n {
249 let raw_rec: *u8 = ((reg.records as nx_size) + (i as nx_size) * NX_PROV_REC_BYTES) as *u8
250 let rec: *NxSourceRecord = raw_rec as *NxSourceRecord
251 if rec.license_class == lc { c = c + 1 }
252 i = i + 1
253 }
254 return c
255}