qualia_core_db/domains/geospatial/
steward.rs1use crate::modalities::logic::deontic::{extract_deontic_opcode, OP_PERMIT};
4use crate::q_hash;
5use crate::NQuin;
6
7use super::canvas_rights;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct StewardContract {
12 pub steward_did_hash: u64,
13 pub asset_hash: u64,
14 pub scope: u64,
15 pub expiry_unix: u32,
16 pub quorum: u8,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum StewardVerdict {
22 Granted,
23 Denied(&'static str),
24 Expired,
25}
26
27pub fn validate_steward_unlock(
32 requester_hash: u64,
33 contract: &StewardContract,
34 arena_quins: &[NQuin],
35 now_unix: u32,
36) -> StewardVerdict {
37 if now_unix > contract.expiry_unix {
38 return StewardVerdict::Expired;
39 }
40
41 if canvas_rights::explicit_denial_guard(
42 contract.scope,
43 requester_hash,
44 contract.asset_hash,
45 arena_quins,
46 ) {
47 return StewardVerdict::Denied("explicit denial guard");
48 }
49
50 let unlock_path = q_hash("q42:stewardUnlock");
51 let stewards_pred = q_hash("q42:stewards");
52
53 const PATH_WINDOW: u64 = 0x7FFF_FFFF_FFFF_FF00;
59 let expected_path_bits = (unlock_path << 8) & PATH_WINDOW;
60
61 let mut has_permit = false;
62 for quin in arena_quins {
63 if extract_deontic_opcode(quin.predicate) != OP_PERMIT {
64 continue;
65 }
66 let path_bits = quin.predicate & PATH_WINDOW;
67 if quin.subject == contract.steward_did_hash
68 && path_bits == expected_path_bits
69 && quin.object == contract.asset_hash
70 && quin.context == contract.scope
71 {
72 let expiry = (quin.metadata & 0xFFFF_FFFF) as u32;
73 if now_unix <= expiry {
74 has_permit = true;
75 break;
76 }
77 }
78 }
79
80 if !has_permit {
81 return StewardVerdict::Denied("no active steward unlock permit");
82 }
83
84 let mut endorsements = 0u8;
85 for quin in arena_quins {
86 if quin.predicate == stewards_pred && quin.subject == contract.steward_did_hash {
87 if quin.object == requester_hash || quin.object == contract.asset_hash {
88 endorsements = endorsements.saturating_add(1);
89 }
90 }
91 }
92
93 if endorsements < contract.quorum {
94 return StewardVerdict::Denied("steward quorum not met");
95 }
96
97 if requester_hash != contract.steward_did_hash && endorsements == 0 {
98 return StewardVerdict::Denied("requester not authorized");
99 }
100
101 StewardVerdict::Granted
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::modalities::logic::deontic::compile_norm_quin;
108
109 fn contract() -> StewardContract {
110 StewardContract {
111 steward_did_hash: 0xAA,
112 asset_hash: 0xBB,
113 scope: 0xCC,
114 expiry_unix: 2_000_000_000,
115 quorum: 2,
116 }
117 }
118
119 fn permit_quin(contract: &StewardContract) -> NQuin {
120 compile_norm_quin(
121 contract.steward_did_hash,
122 OP_PERMIT,
123 q_hash("q42:stewardUnlock"),
124 contract.asset_hash,
125 contract.scope,
126 contract.expiry_unix,
127 false,
128 )
129 }
130
131 fn steward_endorsement(steward: u64, object: u64) -> NQuin {
132 let mut q = NQuin {
133 subject: steward,
134 predicate: q_hash("q42:stewards"),
135 object,
136 context: 0,
137 metadata: 0,
138 parity: 0,
139 };
140 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
141 q
142 }
143
144 #[test]
145 fn steward_unlock_granted() {
146 let c = contract();
147 let requester = 0xDD;
148 let quins = vec![
149 permit_quin(&c),
150 steward_endorsement(c.steward_did_hash, requester),
151 steward_endorsement(c.steward_did_hash, c.asset_hash),
152 ];
153
154 assert_eq!(
155 validate_steward_unlock(requester, &c, &quins, 1_900_000_000),
156 StewardVerdict::Granted
157 );
158 }
159
160 #[test]
161 fn steward_unlock_denied_no_permit() {
162 let c = contract();
163 let requester = 0xDD;
164 let quins = vec![
165 steward_endorsement(c.steward_did_hash, requester),
166 steward_endorsement(c.steward_did_hash, c.asset_hash),
167 ];
168
169 assert_eq!(
170 validate_steward_unlock(requester, &c, &quins, 1_900_000_000),
171 StewardVerdict::Denied("no active steward unlock permit")
172 );
173 }
174
175 #[test]
176 fn steward_unlock_expired() {
177 let c = contract();
178 let requester = 0xDD;
179 let quins = vec![
180 permit_quin(&c),
181 steward_endorsement(c.steward_did_hash, requester),
182 steward_endorsement(c.steward_did_hash, c.asset_hash),
183 ];
184
185 assert_eq!(
186 validate_steward_unlock(requester, &c, &quins, 2_100_000_000),
187 StewardVerdict::Expired
188 );
189 }
190}