nx_patent_check.nx source
↩ module page · 227 lines · 7470 B
1// nx_patent_check.nx -- offline patent-encumbrance gate.
2//
3// Per user directive 2026-05-14: "add the patent information/table to
4// nishi library for offline referencing". This primitive reads
5// nxc2/specs/nx_patent_table.txt (TSV: algorithm_id, patent_status,
6// patent_ref, filed_year, expiry_year, notes) and answers per-algo
7// safety queries.
8//
9// Status enum:
10// NX_PAT_UNKNOWN = 0 default deny (no row in table)
11// NX_PAT_ACTIVE = 1 patent in force -- UNSAFE; refuse
12// NX_PAT_EXPIRED = 2 was patented; now safe
13// NX_PAT_PD = 3 never patented; always safe
14//
15// Safe predicate: status >= NX_PAT_EXPIRED (i.e., PD or EXPIRED).
16//
17// Composes with nx_provenance: license filter (#2) blocks tainted
18// source text; patent filter (#3) blocks encumbered algorithm
19// IDEAS. Together they close the IP-exposure surface.
20//
21// genealogy_id: substrate_patent_gate_2026_05_14
22// lineage_id: ip_compliance_filter
23
24// nx_safety_envelope:
25// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
26// sil_target: SIL1
27// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
28// verdict: NOT_YET_EVALUATED
29
30import "nx_syscalls.nx"
31import "nx_runtime.nx"
32import "nx_tier.nx"
33import "nx_str.nx"
34
35const NX_PAT_UNKNOWN: nx_int = 0
36const NX_PAT_ACTIVE: nx_int = 1
37const NX_PAT_EXPIRED: nx_int = 2
38const NX_PAT_PD: nx_int = 3
39
40// Safe lower bound (PD or EXPIRED). Below = refused. Hardcoded as
41// literal because NishiLang const initialisers don't accept other consts.
42const NX_PAT_SAFE_MIN: nx_int = 2
43
44const NX_PAT_MAX_RECORDS: nx_int = 1024
45const NX_PAT_ALGO_ID_CAP: nx_size = 96
46const NX_PAT_STATUS_CAP: nx_size = 16
47
48// ===== struct =======================================================
49
50struct NxPatentRecord {
51 algorithm_id: *u8,
52 patent_status: nx_int,
53 expiry_year: nx_int, // 0 if pd or unknown
54}
55
56const NX_PAT_REC_BYTES: nx_size = 24
57
58struct NxPatentTable {
59 records: *NxPatentRecord,
60 n: nx_int,
61 capacity: nx_int,
62}
63
64// ===== string -> status =============================================
65
66func nx_pat_status_from_str(s: *u8) -> nx_int {
67 if nx_str_eq(s, "pd" as *u8) == 1 { return NX_PAT_PD }
68 if nx_str_eq(s, "expired" as *u8) == 1 { return NX_PAT_EXPIRED }
69 if nx_str_eq(s, "active" as *u8) == 1 { return NX_PAT_ACTIVE }
70 return NX_PAT_UNKNOWN
71}
72
73// ===== TSV row parser ===============================================
74
75// Parse one TSV row into out_rec. Skips blank / comment lines.
76// Returns 1 on success, 0 on malformed / skipped.
77func nx_pat_parse_line(buf: *u8, lo: nx_int, hi: nx_int,
78 id_buf: *u8, status_buf: *u8,
79 out: *NxPatentRecord) -> nx_int {
80 // Skip leading whitespace
81 var p: nx_int = lo
82 var ws_done: nx_int = 0
83 while ws_done == 0 {
84 if p >= hi { ws_done = 1 }
85 if ws_done == 0 {
86 let c: nx_int = buf[p] as nx_int
87 if c == 32 { p = p + 1 }
88 if c != 32 {
89 if c == 9 { p = p + 1 }
90 if c != 9 { ws_done = 1 }
91 }
92 }
93 }
94 if p >= hi { return 0 }
95 if buf[p] == 35 { return 0 } // '#' comment
96 if buf[p] == 10 { return 0 } // blank line
97
98 // Field 1: algorithm_id (up to TAB or NL)
99 var f1_end: nx_int = p
100 var d1: nx_int = 0
101 while d1 == 0 {
102 if f1_end >= hi { d1 = 1 }
103 if d1 == 0 {
104 let c: nx_int = buf[f1_end] as nx_int
105 if c == 9 { d1 = 1 }
106 if c == 10 { d1 = 1 }
107 if d1 == 0 { f1_end = f1_end + 1 }
108 }
109 }
110 if f1_end >= hi { return 0 }
111 if buf[f1_end] == 10 { return 0 } // no separator
112 let id_len: nx_int = f1_end - p
113 if id_len == 0 { return 0 }
114 if id_len >= (NX_PAT_ALGO_ID_CAP as nx_int) { return 0 }
115 var i: nx_int = 0
116 while i < id_len {
117 id_buf[i] = buf[p + i]
118 i = i + 1
119 }
120 id_buf[id_len] = 0
121
122 // Field 2: patent_status
123 p = f1_end + 1
124 var f2_end: nx_int = p
125 var d2: nx_int = 0
126 while d2 == 0 {
127 if f2_end >= hi { d2 = 1 }
128 if d2 == 0 {
129 let c: nx_int = buf[f2_end] as nx_int
130 if c == 9 { d2 = 1 }
131 if c == 10 { d2 = 1 }
132 if d2 == 0 { f2_end = f2_end + 1 }
133 }
134 }
135 let st_len: nx_int = f2_end - p
136 if st_len == 0 { return 0 }
137 if st_len >= (NX_PAT_STATUS_CAP as nx_int) { return 0 }
138 i = 0
139 while i < st_len {
140 status_buf[i] = buf[p + i]
141 i = i + 1
142 }
143 status_buf[st_len] = 0
144
145 // Own the algorithm_id copy.
146 let owned_id: *u8 = sys_mmap((id_len + 1) as nx_size)
147 nx_str_cpy(owned_id, id_buf)
148 out.algorithm_id = owned_id
149 out.patent_status = nx_pat_status_from_str(status_buf)
150 out.expiry_year = 0 // present in file but unused at gate time
151 return 1
152}
153
154// ===== load + query =================================================
155
156func nx_patent_load(path: *u8) -> *NxPatentTable {
157 let raw: *u8 = sys_mmap(24)
158 let t: *NxPatentTable = raw as *NxPatentTable
159 t.records = (sys_mmap((NX_PAT_MAX_RECORDS as nx_size) * NX_PAT_REC_BYTES)) as *NxPatentRecord
160 t.n = 0
161 t.capacity = NX_PAT_MAX_RECORDS
162
163 let len_p: *nx_size = (sys_mmap(NX_SIZEOF_NX_SIZE)) as *nx_size
164 len_p[0] = 0
165 let buf: *u8 = sys_read_file(path, len_p)
166 if (buf as nx_size) == 0 { return t } // empty -> default-deny everything
167 let n: nx_int = len_p[0] as nx_int
168
169 let id_buf: *u8 = sys_mmap(NX_PAT_ALGO_ID_CAP)
170 let status_buf: *u8 = sys_mmap(NX_PAT_STATUS_CAP)
171 var p: nx_int = 0
172 while p < n {
173 var eol: nx_int = p
174 var de: nx_int = 0
175 while de == 0 {
176 if eol >= n { de = 1 }
177 if de == 0 {
178 if buf[eol] == 10 { de = 1 }
179 if de == 0 { eol = eol + 1 }
180 }
181 }
182 if t.n < t.capacity {
183 let raw_rec: *u8 = ((t.records as nx_size) + (t.n as nx_size) * NX_PAT_REC_BYTES) as *u8
184 let rec: *NxPatentRecord = raw_rec as *NxPatentRecord
185 let parsed: nx_int = nx_pat_parse_line(buf, p, eol, id_buf, status_buf, rec)
186 if parsed == 1 { t.n = t.n + 1 }
187 }
188 p = eol + 1
189 }
190 return t
191}
192
193// Look up algorithm_id; returns status enum, or NX_PAT_UNKNOWN.
194func nx_patent_check(t: *NxPatentTable, algo_id: *u8) -> nx_int {
195 var i: nx_int = 0
196 while i < t.n {
197 let raw_rec: *u8 = ((t.records as nx_size) + (i as nx_size) * NX_PAT_REC_BYTES) as *u8
198 let rec: *NxPatentRecord = raw_rec as *NxPatentRecord
199 if nx_str_eq(rec.algorithm_id, algo_id) == 1 {
200 return rec.patent_status
201 }
202 i = i + 1
203 }
204 return NX_PAT_UNKNOWN
205}
206
207// Returns 1 if algo_id is patent-safe (PD or EXPIRED), 0 otherwise.
208func nx_patent_is_safe(t: *NxPatentTable, algo_id: *u8) -> nx_int {
209 let s: nx_int = nx_patent_check(t, algo_id)
210 if s >= NX_PAT_SAFE_MIN { return 1 }
211 return 0
212}
213
214// Observability
215func nx_patent_n_records(t: *NxPatentTable) -> nx_int { return t.n }
216
217func nx_patent_n_by_status(t: *NxPatentTable, status: nx_int) -> nx_int {
218 var c: nx_int = 0
219 var i: nx_int = 0
220 while i < t.n {
221 let raw_rec: *u8 = ((t.records as nx_size) + (i as nx_size) * NX_PAT_REC_BYTES) as *u8
222 let rec: *NxPatentRecord = raw_rec as *NxPatentRecord
223 if rec.patent_status == status { c = c + 1 }
224 i = i + 1
225 }
226 return c
227}