nx_rights_ledger.nx source
↩ module page · 182 lines · 6752 B
1// nx_rights_ledger.nx -- the rights/consent spine for licensed talent
2// likeness + media (the DMS keystone). THE predicate:
3//
4// nx_rights_is_licensed(talent, use, now) -> 1 allow / 0 deny
5//
6// default-DENY: a use is permitted ONLY when a current, unexpired, unrevoked
7// grant for that EXACT (talent, use_scope) exists. Models a real licensing
8// contract -- a grant carries a term (expires_us) and a use-scope (what was
9// licensed); revocation supersedes a grant; the grant row is NEVER erased
10// (ADDITIVE law #13 -- it stays as the audit trail). Unknown talent,
11// unknown use, expired, and revoked all DENY by construction.
12//
13// Mirrors the proven nx_trace_consent state machine (propose/decide/check ->
14// grant/revoke/is_licensed) with licensing-specific use scopes.
15//
16// RUNG 1 (this file): semantics, in-memory. RUNG 2 (flagged, NOT a silent
17// cap): persist each row through nx_canon_cid -> nx_seg_store for the durable
18// never-lose audit trail on the NAS.
19// license_tier: ORIGINAL
20
21import "nx_syscalls.nx"
22import "nx_tier.nx"
23
24// ===== Sealed enum: NxRightState ================================
25const NX_RL_NONE: nx_int = 0 // no matching row -> default-deny
26const NX_RL_GRANTED: nx_int = 1
27const NX_RL_EXPIRED: nx_int = 2
28const NX_RL_REVOKED: nx_int = 3
29const NX_RL_N_STATES: nx_int = 4
30
31// ===== Sealed enum: NxUseScope =================================
32// the distinct things a performer can license; a grant for one use does NOT
33// authorize another (MERCH != WEB_GALLERY != AD != non-explicit AVATAR).
34const NX_USE_MERCH: nx_int = 0 // physical merchandise likeness
35const NX_USE_WEB_GALLERY: nx_int = 1 // display in owned gallery/search
36const NX_USE_AD_CAMPAIGN: nx_int = 2 // marketing / advertisement
37const NX_USE_AVATAR_NONEXPLICIT: nx_int = 3 // non-explicit synthetic avatar
38const NX_USE_N_SCOPES: nx_int = 4
39
40// ===== status codes ============================================
41const NX_RL_OK: nx_int = 0
42const NX_RL_ERR_BAD_USE: nx_int = 1
43const NX_RL_ERR_FULL: nx_int = 2
44const NX_RL_ERR_NOT_FOUND: nx_int = 3
45const NX_RL_ERR_NOT_GRANTED: nx_int = 4
46
47// ===== struct: NxLicense =======================================
48// one license row. talent_bk = talent business key (stable id; rung 2 swaps
49// to the content-addressed talent hub key).
50struct NxLicense {
51 grant_id: nx_int,
52 talent_bk: nx_int,
53 use_scope: nx_int,
54 state: nx_int,
55 granted_us: nx_size,
56 expires_us: nx_size,
57 revoked_us: nx_size,
58}
59
60struct NxRightsLedger {
61 rows: *NxLicense,
62 capacity: nx_size,
63 count: nx_size,
64}
65
66const NX_RL_ROW_BYTES: nx_size = 56 // 7 fields * 8 bytes
67
68func nx_use_is_valid(u: nx_int) -> nx_int {
69 if u < 0 { return 0 }
70 if u >= NX_USE_N_SCOPES { return 0 }
71 return 1
72}
73
74func nx_rights_ledger_new(capacity: nx_size) -> *NxRightsLedger {
75 let l: *NxRightsLedger = (sys_mmap(24)) as *NxRightsLedger
76 let bytes: nx_size = capacity * NX_RL_ROW_BYTES
77 l.rows = (sys_mmap(bytes)) as *NxLicense
78 l.capacity = capacity
79 l.count = 0
80 return l
81}
82
83func _rl_at(l: *NxRightsLedger, idx: nx_size) -> *NxLicense {
84 return (l.rows as i64 + (idx as i64) * NX_RL_ROW_BYTES) as *NxLicense
85}
86
87func _rl_find(l: *NxRightsLedger, grant_id: nx_int) -> nx_int {
88 var i: nx_size = 0
89 while i < l.count {
90 let r: *NxLicense = _rl_at(l, i)
91 if r.grant_id == grant_id { return i as i64 }
92 i = i + 1
93 }
94 return -1
95}
96
97// ===== nx_rights_grant =========================================
98// talent_bk grants use_scope from now_us for term_us (additive append).
99func nx_rights_grant(l: *NxRightsLedger,
100 grant_id: nx_int,
101 talent_bk: nx_int,
102 use_scope: nx_int,
103 now_us: nx_size,
104 term_us: nx_size) -> nx_int {
105 if nx_use_is_valid(use_scope) == 0 { return NX_RL_ERR_BAD_USE }
106 if l.count >= l.capacity { return NX_RL_ERR_FULL }
107 let r: *NxLicense = _rl_at(l, l.count)
108 r.grant_id = grant_id
109 r.talent_bk = talent_bk
110 r.use_scope = use_scope
111 r.state = NX_RL_GRANTED
112 r.granted_us = now_us
113 r.expires_us = now_us + term_us
114 r.revoked_us = 0
115 l.count = l.count + 1
116 return NX_RL_OK
117}
118
119// ===== nx_rights_revoke ========================================
120// revoke a GRANTED license by grant_id; stamps revoked_us, keeps the row.
121func nx_rights_revoke(l: *NxRightsLedger, grant_id: nx_int, now_us: nx_size) -> nx_int {
122 let idx: nx_int = _rl_find(l, grant_id)
123 if idx < 0 { return NX_RL_ERR_NOT_FOUND }
124 let r: *NxLicense = _rl_at(l, idx as nx_size)
125 if r.state != NX_RL_GRANTED { return NX_RL_ERR_NOT_GRANTED }
126 r.state = NX_RL_REVOKED
127 r.revoked_us = now_us
128 return NX_RL_OK
129}
130
131// ===== nx_rights_state =========================================
132// detailed current state of (talent, use) at now_us, for audit/UI.
133// GRANTED iff some effective grant exists; else the most relevant deny reason
134// (REVOKED beats EXPIRED beats NONE). Auto-flips lapsed grants to EXPIRED.
135func nx_rights_state(l: *NxRightsLedger, talent_bk: nx_int, use_scope: nx_int, now_us: nx_size) -> nx_int {
136 var saw_revoked: nx_int = 0
137 var saw_expired: nx_int = 0
138 var i: nx_size = 0
139 while i < l.count {
140 let r: *NxLicense = _rl_at(l, i)
141 if r.talent_bk == talent_bk {
142 if r.use_scope == use_scope {
143 if r.state == NX_RL_GRANTED {
144 if now_us > r.expires_us {
145 r.state = NX_RL_EXPIRED
146 saw_expired = 1
147 } else {
148 return NX_RL_GRANTED
149 }
150 }
151 if r.state == NX_RL_REVOKED { saw_revoked = 1 }
152 if r.state == NX_RL_EXPIRED { saw_expired = 1 }
153 }
154 }
155 i = i + 1
156 }
157 if saw_revoked == 1 { return NX_RL_REVOKED }
158 if saw_expired == 1 { return NX_RL_EXPIRED }
159 return NX_RL_NONE
160}
161
162// ===== nx_rights_is_licensed ===================================
163// THE keystone predicate. default-DENY: 1 = allowed, 0 = denied.
164func nx_rights_is_licensed(l: *NxRightsLedger, talent_bk: nx_int, use_scope: nx_int, now_us: nx_size) -> nx_int {
165 if nx_rights_state(l, talent_bk, use_scope, now_us) == NX_RL_GRANTED { return 1 }
166 return 0
167}
168
169func nx_rights_count(l: *NxRightsLedger) -> nx_size {
170 return l.count
171}
172
173func nx_rights_count_by_state(l: *NxRightsLedger, state: nx_int) -> nx_int {
174 var hits: nx_int = 0
175 var i: nx_size = 0
176 while i < l.count {
177 let r: *NxLicense = _rl_at(l, i)
178 if r.state == state { hits = hits + 1 }
179 i = i + 1
180 }
181 return hits
182}