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}