code wiki / (root) / nx_asset_prov_gate.nx

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}