nx_hr_sov.nx source
↩ module page · 197 lines · 13688 B
1// nx_hr_sov.nx -- the SOVEREIGN-STORED HR directory: the same access-SSOT semantics as nx_hr (cred_id-keyed,
2// invited/active/suspended lifecycle, deny-by-default, append-only history) but persisted on nx_seg_store + canon
3// records instead of a TAB-delimited log (operator NO-TSV doctrine [[feedback-no-3rd-party-tsv-sovereign-store]]).
4// This is the ADOPTABLE FOUNDATION for the HR team's migration (filed via the coord queue): a drop-in proven core,
5// so the cutover is "point nx_hr at this + switch callers' store path together" -- NOT a risky unilateral rewrite of
6// the shared organ (torrent/gallery/hub depend on nx_hr's path). seg_store gives latest-wins (resolve), additive
7// history (audit), crash-safe commit -- for free. cred_id = SHA-256(realm|||handle) (same derivation as nx_hr/whoami).
8// record (keyed by cred_id): handle, level, family, status, ts, actor status: invited|active|suspended
9import "hub/nx_no_cookie_session.nx" // nx_ncs_derive_user_id_hash + NX_NCS_OK
10import "nx_seg_store.nx" // ss_begin / ss_add / ss_commit / ss_open / ss_hget
11import "nx_canon_cid.nx" // canon_encode
12import "nx_uxf_decode.nx" // canon_decode
13import "nx_sha256.nx" // sha256_digest (auto-segid)
14import "nx_syscalls.nx"
15const HRS_MAGIC_4096: i64 = 4096
16
17const HRS_OWNER: i64 = 3
18const HRS_MEMBER: i64 = 1
19const HRS_NONE: i64 = 0
20const HRS_ST_NONE: i64 = 0
21const HRS_ST_ACTIVE: i64 = 1
22const HRS_ST_SUSPENDED: i64 = 2
23const HRS_ST_INVITED: i64 = 3
24
25func hrs_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while 1==1 { let ca: i64=a[i] as i64; let cb: i64=b[i] as i64; if ca!=cb {return 0} if ca==0 {return 1} i=i+1 } return 1 }
26func hrs_hexenc(inp: *u8, n: i64, out: *u8) -> i64 { let hx: *u8="0123456789abcdef" as *u8; var i: i64=0; while i<n { out[i*2]=hx[((inp[i] as i64)>>4)&15]; out[i*2+1]=hx[(inp[i] as i64)&15]; i=i+1 } out[n*2]=0 as u8; return n*2 }
27func hrs_itoa(v: i64, out: *u8) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{out[i]=t[k-1-i];i=i+1} out[k]=0 as u8; return k }
28func hrs_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v }
29
30// the stable cred_id (64-hex) for (realm, handle) -- the directory key, derivable WITHOUT a login (== whoami uid).
31func hrs_cred_id(realm: *u8, realm_n: i64, handle: *u8, hn: i64, out_hex: *u8) -> i64 {
32 let uid: *u8=sys_mmap(32)
33 if nx_ncs_derive_user_id_hash(realm, realm_n, handle, hn, uid) != NX_NCS_OK { out_hex[0]=0 as u8; return 0 }
34 return hrs_hexenc(uid, 32, out_hex)
35}
36
37func hrs_atoi_n(s: *u8, n: i64) -> i64 { var v: i64=0; var i: i64=0; while i<n { let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v }
38// AUTO-SEGID: a UNIQUE seg_store commit id derived from the record content (sha256(cred|status|ts), 7 bytes ->
39// always-positive i64). Distinct (cred,status,ts) -> distinct segid (no data loss); an idempotent re-emit of the
40// IDENTICAL record -> same segid -> harmless overwrite. So the HR API needs NO segid param (true drop-in for nx_hr).
41func hrs_segid(cred_id: *u8, status: *u8, ts: i64) -> i64 {
42 let b: *u8=sys_mmap(256); var o: i64=0
43 var i: i64=0; while cred_id[i]!=(0 as u8){ b[o]=cred_id[i]; o=o+1; i=i+1 }
44 b[o]=124 as u8; o=o+1
45 var j: i64=0; while status[j]!=(0 as u8){ b[o]=status[j]; o=o+1; j=j+1 }
46 b[o]=124 as u8; o=o+1
47 let tss: *u8=sys_mmap(28); let tl: i64=hrs_itoa(ts, tss); var k: i64=0; while k<tl{ b[o]=tss[k]; o=o+1; k=k+1 }
48 let dig: *u8=sys_mmap(32); sha256_digest(b, o, dig)
49 var v: i64=0; var m: i64=0; while m<7 { v=(v<<8)|((dig[m] as i64)&255); m=m+1 } // 56-bit -> positive
50 return v
51}
52// canon-encode the 6-field record into out; returns blen.
53func hrs_encode_rec(handle: *u8, level: i64, family: *u8, status: *u8, ts: i64, actor: *u8, out: *u8) -> i64 {
54 let lv: *u8=sys_mmap(28); hrs_itoa(level, lv)
55 let tss: *u8=sys_mmap(28); hrs_itoa(ts, tss)
56 let keys: *i64=sys_mmap(8*7) as *i64; let vals: *i64=sys_mmap(8*7) as *i64
57 keys[0]="handle" as *u8 as i64; vals[0]=handle as i64
58 keys[1]="level" as *u8 as i64; vals[1]=lv as i64
59 keys[2]="family" as *u8 as i64; vals[2]=family as i64
60 keys[3]="status" as *u8 as i64; vals[3]=status as i64
61 keys[4]="ts" as *u8 as i64; vals[4]=tss as i64
62 keys[5]="actor" as *u8 as i64; vals[5]=actor as i64
63 return canon_encode(keys, vals, 6, out)
64}
65// 1 iff a segment with this segid is ALREADY in the manifest ("seg-<segid>"). Re-committing a deterministic segid would
66// DUPLICATE the manifest entry (seg_store's contract is unique-segid-per-commit, which corrupts reads) -> dedup here
67// makes every put IDEMPOTENT (#10): re-migrating / re-provisioning the identical record is a true no-op.
68func hrs_seg_committed(prefix: *u8, segid: i64) -> i64 {
69 let segs: *i64=sys_mmap(8*260) as *i64; let ns: i64=ss_manifest(prefix, segs)
70 if ns<=0 { return 0 }
71 let want: *u8=sys_mmap(64); let p: *u8="seg-" as *u8; var o: i64=0; while p[o]!=(0 as u8){want[o]=p[o];o=o+1}
72 hrs_itoa(segid, (want as i64 + o) as *u8)
73 var i: i64=0; while i<ns { if hrs_streq(segs[i] as *u8, want)==1 { return 1 } i=i+1 } return 0
74}
75// PUT one directory record keyed by cred_id (no index touch) -- for suspend/set_role on an EXISTING cred. UNIQUE segid.
76func hrs_put(prefix: *u8, cred_id: *u8, handle: *u8, level: i64, family: *u8, status: *u8, ts: i64, actor: *u8, segid: i64) -> i64 {
77 if hrs_seg_committed(prefix, segid)==1 { return 0 }
78 let buf: *u8=sys_mmap(HRS_MAGIC_4096); let blen: i64=hrs_encode_rec(handle, level, family, status, ts, actor, buf)
79 let w: *i64=ss_begin(); ss_add(w, 1, cred_id, buf, blen); ss_commit(prefix, w, segid)
80 return 0
81}
82// ---- the roster INDEX (so seg_store can ENUMERATE distinct creds, like nx_native_config's count+NNNN) ----
83// key "idx:count" -> N ; key "idx:<i>" -> the i-th distinct cred_id (64 hex).
84func hrs_idx_key(i: i64, out: *u8) -> i64 { let p: *u8="idx:" as *u8; var o: i64=0; while p[o]!=(0 as u8){out[o]=p[o];o=o+1} let k: i64=hrs_itoa(i, (out as i64 + o) as *u8); return o+k }
85func hrs_index_count(prefix: *u8) -> i64 {
86 let h: *i64=ss_open(prefix); if (h as i64)==0 { return 0 }
87 let pp: *i64=sys_mmap(16) as *i64; let ll: *i64=sys_mmap(16) as *i64
88 if ss_hget(h, "idx:count" as *u8, pp, ll) != 1 { return 0 }
89 return hrs_atoi_n(pp[0] as *u8, ll[0])
90}
91func hrs_index_get_cred(prefix: *u8, i: i64, out64: *u8) -> i64 {
92 let ik: *u8=sys_mmap(32); hrs_idx_key(i, ik); ik[hrs_idx_key(i, ik)]=0 as u8
93 let h: *i64=ss_open(prefix); if (h as i64)==0 { return 0 }
94 let pp: *i64=sys_mmap(16) as *i64; let ll: *i64=sys_mmap(16) as *i64
95 if ss_hget(h, ik, pp, ll) != 1 { return 0 }
96 let src: *u8=pp[0] as *u8; var j: i64=0; while j<ll[0] { out64[j]=src[j]; j=j+1 } out64[ll[0]]=0 as u8
97 return 1
98}
99// PUT a record AND (if cred is new) append it to the roster index -- ONE atomic commit. for enroll/invite.
100func hrs_put_indexed(prefix: *u8, cred_id: *u8, handle: *u8, level: i64, family: *u8, status: *u8, ts: i64, actor: *u8, segid: i64) -> i64 {
101 if hrs_seg_committed(prefix, segid)==1 { return 0 } // idempotent: this exact record already committed (no manifest dup)
102 var isnew: i64 = 0; if hrs_status_code(prefix, cred_id)==HRS_ST_NONE { isnew=1 }
103 var n: i64 = 0; if isnew==1 { n=hrs_index_count(prefix) }
104 let buf: *u8=sys_mmap(HRS_MAGIC_4096); let blen: i64=hrs_encode_rec(handle, level, family, status, ts, actor, buf)
105 let w: *i64=ss_begin()
106 ss_add(w, 1, cred_id, buf, blen)
107 if isnew==1 {
108 let ik: *u8=sys_mmap(32); let ikl: i64=hrs_idx_key(n, ik); ik[ikl]=0 as u8
109 ss_add(w, 1, ik, cred_id, 64)
110 let cv: *u8=sys_mmap(28); let cl: i64=hrs_itoa(n+1, cv)
111 ss_add(w, 1, "idx:count" as *u8, cv, cl)
112 }
113 ss_commit(prefix, w, segid)
114 return 0
115}
116// read the LATEST record for cred_id -> decode into out_keys/out_vals. returns nf (>0) or <=0 if absent.
117func hrs_get(prefix: *u8, cred_id: *u8, out_keys: *i64, out_vals: *i64, maxf: i64) -> i64 {
118 let h: *i64=ss_open(prefix); if (h as i64)==0 { return 0 }
119 let pp: *i64=sys_mmap(16) as *i64; let ll: *i64=sys_mmap(16) as *i64
120 if ss_hget(h, cred_id, pp, ll) != 1 { return 0 }
121 return canon_decode(pp[0] as *u8, ll[0], out_keys, out_vals, maxf)
122}
123func hrs_field(ok: *i64, ov: *i64, nf: i64, name: *u8) -> *u8 { var i: i64=0; while i<nf { if hrs_streq(ok[i] as *u8, name)==1 { return ov[i] as *u8 } i=i+1 } return 0 as *u8 }
124
125// ENROLL active at a level (derives + returns cred_id). INVITE = enroll status=invited. SUSPEND / SET_ROLE = a new put.
126func hrs_enroll(prefix: *u8, realm: *u8, realm_n: i64, handle: *u8, hn: i64, level: i64, family: *u8, ts: i64, actor: *u8, out_cid: *u8) -> i64 {
127 if hrs_cred_id(realm, realm_n, handle, hn, out_cid) <= 0 { return 1 }
128 return hrs_put_indexed(prefix, out_cid, handle, level, family, "active" as *u8, ts, actor, hrs_segid(out_cid, "active" as *u8, ts))
129}
130func hrs_invite(prefix: *u8, realm: *u8, realm_n: i64, handle: *u8, hn: i64, level: i64, family: *u8, ts: i64, actor: *u8, out_cid: *u8) -> i64 {
131 if hrs_cred_id(realm, realm_n, handle, hn, out_cid) <= 0 { return 1 }
132 return hrs_put_indexed(prefix, out_cid, handle, level, family, "invited" as *u8, ts, actor, hrs_segid(out_cid, "invited" as *u8, ts))
133}
134func hrs_suspend(prefix: *u8, cred_id: *u8, handle: *u8, family: *u8, ts: i64, actor: *u8) -> i64 { return hrs_put(prefix, cred_id, handle, 0, family, "suspended" as *u8, ts, actor, hrs_segid(cred_id, "suspended" as *u8, ts)) }
135func hrs_set_role(prefix: *u8, cred_id: *u8, handle: *u8, level: i64, family: *u8, ts: i64, actor: *u8) -> i64 { return hrs_put(prefix, cred_id, handle, level, family, "active" as *u8, ts, actor, hrs_segid(cred_id, "active" as *u8, ts)) }
136// MIGRATE one historical record verbatim, replayed by cred_id (preserving status/ts/actor): auto-segid + index on the
137// FIRST occurrence of a cred. The TAB-log -> seg_store migrator calls this per line so ALL versions (history) survive
138// and latest-wins is preserved. Idempotent: re-migrating the identical (cred,status,ts) -> same segid -> no dup.
139func hrs_migrate(prefix: *u8, cred_id: *u8, handle: *u8, level: i64, family: *u8, status: *u8, ts: i64, actor: *u8) -> i64 {
140 return hrs_put_indexed(prefix, cred_id, handle, level, family, status, ts, actor, hrs_segid(cred_id, status, ts))
141}
142// CLAIM an invite: invited -> active at the SAME intended level (the LAN-signup completing). returns the claimed
143// level (>0), or 0 if the handle was not invited (idempotent: a re-claim of an active user returns 0).
144func hrs_claim(prefix: *u8, realm: *u8, realm_n: i64, handle: *u8, hn: i64, family: *u8, ts: i64, actor: *u8) -> i64 {
145 let cid: *u8=sys_mmap(96); if hrs_cred_id(realm, realm_n, handle, hn, cid) <= 0 { return 0 }
146 let ok: *i64=sys_mmap(64*8) as *i64; let ov: *i64=sys_mmap(64*8) as *i64
147 let nf: i64=hrs_get(prefix, cid, ok, ov, 64)
148 if nf<=0 { return 0 }
149 let st: *u8=hrs_field(ok, ov, nf, "status" as *u8)
150 if (st as i64)==0 { return 0 }
151 if hrs_streq(st, "invited" as *u8)==0 { return 0 }
152 let lvf: *u8=hrs_field(ok, ov, nf, "level" as *u8)
153 var want: i64=0; if (lvf as i64)!=0 { want=hrs_atoi(lvf) }
154 hrs_set_role(prefix, cid, handle, want, family, ts, actor)
155 return want
156}
157
158// status code of the latest record (deny-by-default: unknown -> NONE).
159func hrs_status_code(prefix: *u8, cred_id: *u8) -> i64 {
160 let ok: *i64=sys_mmap(64*8) as *i64; let ov: *i64=sys_mmap(64*8) as *i64
161 let nf: i64=hrs_get(prefix, cred_id, ok, ov, 64)
162 if nf<=0 { return HRS_ST_NONE }
163 let st: *u8=hrs_field(ok, ov, nf, "status" as *u8)
164 if (st as i64)==0 { return HRS_ST_NONE }
165 if hrs_streq(st, "active" as *u8)==1 { return HRS_ST_ACTIVE }
166 if hrs_streq(st, "invited" as *u8)==1 { return HRS_ST_INVITED }
167 if hrs_streq(st, "suspended" as *u8)==1 { return HRS_ST_SUSPENDED }
168 return HRS_ST_NONE
169}
170// THE RESOLVER (access SSOT): latest level if status=active, else 0. Unknown/suspended/invited -> 0 (deny-by-default).
171func hrs_resolve_level(prefix: *u8, cred_id: *u8) -> i64 {
172 let ok: *i64=sys_mmap(64*8) as *i64; let ov: *i64=sys_mmap(64*8) as *i64
173 let nf: i64=hrs_get(prefix, cred_id, ok, ov, 64)
174 if nf<=0 { return 0 }
175 let st: *u8=hrs_field(ok, ov, nf, "status" as *u8)
176 if (st as i64)==0 { return 0 }
177 if hrs_streq(st, "active" as *u8)==0 { return 0 }
178 let lv: *u8=hrs_field(ok, ov, nf, "level" as *u8)
179 if (lv as i64)==0 { return 0 }
180 return hrs_atoi(lv)
181}
182func hrs_is_superadmin(prefix: *u8, cred_id: *u8) -> i64 { if hrs_resolve_level(prefix, cred_id) >= HRS_OWNER { return 1 } return 0 }
183func hrs_is_invited(prefix: *u8, realm: *u8, realm_n: i64, handle: *u8, hn: i64) -> i64 {
184 let cid: *u8=sys_mmap(96); if hrs_cred_id(realm, realm_n, handle, hn, cid) <= 0 { return 0 }
185 if hrs_status_code(prefix, cid)==HRS_ST_INVITED { return 1 } return 0
186}
187// ROSTER ENUMERATION (the "logins you see"): count distinct creds whose LATEST status == want_status. Iterates the
188// index, so seg_store enumerates without a foreign format. (store, HRS_ST_ACTIVE)=active logins; (.,INVITED)=pending.
189func hrs_roster_count(prefix: *u8, want_status: i64) -> i64 {
190 let n: i64=hrs_index_count(prefix)
191 var cnt: i64=0; var i: i64=0; let credbuf: *u8=sys_mmap(72)
192 while i<n {
193 if hrs_index_get_cred(prefix, i, credbuf)==1 { if hrs_status_code(prefix, credbuf)==want_status { cnt=cnt+1 } }
194 i=i+1
195 }
196 return cnt
197}