nx_rebac.nx
buildroot/runtime/nx_rebac.nx
about
nx_rebac.nx -- SOVEREIGN RELATIONSHIP-BASED ACCESS CONTROL (the Google Zanzibar / ReBAC model), the ONE shared
authorization plane for every Nishi surface. PURE LIB (no main): consumers = nx_rebac_gate (adversarial gate) +
the surfaces (relate/social/office/project) which import this and call rb_check instead of re-implementing ACLs.
WHY: the ecosystem had 3 incompatible authz philosophies (relate tenant-wall `acl:`, project `mem:`, connect
in-memory group-roles) + an UNWIRED consent primitive + zero relationship/group permissions. This unifies them:
EVERYTHING is a TUPLE (object, relation, subject) persisted in seg_store, and access is a bounded graph walk
with userset rewrites -- exactly how Google Docs / GitHub / SpiceDB / OpenFGA decide "can X do Y on Z".
friend -> (user:bob, friend, user:alice) [mutual: store both directions]
join a group -> (group:elders, member, user:bob)
share a doc -> (doc:budget, viewer, user:carol) OR (doc:budget, viewer, group:elders#member) <- userset
the tenant wall -> (tenant:andelinwest, admin, user:elderwesto) [subsumes relate acl: + project mem:]
block / defriend -> a NEW tuple with cur=0 (tombstone) -- ADDITIVE-ONLY, never delete (rule 13); latest wins.
PERMISSIONS derive from RELATIONS via a data-driven REWRITE table (rb_expand): e.g. doc.read = viewer|editor|owner.
Ties to NISHI HR: rb_check_hr composes the HR level (ag_uid_to_level / hac_gate) as a realm-scoped operator
override -- a site operator (level 3) is god within their realm; everyone else is pure deny-by-default tuples.
DENY-BY-DEFAULT everywhere; blocks outrank; bounded recursion (RB_MAXDEPTH) so userset cycles TERMINATE not hang.
license_tier: ORIGINAL
dependencies 4 imports · 6 importers
imports: nx_syscalls.nxnx_itoa_lib.nxnx_canon_cid.nxnx_seg_store.nx
imported by: nx_authz_svc.nxnx_office_serve.nxnx_rebac_gate.nxnx_relate_serve.nxnx_share_convergence_gate.nxnx_suite_share.nx
structs
| none |
consts
| 23 | const RB_MAGIC_2048: i64 = 2048 |
| 25 | const RB_MAXDEPTH: i64 = 8 // Zanzibar bounds check() recursion; a member-of cycle terminates here |
| 26 | const RB_OPERATOR: i64 = 3 // HR level that is site-operator (god within realm); mirrors HRA_LVL_OWNER |
functions
| 29 | func rb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 30 | func rb_p(s: *u8) -> i64 { sys_write(1, s, rb_slen(s)); return 0 } |
| 35 | func rb_pn(v: i64) -> i64 { nxi_out(v); return 0 } |
| 36 | func rb_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i } |
| 37 | func rb_catn(dst: *u8, off: i64, v: i64) -> i64 |
| 44 | func rb_seq(a: *u8, b: *u8) -> i64 |
| 50 | func rb_kmemeq(b: *u8, off: i64, n: i64, s: *u8) -> i64 |
| 55 | func rb_iskind(b: *u8, koff: i64, kl: i64, pfx: *u8) -> i64 { if kl <= 4 { return 0 } return rb_kmemeq(b, koff, 4, pfx) } |
| 57 | func rb_field(rec: *u8, rl: i64, want: *u8, out: *u8, cap: i64) -> i64 |
| 76 | func rb_otype(obj: *u8, out: *u8, cap: i64) -> i64 |
| 81 | func rb_has_hash(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { if (s[i] as i64) == 35 { return 1 } i = i + 1 } return 0 } |
| 83 | func rb_split_userset(s: *u8, gobj: *u8, grel: *u8) -> i64 |
| 95 | func rb_save_tuple(w: *i64, obj: *u8, rel: *u8, sub: *u8, grantor: *u8, cur: i64) -> i64 |
| 115 | func rb_put(prefix: *u8, obj: *u8, rel: *u8, sub: *u8, grantor: *u8, cur: i64) -> i64 called by 13: mainaz_handleal_doof_do_shareof_do_groupof_handle_auth+7 calls 4: ss_beginrb_save_tupless_commitsys_now_us |
| 123 | func rb_tuple_current(prefix: *u8, obj: *u8, rel: *u8, sub: *u8) -> i64 |
| 160 | func rb_expand(otype: *u8, perm: *u8, out: *i64, cap: i64) -> i64 |
| 207 | func rb_check(prefix: *u8, sub: *u8, perm: *u8, obj: *u8, depth: i64) -> i64 |
| 262 | func rb_check_hr(prefix: *u8, sub: *u8, perm: *u8, obj: *u8, hr_level: i64) -> i64 |
| 272 | func rb_tuple_current2(p1: *u8, p2: *u8, obj: *u8, rel: *u8, sub: *u8) -> i64 |
| 277 | func rb_userset_scan(store: *u8, p1: *u8, p2: *u8, sub: *u8, obj: *u8, rel: *u8, depth: i64) -> i64 |
| 312 | func rb_check2(p1: *u8, p2: *u8, sub: *u8, perm: *u8, obj: *u8, depth: i64) -> i64 |
| 332 | func rb_obj_has_owner(prefix: *u8, obj: *u8) -> i64 |
| 361 | func rb_may_grant(prefix: *u8, grantor: *u8, obj: *u8) -> i64 |
| 369 | func rb_hex(dst: *u8, src: *u8, n: i64) -> i64 called by 1: rb_resolve_handle |
| 382 | func rb_idx_lookup(idx: *u8, idxlen: i64, uidhex: *u8, uxn: i64, out_h: *u8, cap: i64) -> i64 called by 1: rb_resolve_handle |
| 412 | func rb_resolve_handle(idxpath: *u8, uid: *u8, uidn: i64, out_h: *u8, cap: i64) -> i64 |
| 422 | func rb_in_list(out: *i64, n: i64, s: *u8) -> i64 |
| 429 | func rb_list_subjects(prefix: *u8, obj: *u8, rel: *u8, out: *i64, cap: i64) -> i64 called by 9: mainaz_handleal_doof_managepageof_groupspagemain+3 calls 10: sys_mmapss_manifest_dynrb_catss_readallss_r32rb_iskind+4 |
| 473 | func rb_list_subjects_history(prefix: *u8, obj: *u8, rel: *u8, out: *i64, cap: i64) -> i64 |
| 514 | func rb_list_objects_for_sub(prefix: *u8, sub: *u8, rel: *u8, out: *i64, cap: i64) -> i64 |