1use crate::generic::{
2 GenericGovernanceCaseArtifact, GenericGovernanceCaseEvaluation,
3 GenericGovernanceCaseEvaluationRequest, GenericGovernanceCaseKind, GenericGovernanceCaseState,
4 GenericGovernanceEffectiveState, GenericGovernanceFinding, GenericGovernanceFindingCode,
5};
6use crate::listing::normalize_namespace;
7
8pub fn evaluate_generic_governance_case(
9 request: &GenericGovernanceCaseEvaluationRequest,
10 now: u64,
11) -> Result<GenericGovernanceCaseEvaluation, String> {
12 request.validate()?;
13 let evaluated_at = request.evaluated_at.unwrap_or(now);
14
15 if !request
16 .listing
17 .verify_signature()
18 .map_err(|error| error.to_string())?
19 {
20 return Ok(governance_failure(
21 request,
22 evaluated_at,
23 GenericGovernanceFindingCode::ListingUnverifiable,
24 "listing signature is invalid",
25 ));
26 }
27 if !request
28 .charter
29 .verify_signature()
30 .map_err(|error| error.to_string())?
31 {
32 return Ok(governance_failure(
33 request,
34 evaluated_at,
35 GenericGovernanceFindingCode::CharterUnverifiable,
36 "governance charter signature is invalid",
37 ));
38 }
39 if let Err(error) = request.charter.body.validate() {
40 return Ok(governance_failure(
41 request,
42 evaluated_at,
43 GenericGovernanceFindingCode::CharterUnverifiable,
44 &error,
45 ));
46 }
47 if !request
48 .case
49 .verify_signature()
50 .map_err(|error| error.to_string())?
51 {
52 return Ok(governance_failure(
53 request,
54 evaluated_at,
55 GenericGovernanceFindingCode::CaseUnverifiable,
56 "governance case signature is invalid",
57 ));
58 }
59 if let Err(error) = request.case.body.validate() {
60 return Ok(governance_failure(
61 request,
62 evaluated_at,
63 GenericGovernanceFindingCode::CaseUnverifiable,
64 &error,
65 ));
66 }
67 if let Some(activation) = request.activation.as_ref() {
68 if !activation
69 .verify_signature()
70 .map_err(|error| error.to_string())?
71 {
72 return Ok(governance_failure(
73 request,
74 evaluated_at,
75 GenericGovernanceFindingCode::ActivationUnverifiable,
76 "trust activation signature is invalid",
77 ));
78 }
79 if let Err(error) = activation.body.validate() {
80 return Ok(governance_failure(
81 request,
82 evaluated_at,
83 GenericGovernanceFindingCode::ActivationUnverifiable,
84 &error,
85 ));
86 }
87 }
88 if let Some(prior_case) = request.prior_case.as_ref() {
89 if !prior_case
90 .verify_signature()
91 .map_err(|error| error.to_string())?
92 {
93 return Ok(governance_failure(
94 request,
95 evaluated_at,
96 GenericGovernanceFindingCode::PriorCaseUnverifiable,
97 "prior governance case signature is invalid",
98 ));
99 }
100 if let Err(error) = prior_case.body.validate() {
101 return Ok(governance_failure(
102 request,
103 evaluated_at,
104 GenericGovernanceFindingCode::PriorCaseUnverifiable,
105 &error,
106 ));
107 }
108 }
109 if let Some(activation) = request.activation.as_ref() {
110 if activation.body.local_operator_id != request.charter.body.governing_operator_id {
111 return Ok(governance_failure(
112 request,
113 evaluated_at,
114 GenericGovernanceFindingCode::ActivationMismatch,
115 "governance cases require a trust activation issued by the governing operator",
116 ));
117 }
118 }
119
120 let charter = &request.charter.body;
121 let case = &request.case.body;
122 let listing = &request.listing.body;
123 let namespace = normalize_namespace(&listing.namespace);
124
125 if charter.governing_operator_id != case.governing_operator_id
126 || charter.charter_id != case.charter_id
127 || normalize_namespace(&charter.authority_scope.namespace) != namespace
128 || normalize_namespace(&case.namespace) != namespace
129 || case.listing_id != listing.listing_id
130 {
131 return Ok(governance_failure(
132 request,
133 evaluated_at,
134 GenericGovernanceFindingCode::CaseMismatch,
135 "governance charter or case does not match the current listing identity or namespace",
136 ));
137 }
138
139 if charter
140 .expires_at
141 .is_some_and(|expires_at| expires_at <= evaluated_at)
142 {
143 return Ok(governance_failure(
144 request,
145 evaluated_at,
146 GenericGovernanceFindingCode::CharterExpired,
147 "governance charter has expired",
148 ));
149 }
150 if case
151 .expires_at
152 .is_some_and(|expires_at| expires_at <= evaluated_at)
153 {
154 return Ok(governance_failure(
155 request,
156 evaluated_at,
157 GenericGovernanceFindingCode::CaseExpired,
158 "governance case has expired",
159 ));
160 }
161 if !charter.allowed_case_kinds.contains(&case.kind) {
162 return Ok(governance_failure(
163 request,
164 evaluated_at,
165 GenericGovernanceFindingCode::CharterKindUnsupported,
166 "governance charter does not authorize this case kind",
167 ));
168 }
169 if !charter
170 .authority_scope
171 .allowed_listing_operator_ids
172 .is_empty()
173 && !charter
174 .authority_scope
175 .allowed_listing_operator_ids
176 .contains(&request.current_publisher.operator_id)
177 {
178 return Ok(governance_failure(
179 request,
180 evaluated_at,
181 GenericGovernanceFindingCode::CharterScopeMismatch,
182 "current listing publisher falls outside the charter authority scope",
183 ));
184 }
185 if !charter.authority_scope.allowed_actor_kinds.is_empty()
186 && !charter
187 .authority_scope
188 .allowed_actor_kinds
189 .contains(&listing.subject.actor_kind)
190 {
191 return Ok(governance_failure(
192 request,
193 evaluated_at,
194 GenericGovernanceFindingCode::CharterScopeMismatch,
195 "listing actor kind falls outside the charter authority scope",
196 ));
197 }
198
199 if matches!(
200 case.kind,
201 GenericGovernanceCaseKind::Freeze | GenericGovernanceCaseKind::Sanction
202 ) {
203 let Some(activation) = request.activation.as_ref() else {
204 return Ok(governance_failure(
205 request,
206 evaluated_at,
207 GenericGovernanceFindingCode::MissingActivation,
208 "freeze or sanction cases require an explicit local trust activation",
209 ));
210 };
211 if case.activation_id.as_deref() != Some(activation.body.activation_id.as_str()) {
212 return Ok(governance_failure(
213 request,
214 evaluated_at,
215 GenericGovernanceFindingCode::ActivationMismatch,
216 "governance case activation does not match the provided trust activation",
217 ));
218 }
219 }
220
221 if let Some(supersedes_case_id) = case.supersedes_case_id.as_deref() {
222 let Some(prior_case) = request.prior_case.as_ref() else {
223 return Ok(governance_failure(
224 request,
225 evaluated_at,
226 GenericGovernanceFindingCode::SupersessionTargetMissing,
227 "superseding governance case requires prior_case",
228 ));
229 };
230 if prior_case.body.case_id != supersedes_case_id
231 || normalize_namespace(&prior_case.body.namespace) != namespace
232 || prior_case.body.listing_id != listing.listing_id
233 {
234 return Ok(governance_failure(
235 request,
236 evaluated_at,
237 GenericGovernanceFindingCode::SupersessionTargetInvalid,
238 "supersession target does not match the referenced prior governance case",
239 ));
240 }
241 }
242
243 if matches!(case.kind, GenericGovernanceCaseKind::Appeal) {
244 let Some(appeal_of_case_id) = case.appeal_of_case_id.as_deref() else {
245 return Ok(governance_failure(
246 request,
247 evaluated_at,
248 GenericGovernanceFindingCode::AppealTargetMissing,
249 "appeal case requires appeal_of_case_id",
250 ));
251 };
252 let Some(prior_case) = request.prior_case.as_ref() else {
253 return Ok(governance_failure(
254 request,
255 evaluated_at,
256 GenericGovernanceFindingCode::AppealTargetMissing,
257 "appeal case requires prior_case",
258 ));
259 };
260 if prior_case.body.case_id != appeal_of_case_id
261 || normalize_namespace(&prior_case.body.namespace) != namespace
262 || prior_case.body.listing_id != listing.listing_id
263 || matches!(prior_case.body.kind, GenericGovernanceCaseKind::Appeal)
264 {
265 return Ok(governance_failure(
266 request,
267 evaluated_at,
268 GenericGovernanceFindingCode::AppealTargetInvalid,
269 "appeal target does not match a valid prior governance case",
270 ));
271 }
272 }
273
274 let (effective_state, blocks_admission) = effective_state_for_case(case);
275 Ok(GenericGovernanceCaseEvaluation {
276 listing_id: listing.listing_id.clone(),
277 namespace,
278 charter_id: charter.charter_id.clone(),
279 case_id: case.case_id.clone(),
280 governing_operator_id: case.governing_operator_id.clone(),
281 kind: case.kind,
282 state: case.state,
283 effective_state,
284 evaluated_at,
285 blocks_admission,
286 findings: Vec::new(),
287 })
288}
289
290pub(crate) fn effective_state_for_case(
291 case: &GenericGovernanceCaseArtifact,
292) -> (GenericGovernanceEffectiveState, bool) {
293 match case.state {
294 GenericGovernanceCaseState::Resolved
295 | GenericGovernanceCaseState::Denied
296 | GenericGovernanceCaseState::Superseded => (GenericGovernanceEffectiveState::Clear, false),
297 GenericGovernanceCaseState::Open | GenericGovernanceCaseState::Escalated => match case.kind
298 {
299 GenericGovernanceCaseKind::Dispute => {
300 (GenericGovernanceEffectiveState::Disputed, false)
301 }
302 GenericGovernanceCaseKind::Appeal => (GenericGovernanceEffectiveState::Appealed, false),
303 GenericGovernanceCaseKind::Freeze => (GenericGovernanceEffectiveState::Frozen, false),
304 GenericGovernanceCaseKind::Sanction => {
305 (GenericGovernanceEffectiveState::Sanctioned, false)
306 }
307 },
308 GenericGovernanceCaseState::Enforced => match case.kind {
309 GenericGovernanceCaseKind::Dispute => {
310 (GenericGovernanceEffectiveState::Disputed, false)
311 }
312 GenericGovernanceCaseKind::Appeal => (GenericGovernanceEffectiveState::Appealed, false),
313 GenericGovernanceCaseKind::Freeze => (GenericGovernanceEffectiveState::Frozen, true),
314 GenericGovernanceCaseKind::Sanction => {
315 (GenericGovernanceEffectiveState::Sanctioned, true)
316 }
317 },
318 }
319}
320
321pub(crate) fn governance_failure(
322 request: &GenericGovernanceCaseEvaluationRequest,
323 evaluated_at: u64,
324 code: GenericGovernanceFindingCode,
325 message: &str,
326) -> GenericGovernanceCaseEvaluation {
327 GenericGovernanceCaseEvaluation {
328 listing_id: request.listing.body.listing_id.clone(),
329 namespace: request.listing.body.namespace.clone(),
330 charter_id: request.case.body.charter_id.clone(),
331 case_id: request.case.body.case_id.clone(),
332 governing_operator_id: request.case.body.governing_operator_id.clone(),
333 kind: request.case.body.kind,
334 state: request.case.body.state,
335 effective_state: GenericGovernanceEffectiveState::Clear,
336 evaluated_at,
337 blocks_admission: false,
338 findings: vec![GenericGovernanceFinding {
339 code,
340 message: message.to_string(),
341 }],
342 }
343}