nx_asset_prov_clock_candidate_t346.nx source
↩ module page · 246 lines · 10723 B
1// nx_asset_prov_lib.nx -- THE PER-ASSET PROVENANCE ROW AND ITS FAIL-CLOSED VERDICT (/compare/modding MD9, 2026-09-06).
2// ONE rights table in this estate (knowledge/model_license.conf, read by nx_licgate_lib) and ONE journal of ingested assets:
3// a row is `asset<TAB>sha256<TAB>source<TAB>licence_id<TAB>origin<TAB>epoch`, APPEND-ONLY -- a later row for the same sha
4// SUPERSEDES the earlier one (a relicence is a new row, never an edit) and the LAST row wins. Field bytes are sanitised on
5// write (a tab or newline inside a source url becomes a space) so no caller can forge a licence column through a url.
6// THE VERDICT IS FAIL-CLOSED BY CONSTRUCTION: no journal or no row -> REFUSE (NO-JOURNAL / NO-ROW); a licence id the rights
7// table does not carry -> REFUSE (UNKNOWN-LICENCE), so "unknown" is never a table row, it is the absence of one; a licence
8// whose weights_redist right is NO -> REFUSE (NON-REDISTRIBUTABLE); CONDITIONAL -> REVIEW; an UNVERIFIED licence is capped
9// at CONDITIONAL, the same law nx_licgate_lib applies to models (a licence nobody read cannot grant an outright YES).
10// Every export and publish door asks pv_verdict in-process; PRIVATE USE IS ALWAYS ALLOWED (res[PV_RES_PRIVATE]=1) -- the
11// refusal is about redistribution, never about looking at what you downloaded.
12// The journal path is a PARAMETER: a gate drives this lib on its own scratch journal and never on the production one.
13// license_tier: ORIGINAL No hw writes (Rule 26).
14import "nx_syscalls.nx"
15import "nx_licgate_lib.nx"
16import "nx_sha256.nx"
17
18const PV_JRNL_DEFAULT: *u8 = "knowledge/provenance/assets.jrnl"
19const PV_DIR_MODE: i64 = 493
20const PV_FILE_MODE: i64 = 420
21const PV_OPEN_APPEND: i64 = 1089 // O_WRONLY | O_CREAT | O_APPEND
22const PV_SYS_OPENAT: i64 = 257
23const PV_SYS_TIME: i64 = 201
24const PV_SHA_BYTES: i64 = 32
25const PV_SHA_HEX: i64 = 64
26const PV_TAG: *u8 = "asset"
27const PV_TAB: i64 = 9
28const PV_NL: i64 = 10
29const PV_SPACE: i64 = 32
30const PV_SLASH: i64 = 47
31const PV_LIC_CAP: i64 = 128
32const PV_LINE_CAP: i64 = 4096
33const PV_PATH_CAP: i64 = 1024
34const PV_F_TAG: i64 = 0
35const PV_F_SHA: i64 = 1
36const PV_F_SOURCE: i64 = 2
37const PV_F_LIC: i64 = 3
38const PV_F_ORIGIN: i64 = 4
39const PV_F_EPOCH: i64 = 5
40// verdict codes are nx_licgate_lib's own exit vocabulary: LG_RC_OK 0 / LG_RC_REVIEW 3 / LG_RC_REFUSE 4
41const PV_R_OK: i64 = 0
42const PV_R_NO_ROW: i64 = 1
43const PV_R_UNKNOWN_LIC: i64 = 2
44const PV_R_NON_REDIST: i64 = 3
45const PV_R_CONDITIONAL: i64 = 4
46const PV_R_UNVERIFIED: i64 = 5
47const PV_R_TABLE_UNREADABLE: i64 = 6
48const PV_R_NO_JOURNAL: i64 = 7
49const PV_RES_REASON: i64 = 0
50const PV_RES_REDIST: i64 = 1
51const PV_RES_LICIDX: i64 = 2
52const PV_RES_PRIVATE: i64 = 3
53const PV_RES_ROWS: i64 = 4
54const PV_RES_N: i64 = 8
55// box slots for pv_row_find
56const PV_BOX_BUF: i64 = 0
57const PV_BOX_S: i64 = 1
58const PV_BOX_E: i64 = 2
59const PV_BOX_ROWS: i64 = 3
60const PV_BOX_N: i64 = 4
61
62func pv_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
63func pv_puts(s: *u8) -> i64 { sys_write(1, s, pv_len(s)); return 0 }
64func pv_putn(v: i64) -> i64 { let t: *u8 = sys_mmap(32); var e: i64 = 0; if v < 0 { t[0] = 45 as u8; e = lg_putd(t, 1, 0 - v) } else { e = lg_putd(t, 0, v) } sys_write(1, t, e); return 0 }
65func pv_hexdig(v: i64) -> u8 { if v < 10 { return (48 + v) as u8 } return (87 + v) as u8 }
66func pv_hex(dig: *u8, out: *u8) -> i64 {
67 var i: i64 = 0
68 while i < PV_SHA_BYTES {
69 let b: i64 = (dig[i] & 0xff) as i64
70 out[i * 2] = pv_hexdig(b / 16)
71 out[i * 2 + 1] = pv_hexdig(b % 16)
72 i = i + 1
73 }
74 out[PV_SHA_HEX] = 0 as u8
75 return PV_SHA_HEX
76}
77func pv_hash_bytes(b: *u8, n: i64, out: *u8) -> i64 {
78 let d: *u8 = sys_mmap(PV_SHA_BYTES)
79 sha256_digest(b, n, d)
80 return pv_hex(d, out)
81}
82// returns the file length hashed, or -1 when the file cannot be read (an unreadable asset has NO sha and NO row)
83func pv_hash_file(path: *u8, out: *u8) -> i64 {
84 let lp: *i64 = sys_mmap(16) as *i64
85 let buf: *u8 = sys_read_file(path, lp)
86 if (buf as i64) == 0 { return 0 - 1 }
87 pv_hash_bytes(buf, lp[0], out)
88 return lp[0]
89}
90func pv_now() -> i64 { return sys_now_realtime_sec() }
91func pv_is_sha_hex(s: *u8) -> i64 {
92 if pv_len(s) != PV_SHA_HEX { return 0 }
93 var i: i64 = 0
94 while i < PV_SHA_HEX {
95 let c: i64 = s[i] as i64
96 var ok: i64 = 0
97 if c >= 48 { if c <= 57 { ok = 1 } }
98 if c >= 97 { if c <= 102 { ok = 1 } }
99 if ok == 0 { return 0 }
100 i = i + 1
101 }
102 return 1
103}
104// a field byte that would open a new column or a new row is replaced by a space: the schema cannot be forged from a value
105func pv_put_field(o: *u8, at: i64, s: *u8, cap: i64) -> i64 {
106 var a: i64 = at
107 var i: i64 = 0
108 while s[i] != (0 as u8) {
109 var c: i64 = s[i] as i64
110 if c == PV_TAB { c = PV_SPACE }
111 if c == PV_NL { c = PV_SPACE }
112 if c == 13 { c = PV_SPACE }
113 if a < cap - 1 { o[a] = c as u8; a = a + 1 }
114 i = i + 1
115 }
116 return a
117}
118func pv_mkdir_of(jrnl: *u8) -> i64 {
119 let d: *u8 = sys_mmap(PV_PATH_CAP)
120 var last: i64 = 0 - 1
121 var i: i64 = 0
122 while jrnl[i] != (0 as u8) { if (jrnl[i] as i64) == PV_SLASH { last = i } i = i + 1 }
123 if last <= 0 { return 0 }
124 var k: i64 = 0
125 while k < last { d[k] = jrnl[k]; k = k + 1 }
126 d[last] = 0 as u8
127 sys_mkdir(d, PV_DIR_MODE)
128 return 1
129}
130// append one row; returns the bytes written or -1 when the journal cannot be opened for append
131func pv_row_write(jrnl: *u8, sha: *u8, source: *u8, lic: *u8, origin: *u8, epoch: i64) -> i64 {
132 pv_mkdir_of(jrnl)
133 let line: *u8 = sys_mmap(PV_LINE_CAP)
134 var a: i64 = lg_put(line, 0, PV_TAG)
135 line[a] = PV_TAB as u8; a = a + 1
136 a = pv_put_field(line, a, sha, PV_LINE_CAP)
137 line[a] = PV_TAB as u8; a = a + 1
138 a = pv_put_field(line, a, source, PV_LINE_CAP)
139 line[a] = PV_TAB as u8; a = a + 1
140 a = pv_put_field(line, a, lic, PV_LINE_CAP)
141 line[a] = PV_TAB as u8; a = a + 1
142 a = pv_put_field(line, a, origin, PV_LINE_CAP)
143 line[a] = PV_TAB as u8; a = a + 1
144 a = lg_putd(line, a, epoch)
145 line[a] = PV_NL as u8; a = a + 1
146 let fd: i64 = __syscall(PV_SYS_OPENAT, AT_FDCWD, jrnl, PV_OPEN_APPEND, PV_FILE_MODE, 0, 0)
147 if fd < 0 { return 0 - 1 }
148 var done: i64 = 0
149 while done < a { let k: i64 = sys_write(fd, line + done, a - done); if k <= 0 { break } done = done + k }
150 sys_close(fd)
151 return done
152}
153// the LAST row carrying this sha wins. returns 1 found / 0 no row / -1 no journal; box carries buf, line start, line end, rows
154func pv_row_find(jrnl: *u8, sha: *u8, box: *i64) -> i64 {
155 box[PV_BOX_BUF] = 0; box[PV_BOX_S] = 0; box[PV_BOX_E] = 0; box[PV_BOX_ROWS] = 0
156 let lp: *i64 = sys_mmap(16) as *i64
157 let buf: *u8 = sys_read_file(jrnl, lp)
158 if (buf as i64) == 0 { return 0 - 1 }
159 let n: i64 = lp[0]
160 box[PV_BOX_BUF] = buf as i64
161 let fb: *i64 = sys_mmap(32) as *i64
162 var rows: i64 = 0
163 var p: i64 = 0
164 while p < n {
165 var q: i64 = p
166 while q < n { if (buf[q] as i64) == PV_NL { break } q = q + 1 }
167 if q > p {
168 if lg_field(buf, p, q, PV_F_TAG, fb) == 1 {
169 if lg_seq(buf, fb[0], fb[1], PV_TAG) == 1 {
170 if lg_field(buf, p, q, PV_F_SHA, fb) == 1 {
171 if lg_seq(buf, fb[0], fb[1], sha) == 1 { box[PV_BOX_S] = p; box[PV_BOX_E] = q; rows = rows + 1 }
172 }
173 }
174 }
175 }
176 p = q + 1
177 }
178 box[PV_BOX_ROWS] = rows
179 if rows > 0 { return 1 }
180 return 0
181}
182func pv_field_copy(buf: *u8, s: i64, e: i64, idx: i64, out: *u8, cap: i64) -> i64 {
183 let fb: *i64 = sys_mmap(32) as *i64
184 out[0] = 0 as u8
185 if lg_field(buf, s, e, idx, fb) != 1 { return 0 }
186 var n: i64 = fb[1]
187 if n > cap - 1 { n = cap - 1 }
188 var i: i64 = 0
189 while i < n { out[i] = buf[fb[0] + i]; i = i + 1 }
190 out[n] = 0 as u8
191 return n
192}
193// THE VERDICT. res: reason, redist right after the unverified cap, licence row index, private_use (always 1), rows for this sha
194func pv_verdict(jrnl: *u8, sha: *u8, res: *i64) -> i64 {
195 var i: i64 = 0
196 while i < PV_RES_N { res[i] = 0; i = i + 1 }
197 res[PV_RES_PRIVATE] = 1
198 res[PV_RES_LICIDX] = 0 - 1
199 res[PV_RES_REDIST] = LG_NO
200 let ctx: *i64 = lg_ctx()
201 if ctx[3] < 0 { res[PV_RES_REASON] = PV_R_TABLE_UNREADABLE; return LG_RC_REFUSE }
202 let box: *i64 = sys_mmap(8 * PV_BOX_N) as *i64
203 let f: i64 = pv_row_find(jrnl, sha, box)
204 if f < 0 { res[PV_RES_REASON] = PV_R_NO_JOURNAL; return LG_RC_REFUSE }
205 res[PV_RES_ROWS] = box[PV_BOX_ROWS]
206 if f == 0 { res[PV_RES_REASON] = PV_R_NO_ROW; return LG_RC_REFUSE }
207 let licb: *u8 = sys_mmap(PV_LIC_CAP)
208 pv_field_copy(box[PV_BOX_BUF] as *u8, box[PV_BOX_S], box[PV_BOX_E], PV_F_LIC, licb, PV_LIC_CAP)
209 let li: i64 = lg_find_license(ctx, licb)
210 if li < 0 { res[PV_RES_REASON] = PV_R_UNKNOWN_LIC; return LG_RC_REFUSE }
211 res[PV_RES_LICIDX] = li
212 var redist: i64 = lg_lic_redist(ctx, li)
213 var reason: i64 = PV_R_OK
214 if lg_lic_verified(ctx, li) == LG_UNVERIFIED { if redist > LG_COND { redist = LG_COND; reason = PV_R_UNVERIFIED } }
215 res[PV_RES_REDIST] = redist
216 if redist == LG_NO { res[PV_RES_REASON] = PV_R_NON_REDIST; return LG_RC_REFUSE }
217 if redist == LG_COND { if reason == PV_R_OK { reason = PV_R_CONDITIONAL } res[PV_RES_REASON] = reason; return LG_RC_REVIEW }
218 res[PV_RES_REASON] = PV_R_OK
219 return LG_RC_OK
220}
221func pv_verdict_name(rc: i64) -> *u8 {
222 if rc == LG_RC_OK { return "SHIP_OK" as *u8 }
223 if rc == LG_RC_REVIEW { return "REVIEW" as *u8 }
224 return "REFUSE" as *u8
225}
226func pv_reason_name(r: i64) -> *u8 {
227 if r == PV_R_OK { return "OK" as *u8 }
228 if r == PV_R_NO_ROW { return "NO-ROW" as *u8 }
229 if r == PV_R_UNKNOWN_LIC { return "UNKNOWN-LICENCE" as *u8 }
230 if r == PV_R_NON_REDIST { return "NON-REDISTRIBUTABLE" as *u8 }
231 if r == PV_R_CONDITIONAL { return "CONDITIONAL" as *u8 }
232 if r == PV_R_UNVERIFIED { return "UNVERIFIED-LICENCE-CAPPED" as *u8 }
233 if r == PV_R_TABLE_UNREADABLE { return "RIGHTS-TABLE-UNREADABLE" as *u8 }
234 if r == PV_R_NO_JOURNAL { return "NO-JOURNAL" as *u8 }
235 return "UNNAMED" as *u8
236}
237// one line, key=value, the verdict token spelled exactly once so a positional reader and a grep agree
238func pv_print(sha: *u8, rc: i64, res: *i64) -> i64 {
239 pv_puts("ASSET-PROV sha=" as *u8); pv_puts(sha)
240 pv_puts(" verdict=" as *u8); pv_puts(pv_verdict_name(rc))
241 pv_puts(" reason=" as *u8); pv_puts(pv_reason_name(res[PV_RES_REASON]))
242 pv_puts(" redist=" as *u8); pv_putn(res[PV_RES_REDIST])
243 pv_puts(" rows=" as *u8); pv_putn(res[PV_RES_ROWS])
244 pv_puts(" private_use=allowed\n" as *u8)
245 return 0
246}