code wiki / _hdl_build / nx_build_role_gate.nx

nx_build_role_gate.nx source

↩ module page · 93 lines · 4951 B

1// nx_build_role_gate.nx -- R3 of BACKEND-BUILD GOVERNANCE: the ROLE-SPLIT ENFORCEMENT gate. Encodes the 2// operator's PM+Librarian split as a DATA-DRIVEN authorization policy (mirrors nx_access_lib's ag_allow): 3// every governance responsibility CLASS has a required owning ROLE, and role_ok(class, owner) ALLOWS only 4// the correct pairing -- so a responsibility claimed by the WRONG role is DENIED. This is what keeps the 5// R0-census owner column honest going forward. 6// LIB (catalog/SSOT) owns: CATALOG, LIVE_VIEW, DEDUP 7// PM (control-plane) owns: SUBMIT, STATE, OWNERSHIP, AUDIT, NOBYPASS, POLICY 8// P+L (shared) owns: ROLE_ASSIGN (PM or LIB both acceptable) 9// GATE w/ NEGATIVE CONTROLS: correct pairings ALLOW; cross-role claims DENY. GREEN iff every case matches 10// its expected verdict (so the deny cases are load-bearing, not decorative). 11// Sovereign: imports only nx_syscalls. license_tier: ORIGINAL expect_exit: 0 12import "nx_syscalls.nx" 13 14func rg_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 15func rg_p(s: *u8) -> i64 { let n: i64 = rg_len(s); sys_write(1, s, n); return 0 } 16func rg_pn(v: i64) -> i64 { 17 let bb: *u8 = sys_mmap(28); var m: i64 = v 18 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 19 let t: *u8 = sys_mmap(28); var k: i64 = 0 20 if m == 0 { t[0] = 48 as u8; k = 1 } 21 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 22 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 23 sys_write(1, bb, k); return 0 24} 25func rg_streq(a: *u8, b: *u8) -> i64 { 26 var i: i64 = 0 27 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 28 if b[i] != (0 as u8) { return 0 } 29 return 1 30} 31 32// the policy: responsibility class -> required owning role ("LIB" / "PM" / "PL" / "??"). 33func required_role(cls: *u8) -> *u8 { 34 if rg_streq(cls, "CATALOG" as *u8) == 1 { return "LIB" as *u8 } 35 if rg_streq(cls, "LIVE_VIEW" as *u8) == 1 { return "LIB" as *u8 } 36 if rg_streq(cls, "DEDUP" as *u8) == 1 { return "LIB" as *u8 } 37 if rg_streq(cls, "SUBMIT" as *u8) == 1 { return "PM" as *u8 } 38 if rg_streq(cls, "STATE" as *u8) == 1 { return "PM" as *u8 } 39 if rg_streq(cls, "OWNERSHIP" as *u8) == 1 { return "PM" as *u8 } 40 if rg_streq(cls, "AUDIT" as *u8) == 1 { return "PM" as *u8 } 41 if rg_streq(cls, "NOBYPASS" as *u8) == 1 { return "PM" as *u8 } 42 if rg_streq(cls, "POLICY" as *u8) == 1 { return "PM" as *u8 } 43 if rg_streq(cls, "ROLE_ASSIGN" as *u8) == 1 { return "PL" as *u8 } 44 return "??" as *u8 45} 46// DENY-by-default authorization: 1 iff owner is allowed to own this class. 47func role_ok(cls: *u8, owner: *u8) -> i64 { 48 let req: *u8 = required_role(cls) 49 if rg_streq(req, "??" as *u8) == 1 { return 0 } // unknown class -> deny 50 if rg_streq(req, "PL" as *u8) == 1 { // shared -> PM, LIB, or P+L 51 if rg_streq(owner, "PM" as *u8) == 1 { return 1 } 52 if rg_streq(owner, "LIB" as *u8) == 1 { return 1 } 53 if rg_streq(owner, "P+L" as *u8) == 1 { return 1 } 54 return 0 55 } 56 if rg_streq(owner, req) == 1 { return 1 } 57 return 0 58} 59 60// st[0]=ok st[1]=mismatch 61func check(cls: *u8, owner: *u8, expect: i64, st: *i64) -> i64 { 62 let r: i64 = role_ok(cls, owner) 63 rg_p(" role_ok(" as *u8); rg_p(cls); rg_p("," as *u8); rg_p(owner); rg_p(")=" as *u8); rg_pn(r) 64 rg_p(" expect=" as *u8); rg_pn(expect) 65 if r == expect { rg_p(" OK\n" as *u8); st[0] = st[0] + 1 } else { rg_p(" !!MISMATCH\n" as *u8); st[1] = st[1] + 1 } 66 return 0 67} 68 69func main() -> i64 { 70 rg_p("=== nx_build_role_gate: R3 role-split enforcement (PM control-plane + LIB catalog) ===\n" as *u8) 71 let st: *i64 = sys_mmap(32) as *i64 72 st[0] = 0; st[1] = 0 73 // correct pairings -> ALLOW 74 check("CATALOG" as *u8, "LIB" as *u8, 1, st) 75 check("LIVE_VIEW" as *u8, "LIB" as *u8, 1, st) 76 check("DEDUP" as *u8, "LIB" as *u8, 1, st) 77 check("NOBYPASS" as *u8, "PM" as *u8, 1, st) 78 check("AUDIT" as *u8, "PM" as *u8, 1, st) 79 check("POLICY" as *u8, "PM" as *u8, 1, st) 80 check("ROLE_ASSIGN" as *u8, "PM" as *u8, 1, st) // shared accepts PM 81 check("ROLE_ASSIGN" as *u8, "LIB" as *u8, 1, st) // shared accepts LIB 82 // cross-role claims -> DENY (negative controls) 83 check("CATALOG" as *u8, "PM" as *u8, 0, st) // catalog is LIB's, not PM's 84 check("NOBYPASS" as *u8, "LIB" as *u8, 0, st) // control-plane is PM's, not LIB's 85 check("AUDIT" as *u8, "LIB" as *u8, 0, st) 86 check("DEDUP" as *u8, "PM" as *u8, 0, st) 87 check("BOGUS_CLASS" as *u8, "PM" as *u8, 0, st) // unknown class -> deny-by-default 88 89 rg_p(" RESULT ok=" as *u8); rg_pn(st[0]); rg_p(" mismatch=" as *u8); rg_pn(st[1]); rg_p(" verdict=" as *u8) 90 if st[1] == 0 { rg_p("GREEN (role-split enforced: correct owners allowed, cross-role claims denied)\n" as *u8); sys_exit(0); return 0 } 91 rg_p("RED\n" as *u8) 92 sys_exit(1); return 1 93}