nx_asset_access_gate.nx source
↩ module page · 117 lines · 7157 B
1// nx_asset_access_gate.nx -- KAT + TEETH for nx_asset_access (R6 of the universal org-tooling arc).
2//
3// Proves the asset PDP is DENY-BY-DEFAULT + FAIL-CLOSED over a real content-addressed record's
4// classification field (decoded via nx_asset_record):
5// (a) public -> readable by EVERYONE (ANON..OWNER all ALLOW)
6// (b) professional -> ANON/FAMILY DENY, PRO/OWNER ALLOW (the professional split)
7// (c) private -> only OWNER ALLOW; ANON/FAMILY/PRO DENY (the private split)
8// (d) operator-only -> only OWNER ALLOW (the image-stack "operator-only" law)
9// TEETH (must DENY -- the fail-closed proof a default-allow system would get WRONG):
10// (e) NO classification field -> DENY for EVERY level, including OWNER
11// (f) UNRECOGNIZED classification ("bogus") -> DENY for OWNER (fail-closed, not "allow if unsure")
12// (g) private @ ANON -> DENY (the public internet can never read a private asset)
13//
14// Verdict appended to knowledge/status/asset_access_gate.log (additive law #13).
15// expect_exit: 0 license_tier: ORIGINAL
16import "nx_syscalls.nx"
17import "nx_canon_cid.nx"
18import "nx_asset_record.nx"
19import "nx_asset_access.nx"
20import "nx_gate_verdict.nx"
21
22func g_puts(logfd: i64, s: *u8) -> i64 {
23 var n: i64 = 0
24 while s[n] != (0 as u8) { n = n + 1 }
25 sys_write(1, s, n)
26 if logfd > 0 { sys_write(logfd, s, n) }
27 return 0
28}
29func g_putn(logfd: i64, v: i64) -> i64 {
30 let bb: *u8 = sys_mmap(28)
31 var m: i64 = v
32 if m < 0 { g_puts(logfd, "-\x00" as *u8); m = 0 - m }
33 let t: *u8 = sys_mmap(28)
34 var k: i64 = 0
35 if m == 0 { t[0] = 48 as u8; k = 1 }
36 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
37 var i: i64 = 0
38 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
39 sys_write(1, bb, k)
40 if logfd > 0 { sys_write(logfd, bb, k) }
41 return 0
42}
43func E() -> *u8 { return "\x00" as *u8 }
44func sset(a: *i64, i: i64, s: *u8) -> i64 { a[i] = s as i64; return 0 }
45
46// build a minimal asset record with the given classification (cls=E() -> NO classification field).
47func mk_classified(cls: *u8, out: *u8) -> i64 {
48 let core: *i64 = sys_mmap(8 * 8) as *i64
49 sset(core, 0, "doc\x00" as *u8); sset(core, 1, "Asset\x00" as *u8); sset(core, 2, E())
50 sset(core, 3, "2026-06-20\x00" as *u8); sset(core, 4, "a-1\x00" as *u8); sset(core, 5, E()); sset(core, 6, E())
51 let pz: *i64 = sys_mmap(8 * 8) as *i64
52 sset(pz, 0, E()); sset(pz, 1, E()); sset(pz, 2, E()); sset(pz, 3, E()); sset(pz, 4, E()); sset(pz, 5, E())
53 let mz: *i64 = sys_mmap(8 * 8) as *i64
54 sset(mz, 0, E()); sset(mz, 1, E()); sset(mz, 2, E()); sset(mz, 3, E())
55 let oz: *i64 = sys_mmap(8 * 8) as *i64
56 sset(oz, 0, E()); sset(oz, 1, E()); sset(oz, 2, E()); sset(oz, 3, "1\x00" as *u8); sset(oz, 4, cls); sset(oz, 5, E())
57 let keys: *i64 = sys_mmap(8 * 48) as *i64
58 let vals: *i64 = sys_mmap(8 * 48) as *i64
59 let n: i64 = ar_fields(core, pz, mz, oz, keys, vals)
60 return ar_encode(keys, vals, n, out)
61}
62
63// assert aa_allowed(aa_decide(rec,n,level)) == want; logs + returns 1 PASS / 0 FAIL.
64func expect(logfd: i64, label: *u8, rec: *u8, n: i64, level: i64, want: i64) -> i64 {
65 let got: i64 = aa_allowed(aa_decide(rec, n, level))
66 g_puts(logfd, label); g_puts(logfd, " -> allowed=\x00" as *u8); g_putn(logfd, got); g_puts(logfd, " want=\x00" as *u8); g_putn(logfd, want); g_puts(logfd, ": \x00" as *u8)
67 if got == want { g_puts(logfd, "PASS\n\x00" as *u8); return 1 }
68 g_puts(logfd, "FAIL\n\x00" as *u8); return 0
69}
70
71func main() -> i64 {
72 let logfd: i64 = sys_openat_append("knowledge/status/asset_access_gate.log\x00" as *u8, 0x1a4)
73 g_puts(logfd, "=== ASSET-ACCESS-GATE (R6: deny-by-default, fail-closed asset PDP) ===\n\x00" as *u8)
74
75 var pass: i64 = 0
76 var total: i64 = 0
77
78 let rpub: *u8 = sys_mmap(8192); let npub: i64 = mk_classified("public\x00" as *u8, rpub)
79 let rpro: *u8 = sys_mmap(8192); let npro: i64 = mk_classified("professional\x00" as *u8, rpro)
80 let rpriv: *u8 = sys_mmap(8192); let npriv: i64 = mk_classified("private\x00" as *u8, rpriv)
81 let ropr: *u8 = sys_mmap(8192); let nopr: i64 = mk_classified("operator-only\x00" as *u8, ropr)
82 let rnone: *u8 = sys_mmap(8192); let nnone: i64 = mk_classified(E(), rnone)
83 let rbog: *u8 = sys_mmap(8192); let nbog: i64 = mk_classified("bogus\x00" as *u8, rbog)
84
85 // (a) public -> everyone
86 total = total + 1; pass = pass + expect(logfd, " (a1) public @ ANON\x00" as *u8, rpub, npub, AA_ANON(), 1)
87 total = total + 1; pass = pass + expect(logfd, " (a2) public @ OWNER\x00" as *u8, rpub, npub, AA_OWNER(), 1)
88 // (b) professional split
89 total = total + 1; pass = pass + expect(logfd, " (b1) professional @ ANON\x00" as *u8, rpro, npro, AA_ANON(), 0)
90 total = total + 1; pass = pass + expect(logfd, " (b2) professional @ FAMILY\x00" as *u8, rpro, npro, AA_FAMILY(), 0)
91 total = total + 1; pass = pass + expect(logfd, " (b3) professional @ PRO\x00" as *u8, rpro, npro, AA_PRO(), 1)
92 total = total + 1; pass = pass + expect(logfd, " (b4) professional @ OWNER\x00" as *u8, rpro, npro, AA_OWNER(), 1)
93 // (c) private split -- only the owner
94 total = total + 1; pass = pass + expect(logfd, " (c1) private @ PRO\x00" as *u8, rpriv, npriv, AA_PRO(), 0)
95 total = total + 1; pass = pass + expect(logfd, " (c2) private @ OWNER\x00" as *u8, rpriv, npriv, AA_OWNER(), 1)
96 // (d) operator-only -- only the owner (image-stack law)
97 total = total + 1; pass = pass + expect(logfd, " (d1) operator-only @ FAMILY\x00" as *u8, ropr, nopr, AA_FAMILY(), 0)
98 total = total + 1; pass = pass + expect(logfd, " (d2) operator-only @ OWNER\x00" as *u8, ropr, nopr, AA_OWNER(), 1)
99 // (e) TEETH: no classification -> DENY for everyone, incl OWNER (fail-closed)
100 total = total + 1; pass = pass + expect(logfd, " (e1) NO-class @ ANON (fail-closed)\x00" as *u8, rnone, nnone, AA_ANON(), 0)
101 total = total + 1; pass = pass + expect(logfd, " (e2) NO-class @ OWNER (fail-closed)\x00" as *u8, rnone, nnone, AA_OWNER(), 0)
102 // (f) TEETH: unrecognized class -> DENY for OWNER (not "allow if unsure")
103 total = total + 1; pass = pass + expect(logfd, " (f) bogus-class @ OWNER (fail-closed)\x00" as *u8, rbog, nbog, AA_OWNER(), 0)
104 // (g) TEETH: private @ ANON -> the public internet can never read a private asset
105 total = total + 1; pass = pass + expect(logfd, " (g) private @ ANON (internet locked out)\x00" as *u8, rpriv, npriv, AA_ANON(), 0)
106
107 g_puts(logfd, "ASSET-ACCESS-GATE passed \x00" as *u8); g_putn(logfd, pass); g_puts(logfd, "/\x00" as *u8); g_putn(logfd, total)
108 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
109 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
110 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
111 let ctr__dry: *i64 = gv_ctr()
112 ctr__dry[0] = pass
113 ctr__dry[1] = total
114 let rc__dry: i64 = gv_verdict("ASSET-ACCESS-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
115 sys_exit(rc__dry)
116 return rc__dry
117}