nx_asset_prov_gate.nx source
↩ module page · 190 lines · 14170 B
1// nx_asset_prov_gate.nx -- THE PROVENANCE-ROW GATE (/compare/modding MD9, 2026-09-06). Drives nx_asset_prov_lib IN-PROCESS
2// on a scratch journal under /tmp/nx_asset_prov_gate/ (never the production journal), with fixtures built at runtime and
3// the journal UNLINKED at setup so every run measures the same thing. The rights table it reads is the estate's real one
4// (knowledge/model_license.conf): a verified permissive row (apache-2.0), a verified non-redistributable row (cc-by-nc-4.0)
5// and an UNVERIFIED permissive row (stability-community) give every verdict branch a real subject. Then the promoted CLI is
6// forked on the same scratch journal as the second witness. Negative controls: the verdict must fire on an absent row and
7// stay silent on a verified permissive one, and a tab-forged source url must NOT reach the licence column.
8// usage: nx_asset_prov_gate (no args)
9// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
10import "nx_syscalls.nx"
11import "nx_gate_verdict.nx"
12import "nx_gatekit_lib.nx"
13import "nx_tool_run.nx"
14import "nx_asset_prov_lib.nx"
15
16const PG_DIR: *u8 = "/tmp/nx_asset_prov_gate"
17const PG_JRNL: *u8 = "/tmp/nx_asset_prov_gate/assets.jrnl"
18const PG_JRNL_ARG: *u8 = "jrnl=/tmp/nx_asset_prov_gate/assets.jrnl"
19const PG_A: *u8 = "/tmp/nx_asset_prov_gate/a.bin"
20const PG_B: *u8 = "/tmp/nx_asset_prov_gate/b.bin"
21const PG_T: *u8 = "/tmp/nx_asset_prov_gate/tampered.bin"
22const PG_ELF_LIVE: *u8 = "./nx_asset_prov.elf"
23const PG_ELF_STAGE: *u8 = "./nx_asset_prov.sov.elf.new"
24const PG_ELF_BUILD: *u8 = "buildroot/_build/nx_asset_prov.sov.elf"
25const PG_DIR_MODE: i64 = 493
26const PG_FILE_MODE: i64 = 420
27const PG_FIX_BYTES: i64 = 1024
28const PG_SYS_UNLINKAT: i64 = 263
29const PG_CAP: i64 = 65536
30const PG_TMO_MS: i64 = 20000
31const PG_LIC_OK: *u8 = "apache-2.0"
32const PG_LIC_NONREDIST: *u8 = "cc-by-nc-4.0"
33const PG_LIC_UNVERIFIED: *u8 = "stability-community"
34const PG_LIC_UNKNOWN: *u8 = "not-a-licence-anyone-read"
35const PG_ABSENT_SHA: *u8 = "0000000000000000000000000000000000000000000000000000000000000000"
36
37func pg_write_file(path: *u8, b: *u8, n: i64) -> i64 {
38 let fd: i64 = sys_openat_wr(path, PG_FILE_MODE)
39 if fd < 0 { return 0 - 1 }
40 var done: i64 = 0
41 while done < n { let k: i64 = sys_write(fd, b + done, n - done); if k <= 0 { break } done = done + k }
42 sys_close(fd)
43 return done
44}
45func pg_unlink(path: *u8) -> i64 { __syscall(PG_SYS_UNLINKAT, AT_FDCWD, path, 0, 0, 0, 0) return 0 }
46func pg_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 }
47func pg_call(elf: *u8, a1: *u8, a2: *u8, a3: *u8, a4: *u8, a5: *u8, a6: *u8, out: *u8, cap: i64, bl: *i64) -> i64 {
48 let av: *i64 = sys_mmap(8 * 8) as *i64
49 av[0] = elf as i64; av[1] = a1 as i64; av[2] = a2 as i64; av[3] = a3 as i64; av[4] = a4 as i64; av[5] = a5 as i64; av[6] = a6 as i64; av[7] = 0
50 var n: i64 = 1
51 while n < 7 { if av[n] == 0 { break } n = n + 1 }
52 av[n] = 0
53 return tr_run_capture_to(elf, av, out, cap, bl, PG_TMO_MS)
54}
55
56func main(argc: i64, argv: *i64) -> i64 {
57 let ctr: *i64 = gv_ctr()
58 gv_head("nx_asset_prov_gate -- a provenance row per ingested asset, the verdict fail-closed on the ONE rights table, private use always allowed" as *u8)
59 sys_mkdir(PG_DIR, PG_DIR_MODE)
60 pg_unlink(PG_JRNL)
61 // fixtures: two distinct assets and a tampered twin of the first
62 let a: *u8 = sys_mmap(PG_FIX_BYTES)
63 let b: *u8 = sys_mmap(PG_FIX_BYTES)
64 var i: i64 = 0
65 while i < PG_FIX_BYTES { a[i] = ((i * 7 + 3) % 251) as u8; b[i] = ((i * 11 + 5) % 241) as u8; i = i + 1 }
66 gv_check_eq("fixture-a-written" as *u8, pg_write_file(PG_A, a, PG_FIX_BYTES), PG_FIX_BYTES, ctr)
67 gv_check_eq("fixture-b-written" as *u8, pg_write_file(PG_B, b, PG_FIX_BYTES), PG_FIX_BYTES, ctr)
68 a[PG_FIX_BYTES / 2] = (a[PG_FIX_BYTES / 2] as i64 + 1) as u8
69 gv_check_eq("tampered-twin-written (one byte moved)" as *u8, pg_write_file(PG_T, a, PG_FIX_BYTES), PG_FIX_BYTES, ctr)
70 let sa: *u8 = sys_mmap(PV_SHA_HEX + 1)
71 let sb: *u8 = sys_mmap(PV_SHA_HEX + 1)
72 let st: *u8 = sys_mmap(PV_SHA_HEX + 1)
73 gv_check_eq("fixture-a-hashed-whole" as *u8, pv_hash_file(PG_A, sa), PG_FIX_BYTES, ctr)
74 gv_check_eq("fixture-b-hashed-whole" as *u8, pv_hash_file(PG_B, sb), PG_FIX_BYTES, ctr)
75 gv_check_eq("tampered-twin-hashed-whole" as *u8, pv_hash_file(PG_T, st), PG_FIX_BYTES, ctr)
76 gv_check("sha-is-64-hex" as *u8, pv_is_sha_hex(sa), ctr)
77 gv_check("one-byte-tamper-changes-the-sha" as *u8, (lg_ceq(sa, st) == 0) as i64, ctr)
78 let ctx: *i64 = lg_ctx()
79 gv_need("rights-table-readable (knowledge/model_license.conf)" as *u8, (ctx[3] >= 0) as i64, ctr)
80 gv_check("rights-table-carries-the-three-control-licences" as *u8, ((lg_find_license(ctx, PG_LIC_OK) >= 0) as i64) * ((lg_find_license(ctx, PG_LIC_NONREDIST) >= 0) as i64) * ((lg_find_license(ctx, PG_LIC_UNVERIFIED) >= 0) as i64), ctr)
81 let res: *i64 = sys_mmap(8 * PV_RES_N) as *i64
82 // 1. no journal at all
83 var rc: i64 = pv_verdict(PG_JRNL, sa, res)
84 gv_check_eq("no-journal-REFUSES" as *u8, rc, LG_RC_REFUSE, ctr)
85 gv_check_eq("no-journal-reason-NO-JOURNAL" as *u8, res[PV_RES_REASON], PV_R_NO_JOURNAL, ctr)
86 // 2. a journal with somebody else's row: still no row for a
87 gv_check("row-written-for-b" as *u8, (pv_row_write(PG_JRNL, sb, "http://fixture/b" as *u8, PG_LIC_OK, "gate" as *u8, pv_now()) > 0) as i64, ctr)
88 rc = pv_verdict(PG_JRNL, sa, res)
89 gv_check_eq("absent-row-REFUSES" as *u8, rc, LG_RC_REFUSE, ctr)
90 gv_check_eq("absent-row-reason-NO-ROW" as *u8, res[PV_RES_REASON], PV_R_NO_ROW, ctr)
91 gv_check_eq("absent-row-private-use-still-allowed" as *u8, res[PV_RES_PRIVATE], 1, ctr)
92 // 3. a licence id the table does not carry: the absence IS the refusal
93 pv_row_write(PG_JRNL, sa, "http://fixture/a" as *u8, PG_LIC_UNKNOWN, "gate" as *u8, pv_now())
94 rc = pv_verdict(PG_JRNL, sa, res)
95 gv_check_eq("unknown-licence-REFUSES" as *u8, rc, LG_RC_REFUSE, ctr)
96 gv_check_eq("unknown-licence-reason-UNKNOWN-LICENCE" as *u8, res[PV_RES_REASON], PV_R_UNKNOWN_LIC, ctr)
97 // 4. a verified licence whose redistribution right is NO
98 pv_row_write(PG_JRNL, sa, "http://fixture/a" as *u8, PG_LIC_NONREDIST, "gate" as *u8, pv_now())
99 rc = pv_verdict(PG_JRNL, sa, res)
100 gv_check_eq("non-redistributable-REFUSES" as *u8, rc, LG_RC_REFUSE, ctr)
101 gv_check_eq("non-redistributable-reason" as *u8, res[PV_RES_REASON], PV_R_NON_REDIST, ctr)
102 gv_check_eq("non-redistributable-redist-right-NO" as *u8, res[PV_RES_REDIST], LG_NO, ctr)
103 // 5. an UNVERIFIED permissive licence is capped at CONDITIONAL: a licence nobody read cannot grant YES
104 pv_row_write(PG_JRNL, sa, "http://fixture/a" as *u8, PG_LIC_UNVERIFIED, "gate" as *u8, pv_now())
105 rc = pv_verdict(PG_JRNL, sa, res)
106 gv_check_eq("unverified-licence-REVIEWS-not-ships" as *u8, rc, LG_RC_REVIEW, ctr)
107 gv_check_eq("unverified-reason-CAPPED" as *u8, res[PV_RES_REASON], PV_R_UNVERIFIED, ctr)
108 gv_check_eq("unverified-redist-capped-at-CONDITIONAL" as *u8, res[PV_RES_REDIST], LG_COND, ctr)
109 // 6. a verified permissive licence ships; LAST ROW WINS over the three refusals above
110 pv_row_write(PG_JRNL, sa, "http://fixture/a" as *u8, PG_LIC_OK, "gate" as *u8, pv_now())
111 rc = pv_verdict(PG_JRNL, sa, res)
112 gv_check_eq("verified-permissive-SHIPS" as *u8, rc, LG_RC_OK, ctr)
113 gv_check_eq("verified-permissive-reason-OK" as *u8, res[PV_RES_REASON], PV_R_OK, ctr)
114 gv_check_eq("rows-for-a-counted (last of four wins)" as *u8, res[PV_RES_ROWS], 4, ctr)
115 // 7. a later relicence supersedes by a NEW row, never by an edit
116 pv_row_write(PG_JRNL, sa, "http://fixture/a" as *u8, PG_LIC_NONREDIST, "gate" as *u8, pv_now())
117 rc = pv_verdict(PG_JRNL, sa, res)
118 gv_check_eq("relicence-by-new-row-supersedes (REFUSE again)" as *u8, rc, LG_RC_REFUSE, ctr)
119 gv_check_eq("rows-for-a-now-five" as *u8, res[PV_RES_ROWS], 5, ctr)
120 // 8. the tampered twin has no row: a sha is an identity, a filename is not
121 rc = pv_verdict(PG_JRNL, st, res)
122 gv_check_eq("tampered-twin-REFUSES-NO-ROW" as *u8, rc, LG_RC_REFUSE, ctr)
123 gv_check_eq("tampered-twin-reason-NO-ROW" as *u8, res[PV_RES_REASON], PV_R_NO_ROW, ctr)
124 // 9. a tab-forged source url must not move the licence column
125 let forged: *u8 = sys_mmap(128)
126 var o: i64 = lg_put(forged, 0, "http://fixture/forge" as *u8)
127 forged[o] = 9 as u8; o = o + 1
128 o = lg_put(forged, o, PG_LIC_OK)
129 forged[o] = 0 as u8
130 pv_row_write(PG_JRNL, sb, forged, PG_LIC_UNKNOWN, "gate" as *u8, pv_now())
131 rc = pv_verdict(PG_JRNL, sb, res)
132 gv_check_eq("neg-control-tab-forged-url-cannot-reach-the-licence-column (UNKNOWN-LICENCE, not apache)" as *u8, res[PV_RES_REASON], PV_R_UNKNOWN_LIC, ctr)
133 gv_check_eq("forged-row-REFUSES" as *u8, rc, LG_RC_REFUSE, ctr)
134 // 10. bite: the verdict fires on an absent sha and is silent on a verified permissive one (b re-rowed apache)
135 pv_row_write(PG_JRNL, sb, "http://fixture/b" as *u8, PG_LIC_OK, "gate" as *u8, pv_now())
136 let rc_bad: i64 = pv_verdict(PG_JRNL, PG_ABSENT_SHA, res)
137 let rc_good: i64 = pv_verdict(PG_JRNL, sb, res)
138 gv_bite("neg-control-verdict-fires-on-absent-sha-silent-on-verified-permissive" as *u8, (rc_bad != LG_RC_OK) as i64, (rc_good != LG_RC_OK) as i64, ctr)
139 gv_kv("rows_a" as *u8, 5)
140 gv_kv("fixture_bytes" as *u8, PG_FIX_BYTES)
141 // 11. the CLI as the second witness on the same scratch journal
142 var elf: *u8 = 0 as *u8
143 if pg_exists(PG_ELF_LIVE) == 1 { elf = PG_ELF_LIVE }
144 else { if pg_exists(PG_ELF_STAGE) == 1 { elf = PG_ELF_STAGE } else { if pg_exists(PG_ELF_BUILD) == 1 { elf = PG_ELF_BUILD } } }
145 gv_need("cli-witness-present (live, staged or build fossil of nx_asset_prov)" as *u8, ((elf as i64) != 0) as i64, ctr)
146 if (elf as i64) != 0 {
147 let out: *u8 = sys_mmap(PG_CAP)
148 let bl: *i64 = sys_mmap(16) as *i64
149 let rc1: i64 = pg_call(elf, "verdict" as *u8, PG_T, PG_JRNL_ARG, 0 as *u8, 0 as *u8, 0 as *u8, out, PG_CAP, bl)
150 gv_check("cli-verdict-on-tampered-produced-output" as *u8, (bl[0] > 0) as i64, ctr)
151 gv_check_eq("cli-verdict-on-tampered-exits-REFUSE" as *u8, rc1, LG_RC_REFUSE, ctr)
152 gv_check("cli-verdict-on-tampered-names-NO-ROW" as *u8, gk_out_has(out, bl[0], "reason=NO-ROW" as *u8), ctr)
153 let rc2: i64 = pg_call(elf, "row" as *u8, PG_T, "http://fixture/t" as *u8, PG_LIC_OK, "gate-cli" as *u8, PG_JRNL_ARG, out, PG_CAP, bl)
154 gv_check_eq("cli-row-then-verdict-SHIP_OK-exit-0" as *u8, rc2, LG_RC_OK, ctr)
155 gv_check("cli-row-receipt-present" as *u8, gk_out_has(out, bl[0], "ASSET-PROV-ROW sha=" as *u8), ctr)
156 gv_check("cli-row-verdict-line-SHIP_OK" as *u8, gk_out_has(out, bl[0], "verdict=SHIP_OK" as *u8), ctr)
157 let rc3: i64 = pv_verdict(PG_JRNL, st, res)
158 gv_check_eq("lib-reads-the-row-the-cli-wrote (two witnesses agree)" as *u8, rc3, LG_RC_OK, ctr)
159 let rc4: i64 = pg_call(elf, "verdict-sha" as *u8, "not-hex" as *u8, PG_JRNL_ARG, 0 as *u8, 0 as *u8, 0 as *u8, out, PG_CAP, bl)
160 gv_check_eq("cli-refuses-a-non-hex-sha-as-usage" as *u8, rc4, 2, ctr)
161 }
162 // 12. THE EXPORT DOOR reads the verdict in-process: nx_mesh2glb on the same scratch journal (staged twin first, then live)
163 var door: *u8 = 0 as *u8
164 if pg_exists("./nx_mesh2glb.sov.elf.new" as *u8) == 1 { door = "./nx_mesh2glb.sov.elf.new" as *u8 }
165 else { if pg_exists("./nx_mesh2glb.elf" as *u8) == 1 { door = "./nx_mesh2glb.elf" as *u8 } }
166 gv_need("export-door-present (nx_mesh2glb staged or live)" as *u8, ((door as i64) != 0) as i64, ctr)
167 if (door as i64) != 0 {
168 let dout: *u8 = sys_mmap(PG_CAP)
169 let dbl: *i64 = sys_mmap(16) as *i64
170 // a.bin's LAST row is cc-by-nc-4.0 (step 7): the door must refuse by name and write nothing
171 let drc: i64 = pg_call(door, PG_A, "/tmp/nx_asset_prov_gate/refused.glb" as *u8, PG_JRNL_ARG, 0 as *u8, 0 as *u8, 0 as *u8, dout, PG_CAP, dbl)
172 gv_check("door-refuses-a-non-redistributable-asset-BY-NAME" as *u8, gk_out_has(dout, dbl[0], "EXPORT-REFUSED provenance:" as *u8), ctr)
173 gv_check("door-refusal-names-NON-REDISTRIBUTABLE" as *u8, gk_out_has(dout, dbl[0], "reason=NON-REDISTRIBUTABLE" as *u8), ctr)
174 gv_check("door-refusal-exits-non-zero" as *u8, (drc != 0) as i64, ctr)
175 gv_check("door-refusal-wrote-no-output" as *u8, (pg_exists("/tmp/nx_asset_prov_gate/refused.glb" as *u8) == 0) as i64, ctr)
176 // a house asset (no row) passes the provenance check and reaches the format check
177 let hb: *u8 = sys_mmap(PG_FIX_BYTES)
178 var hi: i64 = 0
179 while hi < PG_FIX_BYTES { hb[hi] = ((hi * 13 + 7) % 239) as u8; hi = hi + 1 }
180 pg_write_file("/tmp/nx_asset_prov_gate/house.bin" as *u8, hb, PG_FIX_BYTES)
181 pg_call(door, "/tmp/nx_asset_prov_gate/house.bin" as *u8, "/tmp/nx_asset_prov_gate/house.glb" as *u8, PG_JRNL_ARG, 0 as *u8, 0 as *u8, 0 as *u8, dout, PG_CAP, dbl)
182 gv_check("door-passes-a-house-asset (no row) to the format check" as *u8, gk_out_has(dout, dbl[0], "PROVENANCE house-asset (no row)" as *u8), ctr)
183 gv_check("door-does-not-refuse-the-house-asset-on-provenance" as *u8, (gk_out_has(dout, dbl[0], "EXPORT-REFUSED" as *u8) == 0) as i64, ctr)
184 // b.bin's LAST row is apache-2.0 (step 10): verified permissive ships through the provenance check
185 pg_call(door, PG_B, "/tmp/nx_asset_prov_gate/b.glb" as *u8, PG_JRNL_ARG, 0 as *u8, 0 as *u8, 0 as *u8, dout, PG_CAP, dbl)
186 gv_check("door-passes-a-verified-permissive-asset (verdict=SHIP_OK on the line)" as *u8, gk_out_has(dout, dbl[0], "verdict=SHIP_OK" as *u8), ctr)
187 gv_check("door-does-not-refuse-the-permissive-asset-on-provenance" as *u8, (gk_out_has(dout, dbl[0], "EXPORT-REFUSED" as *u8) == 0) as i64, ctr)
188 }
189 return gv_verdict("NX-ASSET-PROV" as *u8, ctr, "every ingested asset carries a provenance row on the ONE rights table; the verdict refuses no row, an unknown licence and a non-redistributable one, reviews an unread one, ships a verified permissive one, supersedes by a new row and cannot be forged through a url" as *u8)
190}