1pub use chio_core_types::{canonical_json_bytes, crypto, receipt};
2pub use chio_listing as listing;
3
4use serde::{Deserialize, Serialize};
5
6use crate::crypto::sha256_hex;
7use crate::listing::{
8 normalize_namespace, GenericListingActorKind, GenericRegistryPublisher, SignedGenericListing,
9 SignedGenericTrustActivation,
10};
11use crate::receipt::SignedExportEnvelope;
12
13pub const GENERIC_GOVERNANCE_CHARTER_ARTIFACT_SCHEMA: &str = "chio.registry.governance-charter.v1";
14pub const GENERIC_GOVERNANCE_CASE_ARTIFACT_SCHEMA: &str = "chio.registry.governance-case.v1";
15
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "snake_case")]
18pub enum GenericGovernanceCaseKind {
19 Dispute,
20 Freeze,
21 Sanction,
22 Appeal,
23}
24
25#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
26#[serde(rename_all = "snake_case")]
27pub enum GenericGovernanceCaseState {
28 Open,
29 Escalated,
30 Enforced,
31 Resolved,
32 Denied,
33 Superseded,
34}
35
36#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(rename_all = "snake_case")]
38pub enum GenericGovernanceEffectiveState {
39 Clear,
40 Disputed,
41 Frozen,
42 Sanctioned,
43 Appealed,
44}
45
46#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
47#[serde(rename_all = "snake_case")]
48pub enum GenericGovernanceEvidenceKind {
49 Listing,
50 TrustActivation,
51 Certification,
52 RegistrySearch,
53 OperatorReport,
54 External,
55}
56
57#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
58#[serde(rename_all = "snake_case")]
59pub enum GenericGovernanceFindingCode {
60 ListingUnverifiable,
61 ActivationUnverifiable,
62 CharterUnverifiable,
63 CaseUnverifiable,
64 PriorCaseUnverifiable,
65 CharterExpired,
66 CaseExpired,
67 CharterScopeMismatch,
68 CharterKindUnsupported,
69 CaseMismatch,
70 MissingActivation,
71 ActivationMismatch,
72 AppealTargetMissing,
73 AppealTargetInvalid,
74 SupersessionTargetMissing,
75 SupersessionTargetInvalid,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79#[serde(rename_all = "camelCase")]
80pub struct GenericGovernanceAuthorityScope {
81 pub namespace: String,
82 #[serde(default, skip_serializing_if = "Vec::is_empty")]
83 pub allowed_listing_operator_ids: Vec<String>,
84 #[serde(default, skip_serializing_if = "Vec::is_empty")]
85 pub allowed_actor_kinds: Vec<GenericListingActorKind>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub policy_reference: Option<String>,
88}
89
90impl GenericGovernanceAuthorityScope {
91 pub fn validate(&self) -> Result<(), String> {
92 validate_non_empty(&self.namespace, "authority_scope.namespace")?;
93 for (index, operator_id) in self.allowed_listing_operator_ids.iter().enumerate() {
94 validate_non_empty(
95 operator_id,
96 &format!("authority_scope.allowed_listing_operator_ids[{index}]"),
97 )?;
98 }
99 Ok(())
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
104#[serde(rename_all = "camelCase")]
105pub struct GenericGovernanceEvidenceReference {
106 pub kind: GenericGovernanceEvidenceKind,
107 pub reference_id: String,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub uri: Option<String>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub sha256: Option<String>,
112}
113
114impl GenericGovernanceEvidenceReference {
115 pub fn validate(&self, field: &str) -> Result<(), String> {
116 validate_non_empty(&self.reference_id, &format!("{field}.reference_id"))?;
117 if let Some(uri) = self.uri.as_deref() {
118 validate_non_empty(uri, &format!("{field}.uri"))?;
119 }
120 Ok(())
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
125#[serde(rename_all = "camelCase")]
126pub struct GenericGovernanceCharterArtifact {
127 pub schema: String,
128 pub charter_id: String,
129 pub governing_operator_id: String,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub governing_operator_name: Option<String>,
132 pub authority_scope: GenericGovernanceAuthorityScope,
133 pub allowed_case_kinds: Vec<GenericGovernanceCaseKind>,
134 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub escalation_operator_ids: Vec<String>,
136 pub issued_at: u64,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub expires_at: Option<u64>,
139 pub issued_by: String,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub note: Option<String>,
142}
143
144impl GenericGovernanceCharterArtifact {
145 pub fn validate(&self) -> Result<(), String> {
146 if self.schema != GENERIC_GOVERNANCE_CHARTER_ARTIFACT_SCHEMA {
147 return Err(format!(
148 "unsupported generic governance charter schema: {}",
149 self.schema
150 ));
151 }
152 validate_non_empty(&self.charter_id, "charter_id")?;
153 validate_non_empty(&self.governing_operator_id, "governing_operator_id")?;
154 validate_non_empty(&self.issued_by, "issued_by")?;
155 self.authority_scope.validate()?;
156 if normalize_namespace(&self.authority_scope.namespace).is_empty() {
157 return Err("authority_scope.namespace must not be empty".to_string());
158 }
159 if self.allowed_case_kinds.is_empty() {
160 return Err("allowed_case_kinds must not be empty".to_string());
161 }
162 for (index, operator_id) in self.escalation_operator_ids.iter().enumerate() {
163 validate_non_empty(operator_id, &format!("escalation_operator_ids[{index}]"))?;
164 }
165 if let Some(expires_at) = self.expires_at {
166 if expires_at <= self.issued_at {
167 return Err("expires_at must be greater than issued_at".to_string());
168 }
169 }
170 Ok(())
171 }
172}
173
174pub type SignedGenericGovernanceCharter = SignedExportEnvelope<GenericGovernanceCharterArtifact>;
175
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
177#[serde(rename_all = "camelCase")]
178pub struct GenericGovernanceCaseArtifact {
179 pub schema: String,
180 pub case_id: String,
181 pub charter_id: String,
182 pub governing_operator_id: String,
183 pub kind: GenericGovernanceCaseKind,
184 pub state: GenericGovernanceCaseState,
185 pub namespace: String,
186 pub listing_id: String,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub activation_id: Option<String>,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub subject_operator_id: Option<String>,
191 pub opened_at: u64,
192 pub updated_at: u64,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub expires_at: Option<u64>,
195 #[serde(default, skip_serializing_if = "Vec::is_empty")]
196 pub escalated_to_operator_ids: Vec<String>,
197 pub evidence_refs: Vec<GenericGovernanceEvidenceReference>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub appeal_of_case_id: Option<String>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub supersedes_case_id: Option<String>,
202 pub issued_by: String,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub note: Option<String>,
205}
206
207impl GenericGovernanceCaseArtifact {
208 pub fn validate(&self) -> Result<(), String> {
209 if self.schema != GENERIC_GOVERNANCE_CASE_ARTIFACT_SCHEMA {
210 return Err(format!(
211 "unsupported generic governance case schema: {}",
212 self.schema
213 ));
214 }
215 validate_non_empty(&self.case_id, "case_id")?;
216 validate_non_empty(&self.charter_id, "charter_id")?;
217 validate_non_empty(&self.governing_operator_id, "governing_operator_id")?;
218 validate_non_empty(&self.namespace, "namespace")?;
219 validate_non_empty(&self.listing_id, "listing_id")?;
220 validate_non_empty(&self.issued_by, "issued_by")?;
221 if self.updated_at < self.opened_at {
222 return Err("updated_at must be greater than or equal to opened_at".to_string());
223 }
224 if let Some(expires_at) = self.expires_at {
225 if expires_at <= self.opened_at {
226 return Err("expires_at must be greater than opened_at".to_string());
227 }
228 }
229 for (index, operator_id) in self.escalated_to_operator_ids.iter().enumerate() {
230 validate_non_empty(operator_id, &format!("escalated_to_operator_ids[{index}]"))?;
231 }
232 if self.evidence_refs.is_empty() {
233 return Err("evidence_refs must not be empty".to_string());
234 }
235 for (index, evidence_ref) in self.evidence_refs.iter().enumerate() {
236 evidence_ref.validate(&format!("evidence_refs[{index}]"))?;
237 }
238 if matches!(self.kind, GenericGovernanceCaseKind::Appeal) {
239 if self.appeal_of_case_id.as_deref().is_none() {
240 return Err("appeal case requires appeal_of_case_id".to_string());
241 }
242 } else if self.appeal_of_case_id.is_some() {
243 return Err("appeal_of_case_id is only valid for appeal cases".to_string());
244 }
245 if matches!(self.state, GenericGovernanceCaseState::Escalated)
246 && self.escalated_to_operator_ids.is_empty()
247 {
248 return Err("escalated case requires escalated_to_operator_ids".to_string());
249 }
250 Ok(())
251 }
252}
253
254pub type SignedGenericGovernanceCase = SignedExportEnvelope<GenericGovernanceCaseArtifact>;
255
256#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
257#[serde(rename_all = "camelCase")]
258pub struct GenericGovernanceCharterIssueRequest {
259 pub authority_scope: GenericGovernanceAuthorityScope,
260 pub allowed_case_kinds: Vec<GenericGovernanceCaseKind>,
261 #[serde(default, skip_serializing_if = "Vec::is_empty")]
262 pub escalation_operator_ids: Vec<String>,
263 pub issued_by: String,
264 #[serde(default, skip_serializing_if = "Option::is_none")]
265 pub issued_at: Option<u64>,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub expires_at: Option<u64>,
268 #[serde(default, skip_serializing_if = "Option::is_none")]
269 pub note: Option<String>,
270}
271
272impl GenericGovernanceCharterIssueRequest {
273 pub fn validate(&self) -> Result<(), String> {
274 self.authority_scope.validate()?;
275 validate_non_empty(&self.issued_by, "issued_by")?;
276 if self.allowed_case_kinds.is_empty() {
277 return Err("allowed_case_kinds must not be empty".to_string());
278 }
279 Ok(())
280 }
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
284#[serde(rename_all = "camelCase")]
285pub struct GenericGovernanceCaseIssueRequest {
286 pub charter: SignedGenericGovernanceCharter,
287 pub listing: SignedGenericListing,
288 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub activation: Option<SignedGenericTrustActivation>,
290 pub kind: GenericGovernanceCaseKind,
291 pub state: GenericGovernanceCaseState,
292 #[serde(default, skip_serializing_if = "Option::is_none")]
293 pub subject_operator_id: Option<String>,
294 #[serde(default, skip_serializing_if = "Vec::is_empty")]
295 pub escalated_to_operator_ids: Vec<String>,
296 pub evidence_refs: Vec<GenericGovernanceEvidenceReference>,
297 #[serde(default, skip_serializing_if = "Option::is_none")]
298 pub appeal_of_case_id: Option<String>,
299 #[serde(default, skip_serializing_if = "Option::is_none")]
300 pub supersedes_case_id: Option<String>,
301 pub issued_by: String,
302 #[serde(default, skip_serializing_if = "Option::is_none")]
303 pub opened_at: Option<u64>,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub updated_at: Option<u64>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub expires_at: Option<u64>,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub note: Option<String>,
310}
311
312impl GenericGovernanceCaseIssueRequest {
313 pub fn validate(&self) -> Result<(), String> {
314 self.listing.body.validate()?;
315 if !self
316 .listing
317 .verify_signature()
318 .map_err(|error| error.to_string())?
319 {
320 return Err("governance case listing signature is invalid".to_string());
321 }
322 if !self
323 .charter
324 .verify_signature()
325 .map_err(|error| error.to_string())?
326 {
327 return Err("governance charter signature is invalid".to_string());
328 }
329 self.charter.body.validate()?;
330 if let Some(activation) = self.activation.as_ref() {
331 if !activation
332 .verify_signature()
333 .map_err(|error| error.to_string())?
334 {
335 return Err("trust activation signature is invalid".to_string());
336 }
337 activation
338 .body
339 .validate()
340 .map_err(|error| error.to_string())?;
341 }
342 validate_non_empty(&self.issued_by, "issued_by")?;
343 if self.evidence_refs.is_empty() {
344 return Err("evidence_refs must not be empty".to_string());
345 }
346 for (index, evidence_ref) in self.evidence_refs.iter().enumerate() {
347 evidence_ref.validate(&format!("evidence_refs[{index}]"))?;
348 }
349 Ok(())
350 }
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
354#[serde(rename_all = "camelCase")]
355pub struct GenericGovernanceCaseEvaluationRequest {
356 pub listing: SignedGenericListing,
357 pub current_publisher: GenericRegistryPublisher,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub activation: Option<SignedGenericTrustActivation>,
360 pub charter: SignedGenericGovernanceCharter,
361 pub case: SignedGenericGovernanceCase,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub prior_case: Option<SignedGenericGovernanceCase>,
364 #[serde(default, skip_serializing_if = "Option::is_none")]
365 pub evaluated_at: Option<u64>,
366}
367
368impl GenericGovernanceCaseEvaluationRequest {
369 pub fn validate(&self) -> Result<(), String> {
370 self.listing.body.validate()?;
371 self.current_publisher.validate()?;
372 Ok(())
373 }
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
377#[serde(rename_all = "camelCase")]
378pub struct GenericGovernanceFinding {
379 pub code: GenericGovernanceFindingCode,
380 pub message: String,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
384#[serde(rename_all = "camelCase")]
385pub struct GenericGovernanceCaseEvaluation {
386 pub listing_id: String,
387 pub namespace: String,
388 pub charter_id: String,
389 pub case_id: String,
390 pub governing_operator_id: String,
391 pub kind: GenericGovernanceCaseKind,
392 pub state: GenericGovernanceCaseState,
393 pub effective_state: GenericGovernanceEffectiveState,
394 pub evaluated_at: u64,
395 pub blocks_admission: bool,
396 #[serde(default, skip_serializing_if = "Vec::is_empty")]
397 pub findings: Vec<GenericGovernanceFinding>,
398}
399
400pub fn build_generic_governance_charter_artifact(
401 local_operator_id: &str,
402 local_operator_name: Option<String>,
403 request: &GenericGovernanceCharterIssueRequest,
404 issued_at: u64,
405) -> Result<GenericGovernanceCharterArtifact, String> {
406 request.validate()?;
407 validate_non_empty(local_operator_id, "local_operator_id")?;
408 let issued_at = request.issued_at.unwrap_or(issued_at);
409 let charter_id = format!(
410 "charter-{}",
411 sha256_hex(
412 &canonical_json_bytes(&(
413 local_operator_id,
414 normalize_namespace(&request.authority_scope.namespace),
415 &request.allowed_case_kinds,
416 issued_at,
417 ))
418 .map_err(|error| error.to_string())?
419 )
420 );
421 let artifact = GenericGovernanceCharterArtifact {
422 schema: GENERIC_GOVERNANCE_CHARTER_ARTIFACT_SCHEMA.to_string(),
423 charter_id,
424 governing_operator_id: local_operator_id.to_string(),
425 governing_operator_name: local_operator_name,
426 authority_scope: request.authority_scope.clone(),
427 allowed_case_kinds: request.allowed_case_kinds.clone(),
428 escalation_operator_ids: request.escalation_operator_ids.clone(),
429 issued_at,
430 expires_at: request.expires_at,
431 issued_by: request.issued_by.clone(),
432 note: request.note.clone(),
433 };
434 artifact.validate()?;
435 Ok(artifact)
436}
437
438pub fn build_generic_governance_case_artifact(
439 local_operator_id: &str,
440 request: &GenericGovernanceCaseIssueRequest,
441 issued_at: u64,
442) -> Result<GenericGovernanceCaseArtifact, String> {
443 request.validate()?;
444 validate_non_empty(local_operator_id, "local_operator_id")?;
445 if request.charter.body.governing_operator_id != local_operator_id {
446 return Err("governance case must be issued by the charter governing operator".to_string());
447 }
448 if request
449 .activation
450 .as_ref()
451 .is_some_and(|activation| activation.body.local_operator_id != local_operator_id)
452 {
453 return Err(
454 "governance cases must use a trust activation issued by the governing operator"
455 .to_string(),
456 );
457 }
458 let opened_at = request.opened_at.unwrap_or(issued_at);
459 let updated_at = request.updated_at.unwrap_or(opened_at);
460 let case_id = format!(
461 "case-{}",
462 sha256_hex(
463 &canonical_json_bytes(&(
464 local_operator_id,
465 &request.charter.body.charter_id,
466 &request.listing.body.listing_id,
467 request.kind,
468 request.state,
469 opened_at,
470 &request.appeal_of_case_id,
471 &request.supersedes_case_id,
472 ))
473 .map_err(|error| error.to_string())?
474 )
475 );
476 let artifact = GenericGovernanceCaseArtifact {
477 schema: GENERIC_GOVERNANCE_CASE_ARTIFACT_SCHEMA.to_string(),
478 case_id,
479 charter_id: request.charter.body.charter_id.clone(),
480 governing_operator_id: local_operator_id.to_string(),
481 kind: request.kind,
482 state: request.state,
483 namespace: request.listing.body.namespace.clone(),
484 listing_id: request.listing.body.listing_id.clone(),
485 activation_id: request
486 .activation
487 .as_ref()
488 .map(|activation| activation.body.activation_id.clone()),
489 subject_operator_id: request.subject_operator_id.clone(),
490 opened_at,
491 updated_at,
492 expires_at: request.expires_at,
493 escalated_to_operator_ids: request.escalated_to_operator_ids.clone(),
494 evidence_refs: request.evidence_refs.clone(),
495 appeal_of_case_id: request.appeal_of_case_id.clone(),
496 supersedes_case_id: request.supersedes_case_id.clone(),
497 issued_by: request.issued_by.clone(),
498 note: request.note.clone(),
499 };
500 artifact.validate()?;
501 Ok(artifact)
502}
503
504pub fn evaluate_generic_governance_case(
505 request: &GenericGovernanceCaseEvaluationRequest,
506 now: u64,
507) -> Result<GenericGovernanceCaseEvaluation, String> {
508 request.validate()?;
509 let evaluated_at = request.evaluated_at.unwrap_or(now);
510
511 if !request
512 .listing
513 .verify_signature()
514 .map_err(|error| error.to_string())?
515 {
516 return Ok(governance_failure(
517 request,
518 evaluated_at,
519 GenericGovernanceFindingCode::ListingUnverifiable,
520 "listing signature is invalid",
521 ));
522 }
523 if !request
524 .charter
525 .verify_signature()
526 .map_err(|error| error.to_string())?
527 {
528 return Ok(governance_failure(
529 request,
530 evaluated_at,
531 GenericGovernanceFindingCode::CharterUnverifiable,
532 "governance charter signature is invalid",
533 ));
534 }
535 if let Err(error) = request.charter.body.validate() {
536 return Ok(governance_failure(
537 request,
538 evaluated_at,
539 GenericGovernanceFindingCode::CharterUnverifiable,
540 &error,
541 ));
542 }
543 if !request
544 .case
545 .verify_signature()
546 .map_err(|error| error.to_string())?
547 {
548 return Ok(governance_failure(
549 request,
550 evaluated_at,
551 GenericGovernanceFindingCode::CaseUnverifiable,
552 "governance case signature is invalid",
553 ));
554 }
555 if let Err(error) = request.case.body.validate() {
556 return Ok(governance_failure(
557 request,
558 evaluated_at,
559 GenericGovernanceFindingCode::CaseUnverifiable,
560 &error,
561 ));
562 }
563 if let Some(activation) = request.activation.as_ref() {
564 if !activation
565 .verify_signature()
566 .map_err(|error| error.to_string())?
567 {
568 return Ok(governance_failure(
569 request,
570 evaluated_at,
571 GenericGovernanceFindingCode::ActivationUnverifiable,
572 "trust activation signature is invalid",
573 ));
574 }
575 if let Err(error) = activation.body.validate() {
576 return Ok(governance_failure(
577 request,
578 evaluated_at,
579 GenericGovernanceFindingCode::ActivationUnverifiable,
580 &error,
581 ));
582 }
583 }
584 if let Some(prior_case) = request.prior_case.as_ref() {
585 if !prior_case
586 .verify_signature()
587 .map_err(|error| error.to_string())?
588 {
589 return Ok(governance_failure(
590 request,
591 evaluated_at,
592 GenericGovernanceFindingCode::PriorCaseUnverifiable,
593 "prior governance case signature is invalid",
594 ));
595 }
596 if let Err(error) = prior_case.body.validate() {
597 return Ok(governance_failure(
598 request,
599 evaluated_at,
600 GenericGovernanceFindingCode::PriorCaseUnverifiable,
601 &error,
602 ));
603 }
604 }
605 if let Some(activation) = request.activation.as_ref() {
606 if activation.body.local_operator_id != request.charter.body.governing_operator_id {
607 return Ok(governance_failure(
608 request,
609 evaluated_at,
610 GenericGovernanceFindingCode::ActivationMismatch,
611 "governance cases require a trust activation issued by the governing operator",
612 ));
613 }
614 }
615
616 let charter = &request.charter.body;
617 let case = &request.case.body;
618 let listing = &request.listing.body;
619 let namespace = normalize_namespace(&listing.namespace);
620
621 if charter.governing_operator_id != case.governing_operator_id
622 || charter.charter_id != case.charter_id
623 || normalize_namespace(&charter.authority_scope.namespace) != namespace
624 || normalize_namespace(&case.namespace) != namespace
625 || case.listing_id != listing.listing_id
626 {
627 return Ok(governance_failure(
628 request,
629 evaluated_at,
630 GenericGovernanceFindingCode::CaseMismatch,
631 "governance charter or case does not match the current listing identity or namespace",
632 ));
633 }
634
635 if charter
636 .expires_at
637 .is_some_and(|expires_at| expires_at <= evaluated_at)
638 {
639 return Ok(governance_failure(
640 request,
641 evaluated_at,
642 GenericGovernanceFindingCode::CharterExpired,
643 "governance charter has expired",
644 ));
645 }
646 if case
647 .expires_at
648 .is_some_and(|expires_at| expires_at <= evaluated_at)
649 {
650 return Ok(governance_failure(
651 request,
652 evaluated_at,
653 GenericGovernanceFindingCode::CaseExpired,
654 "governance case has expired",
655 ));
656 }
657 if !charter.allowed_case_kinds.contains(&case.kind) {
658 return Ok(governance_failure(
659 request,
660 evaluated_at,
661 GenericGovernanceFindingCode::CharterKindUnsupported,
662 "governance charter does not authorize this case kind",
663 ));
664 }
665 if !charter
666 .authority_scope
667 .allowed_listing_operator_ids
668 .is_empty()
669 && !charter
670 .authority_scope
671 .allowed_listing_operator_ids
672 .contains(&request.current_publisher.operator_id)
673 {
674 return Ok(governance_failure(
675 request,
676 evaluated_at,
677 GenericGovernanceFindingCode::CharterScopeMismatch,
678 "current listing publisher falls outside the charter authority scope",
679 ));
680 }
681 if !charter.authority_scope.allowed_actor_kinds.is_empty()
682 && !charter
683 .authority_scope
684 .allowed_actor_kinds
685 .contains(&listing.subject.actor_kind)
686 {
687 return Ok(governance_failure(
688 request,
689 evaluated_at,
690 GenericGovernanceFindingCode::CharterScopeMismatch,
691 "listing actor kind falls outside the charter authority scope",
692 ));
693 }
694
695 if matches!(
696 case.kind,
697 GenericGovernanceCaseKind::Freeze | GenericGovernanceCaseKind::Sanction
698 ) {
699 let Some(activation) = request.activation.as_ref() else {
700 return Ok(governance_failure(
701 request,
702 evaluated_at,
703 GenericGovernanceFindingCode::MissingActivation,
704 "freeze or sanction cases require an explicit local trust activation",
705 ));
706 };
707 if case.activation_id.as_deref() != Some(activation.body.activation_id.as_str()) {
708 return Ok(governance_failure(
709 request,
710 evaluated_at,
711 GenericGovernanceFindingCode::ActivationMismatch,
712 "governance case activation does not match the provided trust activation",
713 ));
714 }
715 }
716
717 if let Some(supersedes_case_id) = case.supersedes_case_id.as_deref() {
718 let Some(prior_case) = request.prior_case.as_ref() else {
719 return Ok(governance_failure(
720 request,
721 evaluated_at,
722 GenericGovernanceFindingCode::SupersessionTargetMissing,
723 "superseding governance case requires prior_case",
724 ));
725 };
726 if prior_case.body.case_id != supersedes_case_id
727 || normalize_namespace(&prior_case.body.namespace) != namespace
728 || prior_case.body.listing_id != listing.listing_id
729 {
730 return Ok(governance_failure(
731 request,
732 evaluated_at,
733 GenericGovernanceFindingCode::SupersessionTargetInvalid,
734 "supersession target does not match the referenced prior governance case",
735 ));
736 }
737 }
738
739 if matches!(case.kind, GenericGovernanceCaseKind::Appeal) {
740 let Some(appeal_of_case_id) = case.appeal_of_case_id.as_deref() else {
741 return Ok(governance_failure(
742 request,
743 evaluated_at,
744 GenericGovernanceFindingCode::AppealTargetMissing,
745 "appeal case requires appeal_of_case_id",
746 ));
747 };
748 let Some(prior_case) = request.prior_case.as_ref() else {
749 return Ok(governance_failure(
750 request,
751 evaluated_at,
752 GenericGovernanceFindingCode::AppealTargetMissing,
753 "appeal case requires prior_case",
754 ));
755 };
756 if prior_case.body.case_id != appeal_of_case_id
757 || normalize_namespace(&prior_case.body.namespace) != namespace
758 || prior_case.body.listing_id != listing.listing_id
759 || matches!(prior_case.body.kind, GenericGovernanceCaseKind::Appeal)
760 {
761 return Ok(governance_failure(
762 request,
763 evaluated_at,
764 GenericGovernanceFindingCode::AppealTargetInvalid,
765 "appeal target does not match a valid prior governance case",
766 ));
767 }
768 }
769
770 let (effective_state, blocks_admission) = effective_state_for_case(case);
771 Ok(GenericGovernanceCaseEvaluation {
772 listing_id: listing.listing_id.clone(),
773 namespace,
774 charter_id: charter.charter_id.clone(),
775 case_id: case.case_id.clone(),
776 governing_operator_id: case.governing_operator_id.clone(),
777 kind: case.kind,
778 state: case.state,
779 effective_state,
780 evaluated_at,
781 blocks_admission,
782 findings: Vec::new(),
783 })
784}
785
786fn effective_state_for_case(
787 case: &GenericGovernanceCaseArtifact,
788) -> (GenericGovernanceEffectiveState, bool) {
789 match case.state {
790 GenericGovernanceCaseState::Resolved
791 | GenericGovernanceCaseState::Denied
792 | GenericGovernanceCaseState::Superseded => (GenericGovernanceEffectiveState::Clear, false),
793 GenericGovernanceCaseState::Open | GenericGovernanceCaseState::Escalated => match case.kind
794 {
795 GenericGovernanceCaseKind::Dispute => {
796 (GenericGovernanceEffectiveState::Disputed, false)
797 }
798 GenericGovernanceCaseKind::Appeal => (GenericGovernanceEffectiveState::Appealed, false),
799 GenericGovernanceCaseKind::Freeze => (GenericGovernanceEffectiveState::Frozen, false),
800 GenericGovernanceCaseKind::Sanction => {
801 (GenericGovernanceEffectiveState::Sanctioned, false)
802 }
803 },
804 GenericGovernanceCaseState::Enforced => match case.kind {
805 GenericGovernanceCaseKind::Dispute => {
806 (GenericGovernanceEffectiveState::Disputed, false)
807 }
808 GenericGovernanceCaseKind::Appeal => (GenericGovernanceEffectiveState::Appealed, false),
809 GenericGovernanceCaseKind::Freeze => (GenericGovernanceEffectiveState::Frozen, true),
810 GenericGovernanceCaseKind::Sanction => {
811 (GenericGovernanceEffectiveState::Sanctioned, true)
812 }
813 },
814 }
815}
816
817fn governance_failure(
818 request: &GenericGovernanceCaseEvaluationRequest,
819 evaluated_at: u64,
820 code: GenericGovernanceFindingCode,
821 message: &str,
822) -> GenericGovernanceCaseEvaluation {
823 GenericGovernanceCaseEvaluation {
824 listing_id: request.listing.body.listing_id.clone(),
825 namespace: request.listing.body.namespace.clone(),
826 charter_id: request.case.body.charter_id.clone(),
827 case_id: request.case.body.case_id.clone(),
828 governing_operator_id: request.case.body.governing_operator_id.clone(),
829 kind: request.case.body.kind,
830 state: request.case.body.state,
831 effective_state: GenericGovernanceEffectiveState::Clear,
832 evaluated_at,
833 blocks_admission: false,
834 findings: vec![GenericGovernanceFinding {
835 code,
836 message: message.to_string(),
837 }],
838 }
839}
840
841fn validate_non_empty(value: &str, field: &str) -> Result<(), String> {
842 if value.trim().is_empty() {
843 Err(format!("{field} must not be empty"))
844 } else {
845 Ok(())
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852 use crate::crypto::Keypair;
853 use crate::listing::{
854 build_generic_trust_activation_artifact, GenericListingArtifact, GenericListingBoundary,
855 GenericListingCompatibilityReference, GenericListingFreshnessState,
856 GenericListingReplicaFreshness, GenericListingStatus, GenericListingSubject,
857 GenericNamespaceArtifact, GenericNamespaceLifecycleState, GenericNamespaceOwnership,
858 GenericRegistryPublisherRole, GenericTrustActivationDisposition,
859 GenericTrustActivationEligibility, GenericTrustActivationIssueRequest,
860 GenericTrustActivationReviewContext, GenericTrustAdmissionClass,
861 GENERIC_LISTING_ARTIFACT_SCHEMA, GENERIC_NAMESPACE_ARTIFACT_SCHEMA,
862 };
863
864 fn sample_namespace(owner_id: &str, signing_keypair: &Keypair) -> GenericNamespaceArtifact {
865 GenericNamespaceArtifact {
866 schema: GENERIC_NAMESPACE_ARTIFACT_SCHEMA.to_string(),
867 namespace_id: "namespace-registry-chio-example".to_string(),
868 lifecycle_state: GenericNamespaceLifecycleState::Active,
869 ownership: GenericNamespaceOwnership {
870 namespace: "https://registry.chio.example".to_string(),
871 owner_id: owner_id.to_string(),
872 owner_name: Some("Registry Operator".to_string()),
873 registry_url: "https://registry.chio.example".to_string(),
874 signer_public_key: signing_keypair.public_key(),
875 registered_at: 100,
876 transferred_from_owner_id: None,
877 },
878 boundary: GenericListingBoundary::default(),
879 }
880 }
881
882 fn sample_listing(owner_id: &str, signing_keypair: &Keypair) -> GenericListingArtifact {
883 GenericListingArtifact {
884 schema: GENERIC_LISTING_ARTIFACT_SCHEMA.to_string(),
885 listing_id: "listing-artifact-1".to_string(),
886 namespace: "https://registry.chio.example".to_string(),
887 namespace_ownership: sample_namespace(owner_id, signing_keypair).ownership,
888 published_at: 110,
889 expires_at: Some(1_000),
890 status: GenericListingStatus::Active,
891 subject: GenericListingSubject {
892 actor_kind: GenericListingActorKind::ToolServer,
893 actor_id: "tool-server-a".to_string(),
894 display_name: Some("Tool Server A".to_string()),
895 metadata_url: Some("https://tool.chio.example/metadata".to_string()),
896 resolution_url: Some("https://tool.chio.example/mcp".to_string()),
897 homepage_url: Some("https://tool.chio.example".to_string()),
898 },
899 compatibility: GenericListingCompatibilityReference {
900 source_schema: "chio.certify.check.v1".to_string(),
901 source_artifact_id: "artifact-1".to_string(),
902 source_artifact_sha256: "deadbeef".to_string(),
903 },
904 boundary: GenericListingBoundary::default(),
905 }
906 }
907
908 fn signed_sample_listing(owner_id: &str, signing_keypair: &Keypair) -> SignedGenericListing {
909 SignedGenericListing::sign(sample_listing(owner_id, signing_keypair), signing_keypair)
910 .expect("sign sample listing")
911 }
912
913 fn sample_publisher(
914 role: GenericRegistryPublisherRole,
915 operator_id: &str,
916 ) -> GenericRegistryPublisher {
917 GenericRegistryPublisher {
918 role,
919 operator_id: operator_id.to_string(),
920 operator_name: Some(format!("Operator {operator_id}")),
921 registry_url: format!("https://{operator_id}.chio.example"),
922 upstream_registry_urls: Vec::new(),
923 }
924 }
925
926 fn sample_activation(listing: &SignedGenericListing) -> SignedGenericTrustActivation {
927 let authority_keypair = Keypair::generate();
928 let activation = build_generic_trust_activation_artifact(
929 "origin-a",
930 Some("Origin A".to_string()),
931 &GenericTrustActivationIssueRequest {
932 listing: listing.clone(),
933 admission_class: GenericTrustAdmissionClass::Reviewable,
934 disposition: GenericTrustActivationDisposition::Approved,
935 eligibility: GenericTrustActivationEligibility {
936 allowed_actor_kinds: vec![GenericListingActorKind::ToolServer],
937 allowed_publisher_roles: vec![GenericRegistryPublisherRole::Origin],
938 allowed_statuses: vec![GenericListingStatus::Active],
939 require_fresh_listing: true,
940 require_bond_backing: false,
941 required_listing_operator_ids: vec!["origin-a".to_string()],
942 policy_reference: Some("policy/open-registry/default".to_string()),
943 },
944 review_context: GenericTrustActivationReviewContext {
945 publisher: sample_publisher(GenericRegistryPublisherRole::Origin, "origin-a"),
946 freshness: GenericListingReplicaFreshness {
947 state: GenericListingFreshnessState::Fresh,
948 age_secs: 5,
949 max_age_secs: 300,
950 valid_until: 400,
951 generated_at: 100,
952 },
953 },
954 requested_by: "ops@chio.example".to_string(),
955 reviewed_by: Some("reviewer@chio.example".to_string()),
956 requested_at: Some(120),
957 reviewed_at: Some(121),
958 expires_at: Some(500),
959 note: Some("approved".to_string()),
960 },
961 121,
962 )
963 .expect("build activation");
964 SignedGenericTrustActivation::sign(activation, &authority_keypair).expect("sign activation")
965 }
966
967 fn sample_charter_request() -> GenericGovernanceCharterIssueRequest {
968 GenericGovernanceCharterIssueRequest {
969 authority_scope: GenericGovernanceAuthorityScope {
970 namespace: "https://registry.chio.example".to_string(),
971 allowed_listing_operator_ids: vec!["origin-a".to_string()],
972 allowed_actor_kinds: vec![GenericListingActorKind::ToolServer],
973 policy_reference: Some("policy/governance/default".to_string()),
974 },
975 allowed_case_kinds: vec![
976 GenericGovernanceCaseKind::Dispute,
977 GenericGovernanceCaseKind::Freeze,
978 GenericGovernanceCaseKind::Sanction,
979 GenericGovernanceCaseKind::Appeal,
980 ],
981 escalation_operator_ids: vec!["network-audit.chio.example".to_string()],
982 issued_by: "governance@chio.example".to_string(),
983 issued_at: Some(130),
984 expires_at: Some(800),
985 note: Some("default governance charter".to_string()),
986 }
987 }
988
989 fn sample_charter(
990 local_operator_id: &str,
991 authority_keypair: &Keypair,
992 ) -> SignedGenericGovernanceCharter {
993 SignedGenericGovernanceCharter::sign(
994 build_generic_governance_charter_artifact(
995 local_operator_id,
996 Some(format!("Operator {local_operator_id}")),
997 &sample_charter_request(),
998 130,
999 )
1000 .expect("build charter"),
1001 authority_keypair,
1002 )
1003 .expect("sign charter")
1004 }
1005
1006 fn sample_case_issue_request(
1007 charter: SignedGenericGovernanceCharter,
1008 listing: SignedGenericListing,
1009 activation: Option<SignedGenericTrustActivation>,
1010 ) -> GenericGovernanceCaseIssueRequest {
1011 let evidence_kind = if activation.is_some() {
1012 GenericGovernanceEvidenceKind::TrustActivation
1013 } else {
1014 GenericGovernanceEvidenceKind::Listing
1015 };
1016 let reference_id = activation.as_ref().map_or_else(
1017 || listing.body.listing_id.clone(),
1018 |activation| activation.body.activation_id.clone(),
1019 );
1020 GenericGovernanceCaseIssueRequest {
1021 charter,
1022 listing,
1023 activation,
1024 kind: GenericGovernanceCaseKind::Freeze,
1025 state: GenericGovernanceCaseState::Enforced,
1026 subject_operator_id: Some("origin-a".to_string()),
1027 escalated_to_operator_ids: Vec::new(),
1028 evidence_refs: vec![GenericGovernanceEvidenceReference {
1029 kind: evidence_kind,
1030 reference_id,
1031 uri: None,
1032 sha256: None,
1033 }],
1034 appeal_of_case_id: None,
1035 supersedes_case_id: None,
1036 issued_by: "governance@chio.example".to_string(),
1037 opened_at: Some(140),
1038 updated_at: Some(140),
1039 expires_at: Some(500),
1040 note: Some("freeze".to_string()),
1041 }
1042 }
1043
1044 #[test]
1045 fn generic_governance_freeze_requires_activation() {
1046 let listing_keypair = Keypair::generate();
1047 let authority_keypair = Keypair::generate();
1048 let listing = signed_sample_listing("origin-a", &listing_keypair);
1049 let charter = SignedGenericGovernanceCharter::sign(
1050 build_generic_governance_charter_artifact(
1051 "origin-a",
1052 Some("Origin A".to_string()),
1053 &sample_charter_request(),
1054 130,
1055 )
1056 .expect("build charter"),
1057 &authority_keypair,
1058 )
1059 .expect("sign charter");
1060 let case = SignedGenericGovernanceCase::sign(
1061 build_generic_governance_case_artifact(
1062 "origin-a",
1063 &GenericGovernanceCaseIssueRequest {
1064 charter: charter.clone(),
1065 listing: listing.clone(),
1066 activation: None,
1067 kind: GenericGovernanceCaseKind::Freeze,
1068 state: GenericGovernanceCaseState::Enforced,
1069 subject_operator_id: Some("origin-a".to_string()),
1070 escalated_to_operator_ids: Vec::new(),
1071 evidence_refs: vec![GenericGovernanceEvidenceReference {
1072 kind: GenericGovernanceEvidenceKind::Listing,
1073 reference_id: listing.body.listing_id.clone(),
1074 uri: None,
1075 sha256: None,
1076 }],
1077 appeal_of_case_id: None,
1078 supersedes_case_id: None,
1079 issued_by: "governance@chio.example".to_string(),
1080 opened_at: Some(140),
1081 updated_at: Some(140),
1082 expires_at: Some(500),
1083 note: Some("freeze".to_string()),
1084 },
1085 140,
1086 )
1087 .expect("build case"),
1088 &authority_keypair,
1089 )
1090 .expect("sign case");
1091
1092 let evaluation = evaluate_generic_governance_case(
1093 &GenericGovernanceCaseEvaluationRequest {
1094 listing,
1095 current_publisher: sample_publisher(
1096 GenericRegistryPublisherRole::Origin,
1097 "origin-a",
1098 ),
1099 activation: None,
1100 charter,
1101 case,
1102 prior_case: None,
1103 evaluated_at: Some(150),
1104 },
1105 150,
1106 )
1107 .expect("evaluate governance");
1108 assert!(!evaluation.blocks_admission);
1109 assert_eq!(
1110 evaluation.findings[0].code,
1111 GenericGovernanceFindingCode::MissingActivation
1112 );
1113 }
1114
1115 #[test]
1116 fn generic_governance_enforced_freeze_blocks_admission() {
1117 let listing_keypair = Keypair::generate();
1118 let authority_keypair = Keypair::generate();
1119 let listing = signed_sample_listing("origin-a", &listing_keypair);
1120 let activation = sample_activation(&listing);
1121 let charter = SignedGenericGovernanceCharter::sign(
1122 build_generic_governance_charter_artifact(
1123 "origin-a",
1124 Some("Origin A".to_string()),
1125 &sample_charter_request(),
1126 130,
1127 )
1128 .expect("build charter"),
1129 &authority_keypair,
1130 )
1131 .expect("sign charter");
1132 let case = SignedGenericGovernanceCase::sign(
1133 build_generic_governance_case_artifact(
1134 "origin-a",
1135 &GenericGovernanceCaseIssueRequest {
1136 charter: charter.clone(),
1137 listing: listing.clone(),
1138 activation: Some(activation.clone()),
1139 kind: GenericGovernanceCaseKind::Freeze,
1140 state: GenericGovernanceCaseState::Enforced,
1141 subject_operator_id: Some("origin-a".to_string()),
1142 escalated_to_operator_ids: Vec::new(),
1143 evidence_refs: vec![GenericGovernanceEvidenceReference {
1144 kind: GenericGovernanceEvidenceKind::TrustActivation,
1145 reference_id: activation.body.activation_id.clone(),
1146 uri: None,
1147 sha256: None,
1148 }],
1149 appeal_of_case_id: None,
1150 supersedes_case_id: None,
1151 issued_by: "governance@chio.example".to_string(),
1152 opened_at: Some(140),
1153 updated_at: Some(140),
1154 expires_at: Some(500),
1155 note: Some("freeze".to_string()),
1156 },
1157 140,
1158 )
1159 .expect("build case"),
1160 &authority_keypair,
1161 )
1162 .expect("sign case");
1163
1164 let evaluation = evaluate_generic_governance_case(
1165 &GenericGovernanceCaseEvaluationRequest {
1166 listing,
1167 current_publisher: sample_publisher(
1168 GenericRegistryPublisherRole::Origin,
1169 "origin-a",
1170 ),
1171 activation: Some(activation),
1172 charter,
1173 case,
1174 prior_case: None,
1175 evaluated_at: Some(150),
1176 },
1177 150,
1178 )
1179 .expect("evaluate governance");
1180 assert!(evaluation.findings.is_empty());
1181 assert!(evaluation.blocks_admission);
1182 assert_eq!(
1183 evaluation.effective_state,
1184 GenericGovernanceEffectiveState::Frozen
1185 );
1186 }
1187
1188 #[test]
1189 fn generic_governance_case_issue_rejects_non_local_activation_authority() {
1190 let listing_keypair = Keypair::generate();
1191 let authority_keypair = Keypair::generate();
1192 let listing = signed_sample_listing("origin-a", &listing_keypair);
1193 let activation = sample_activation(&listing);
1194 let mut forged_activation_body = activation.body.clone();
1195 forged_activation_body.local_operator_id = "remote-b".to_string();
1196 forged_activation_body.local_operator_name = Some("Remote B".to_string());
1197 let forged_activation =
1198 SignedGenericTrustActivation::sign(forged_activation_body, &Keypair::generate())
1199 .expect("sign forged activation");
1200 let charter = SignedGenericGovernanceCharter::sign(
1201 build_generic_governance_charter_artifact(
1202 "origin-a",
1203 Some("Origin A".to_string()),
1204 &sample_charter_request(),
1205 130,
1206 )
1207 .expect("build charter"),
1208 &authority_keypair,
1209 )
1210 .expect("sign charter");
1211
1212 let error = build_generic_governance_case_artifact(
1213 "origin-a",
1214 &GenericGovernanceCaseIssueRequest {
1215 charter,
1216 listing,
1217 activation: Some(forged_activation),
1218 kind: GenericGovernanceCaseKind::Freeze,
1219 state: GenericGovernanceCaseState::Enforced,
1220 subject_operator_id: Some("origin-a".to_string()),
1221 escalated_to_operator_ids: Vec::new(),
1222 evidence_refs: vec![GenericGovernanceEvidenceReference {
1223 kind: GenericGovernanceEvidenceKind::TrustActivation,
1224 reference_id: activation.body.activation_id,
1225 uri: None,
1226 sha256: None,
1227 }],
1228 appeal_of_case_id: None,
1229 supersedes_case_id: None,
1230 issued_by: "governance@chio.example".to_string(),
1231 opened_at: Some(140),
1232 updated_at: Some(140),
1233 expires_at: Some(500),
1234 note: Some("freeze".to_string()),
1235 },
1236 140,
1237 )
1238 .expect_err("non-local activation authority rejected");
1239 assert!(error.contains("issued by the governing operator"));
1240 }
1241
1242 #[test]
1243 fn generic_governance_evaluation_rejects_non_local_activation_authority() {
1244 let listing_keypair = Keypair::generate();
1245 let authority_keypair = Keypair::generate();
1246 let listing = signed_sample_listing("origin-a", &listing_keypair);
1247 let activation = sample_activation(&listing);
1248 let charter = SignedGenericGovernanceCharter::sign(
1249 build_generic_governance_charter_artifact(
1250 "origin-a",
1251 Some("Origin A".to_string()),
1252 &sample_charter_request(),
1253 130,
1254 )
1255 .expect("build charter"),
1256 &authority_keypair,
1257 )
1258 .expect("sign charter");
1259 let case = SignedGenericGovernanceCase::sign(
1260 build_generic_governance_case_artifact(
1261 "origin-a",
1262 &GenericGovernanceCaseIssueRequest {
1263 charter: charter.clone(),
1264 listing: listing.clone(),
1265 activation: Some(activation.clone()),
1266 kind: GenericGovernanceCaseKind::Freeze,
1267 state: GenericGovernanceCaseState::Enforced,
1268 subject_operator_id: Some("origin-a".to_string()),
1269 escalated_to_operator_ids: Vec::new(),
1270 evidence_refs: vec![GenericGovernanceEvidenceReference {
1271 kind: GenericGovernanceEvidenceKind::TrustActivation,
1272 reference_id: activation.body.activation_id.clone(),
1273 uri: None,
1274 sha256: None,
1275 }],
1276 appeal_of_case_id: None,
1277 supersedes_case_id: None,
1278 issued_by: "governance@chio.example".to_string(),
1279 opened_at: Some(140),
1280 updated_at: Some(140),
1281 expires_at: Some(500),
1282 note: Some("freeze".to_string()),
1283 },
1284 140,
1285 )
1286 .expect("build case"),
1287 &authority_keypair,
1288 )
1289 .expect("sign case");
1290 let mut forged_activation_body = activation.body.clone();
1291 forged_activation_body.local_operator_id = "remote-b".to_string();
1292 forged_activation_body.local_operator_name = Some("Remote B".to_string());
1293 let forged_activation =
1294 SignedGenericTrustActivation::sign(forged_activation_body, &Keypair::generate())
1295 .expect("sign forged activation");
1296
1297 let evaluation = evaluate_generic_governance_case(
1298 &GenericGovernanceCaseEvaluationRequest {
1299 listing,
1300 current_publisher: sample_publisher(
1301 GenericRegistryPublisherRole::Origin,
1302 "origin-a",
1303 ),
1304 activation: Some(forged_activation),
1305 charter,
1306 case,
1307 prior_case: None,
1308 evaluated_at: Some(150),
1309 },
1310 150,
1311 )
1312 .expect("evaluate governance");
1313 assert!(!evaluation.blocks_admission);
1314 assert_eq!(
1315 evaluation.findings[0].code,
1316 GenericGovernanceFindingCode::ActivationMismatch
1317 );
1318 }
1319
1320 #[test]
1321 fn generic_governance_charter_scope_mismatch_fails_closed() {
1322 let listing_keypair = Keypair::generate();
1323 let authority_keypair = Keypair::generate();
1324 let listing = signed_sample_listing("origin-a", &listing_keypair);
1325 let activation = sample_activation(&listing);
1326 let charter = SignedGenericGovernanceCharter::sign(
1327 build_generic_governance_charter_artifact(
1328 "origin-a",
1329 Some("Origin A".to_string()),
1330 &sample_charter_request(),
1331 130,
1332 )
1333 .expect("build charter"),
1334 &authority_keypair,
1335 )
1336 .expect("sign charter");
1337 let case = SignedGenericGovernanceCase::sign(
1338 build_generic_governance_case_artifact(
1339 "origin-a",
1340 &GenericGovernanceCaseIssueRequest {
1341 charter: charter.clone(),
1342 listing: listing.clone(),
1343 activation: Some(activation.clone()),
1344 kind: GenericGovernanceCaseKind::Sanction,
1345 state: GenericGovernanceCaseState::Enforced,
1346 subject_operator_id: Some("origin-a".to_string()),
1347 escalated_to_operator_ids: Vec::new(),
1348 evidence_refs: vec![GenericGovernanceEvidenceReference {
1349 kind: GenericGovernanceEvidenceKind::External,
1350 reference_id: "incident-1".to_string(),
1351 uri: None,
1352 sha256: None,
1353 }],
1354 appeal_of_case_id: None,
1355 supersedes_case_id: None,
1356 issued_by: "governance@chio.example".to_string(),
1357 opened_at: Some(140),
1358 updated_at: Some(140),
1359 expires_at: Some(500),
1360 note: Some("sanction".to_string()),
1361 },
1362 140,
1363 )
1364 .expect("build case"),
1365 &authority_keypair,
1366 )
1367 .expect("sign case");
1368
1369 let evaluation = evaluate_generic_governance_case(
1370 &GenericGovernanceCaseEvaluationRequest {
1371 listing,
1372 current_publisher: sample_publisher(
1373 GenericRegistryPublisherRole::Origin,
1374 "other-origin",
1375 ),
1376 activation: Some(activation),
1377 charter,
1378 case,
1379 prior_case: None,
1380 evaluated_at: Some(150),
1381 },
1382 150,
1383 )
1384 .expect("evaluate governance");
1385 assert_eq!(
1386 evaluation.findings[0].code,
1387 GenericGovernanceFindingCode::CharterScopeMismatch
1388 );
1389 }
1390
1391 #[test]
1392 fn generic_governance_appeal_requires_prior_case() {
1393 let listing_keypair = Keypair::generate();
1394 let authority_keypair = Keypair::generate();
1395 let listing = signed_sample_listing("origin-a", &listing_keypair);
1396 let activation = sample_activation(&listing);
1397 let charter = SignedGenericGovernanceCharter::sign(
1398 build_generic_governance_charter_artifact(
1399 "origin-a",
1400 Some("Origin A".to_string()),
1401 &sample_charter_request(),
1402 130,
1403 )
1404 .expect("build charter"),
1405 &authority_keypair,
1406 )
1407 .expect("sign charter");
1408 let case = SignedGenericGovernanceCase::sign(
1409 build_generic_governance_case_artifact(
1410 "origin-a",
1411 &GenericGovernanceCaseIssueRequest {
1412 charter: charter.clone(),
1413 listing: listing.clone(),
1414 activation: Some(activation),
1415 kind: GenericGovernanceCaseKind::Appeal,
1416 state: GenericGovernanceCaseState::Open,
1417 subject_operator_id: Some("origin-a".to_string()),
1418 escalated_to_operator_ids: Vec::new(),
1419 evidence_refs: vec![GenericGovernanceEvidenceReference {
1420 kind: GenericGovernanceEvidenceKind::External,
1421 reference_id: "appeal-1".to_string(),
1422 uri: None,
1423 sha256: None,
1424 }],
1425 appeal_of_case_id: Some("case-missing".to_string()),
1426 supersedes_case_id: None,
1427 issued_by: "governance@chio.example".to_string(),
1428 opened_at: Some(140),
1429 updated_at: Some(140),
1430 expires_at: Some(500),
1431 note: Some("appeal".to_string()),
1432 },
1433 140,
1434 )
1435 .expect("build case"),
1436 &authority_keypair,
1437 )
1438 .expect("sign case");
1439
1440 let evaluation = evaluate_generic_governance_case(
1441 &GenericGovernanceCaseEvaluationRequest {
1442 listing,
1443 current_publisher: sample_publisher(
1444 GenericRegistryPublisherRole::Origin,
1445 "origin-a",
1446 ),
1447 activation: None,
1448 charter,
1449 case,
1450 prior_case: None,
1451 evaluated_at: Some(150),
1452 },
1453 150,
1454 )
1455 .expect("evaluate governance");
1456 assert_eq!(
1457 evaluation.findings[0].code,
1458 GenericGovernanceFindingCode::AppealTargetMissing
1459 );
1460 }
1461
1462 #[test]
1463 fn generic_governance_authority_scope_rejects_blank_operator_ids() {
1464 let error = GenericGovernanceAuthorityScope {
1465 namespace: "https://registry.chio.example".to_string(),
1466 allowed_listing_operator_ids: vec![" ".to_string()],
1467 allowed_actor_kinds: Vec::new(),
1468 policy_reference: None,
1469 }
1470 .validate()
1471 .expect_err("blank operator ids rejected");
1472
1473 assert!(error.contains("authority_scope.allowed_listing_operator_ids[0]"));
1474 }
1475
1476 #[test]
1477 fn generic_governance_charter_validate_rejects_expired_artifact() {
1478 let error = GenericGovernanceCharterArtifact {
1479 schema: GENERIC_GOVERNANCE_CHARTER_ARTIFACT_SCHEMA.to_string(),
1480 charter_id: "charter-1".to_string(),
1481 governing_operator_id: "origin-a".to_string(),
1482 governing_operator_name: Some("Origin A".to_string()),
1483 authority_scope: GenericGovernanceAuthorityScope {
1484 namespace: "https://registry.chio.example".to_string(),
1485 allowed_listing_operator_ids: vec!["origin-a".to_string()],
1486 allowed_actor_kinds: vec![GenericListingActorKind::ToolServer],
1487 policy_reference: None,
1488 },
1489 allowed_case_kinds: vec![GenericGovernanceCaseKind::Freeze],
1490 escalation_operator_ids: vec!["network-audit.chio.example".to_string()],
1491 issued_at: 200,
1492 expires_at: Some(199),
1493 issued_by: "governance@chio.example".to_string(),
1494 note: None,
1495 }
1496 .validate()
1497 .expect_err("expired charters rejected");
1498
1499 assert!(error.contains("expires_at must be greater than issued_at"));
1500 }
1501
1502 #[test]
1503 fn generic_governance_case_validate_requires_escalation_targets() {
1504 let error = GenericGovernanceCaseArtifact {
1505 schema: GENERIC_GOVERNANCE_CASE_ARTIFACT_SCHEMA.to_string(),
1506 case_id: "case-1".to_string(),
1507 charter_id: "charter-1".to_string(),
1508 governing_operator_id: "origin-a".to_string(),
1509 kind: GenericGovernanceCaseKind::Freeze,
1510 state: GenericGovernanceCaseState::Escalated,
1511 namespace: "https://registry.chio.example".to_string(),
1512 listing_id: "listing-1".to_string(),
1513 activation_id: Some("activation-1".to_string()),
1514 subject_operator_id: Some("origin-a".to_string()),
1515 opened_at: 100,
1516 updated_at: 100,
1517 expires_at: Some(200),
1518 escalated_to_operator_ids: Vec::new(),
1519 evidence_refs: vec![GenericGovernanceEvidenceReference {
1520 kind: GenericGovernanceEvidenceKind::External,
1521 reference_id: "incident-1".to_string(),
1522 uri: None,
1523 sha256: None,
1524 }],
1525 appeal_of_case_id: None,
1526 supersedes_case_id: None,
1527 issued_by: "governance@chio.example".to_string(),
1528 note: None,
1529 }
1530 .validate()
1531 .expect_err("escalated cases require operator targets");
1532
1533 assert!(error.contains("escalated case requires escalated_to_operator_ids"));
1534 }
1535
1536 #[test]
1537 fn generic_governance_case_validate_rejects_appeal_id_on_non_appeal_case() {
1538 let error = GenericGovernanceCaseArtifact {
1539 schema: GENERIC_GOVERNANCE_CASE_ARTIFACT_SCHEMA.to_string(),
1540 case_id: "case-1".to_string(),
1541 charter_id: "charter-1".to_string(),
1542 governing_operator_id: "origin-a".to_string(),
1543 kind: GenericGovernanceCaseKind::Sanction,
1544 state: GenericGovernanceCaseState::Open,
1545 namespace: "https://registry.chio.example".to_string(),
1546 listing_id: "listing-1".to_string(),
1547 activation_id: None,
1548 subject_operator_id: Some("origin-a".to_string()),
1549 opened_at: 100,
1550 updated_at: 100,
1551 expires_at: Some(200),
1552 escalated_to_operator_ids: Vec::new(),
1553 evidence_refs: vec![GenericGovernanceEvidenceReference {
1554 kind: GenericGovernanceEvidenceKind::External,
1555 reference_id: "incident-1".to_string(),
1556 uri: None,
1557 sha256: None,
1558 }],
1559 appeal_of_case_id: Some("case-0".to_string()),
1560 supersedes_case_id: None,
1561 issued_by: "governance@chio.example".to_string(),
1562 note: None,
1563 }
1564 .validate()
1565 .expect_err("appeal target only valid for appeal cases");
1566
1567 assert!(error.contains("only valid for appeal cases"));
1568 }
1569
1570 #[test]
1571 fn generic_governance_case_issue_request_rejects_invalid_listing_signature() {
1572 let listing_keypair = Keypair::generate();
1573 let authority_keypair = Keypair::generate();
1574 let listing = signed_sample_listing("origin-a", &listing_keypair);
1575 let charter = sample_charter("origin-a", &authority_keypair);
1576 let mut tampered_listing = listing.clone();
1577 tampered_listing.body.subject.actor_id = "tool-server-b".to_string();
1578
1579 let error = sample_case_issue_request(charter, tampered_listing, None)
1580 .validate()
1581 .expect_err("tampered listing signature rejected");
1582
1583 assert!(error.contains("listing signature is invalid"));
1584 }
1585
1586 #[test]
1587 fn generic_governance_case_issue_request_rejects_invalid_activation_signature() {
1588 let listing_keypair = Keypair::generate();
1589 let authority_keypair = Keypair::generate();
1590 let listing = signed_sample_listing("origin-a", &listing_keypair);
1591 let charter = sample_charter("origin-a", &authority_keypair);
1592 let activation = sample_activation(&listing);
1593 let mut tampered_activation = activation.clone();
1594 tampered_activation.body.local_operator_id = "remote-b".to_string();
1595
1596 let error = sample_case_issue_request(charter, listing, Some(tampered_activation))
1597 .validate()
1598 .expect_err("tampered activation signature rejected");
1599
1600 assert!(error.contains("trust activation signature is invalid"));
1601 }
1602
1603 #[test]
1604 fn build_generic_governance_charter_artifact_uses_request_issued_at() {
1605 let mut request = sample_charter_request();
1606 request.issued_at = Some(777);
1607
1608 let charter = build_generic_governance_charter_artifact(
1609 "origin-a",
1610 Some("Origin A".to_string()),
1611 &request,
1612 130,
1613 )
1614 .expect("build charter");
1615
1616 assert_eq!(charter.issued_at, 777);
1617 assert_eq!(charter.governing_operator_id, "origin-a");
1618 assert!(charter.charter_id.starts_with("charter-"));
1619 }
1620
1621 #[test]
1622 fn generic_governance_evaluation_rejects_invalid_case_signature() {
1623 let listing_keypair = Keypair::generate();
1624 let authority_keypair = Keypair::generate();
1625 let listing = signed_sample_listing("origin-a", &listing_keypair);
1626 let activation = sample_activation(&listing);
1627 let charter = sample_charter("origin-a", &authority_keypair);
1628 let case = SignedGenericGovernanceCase::sign(
1629 build_generic_governance_case_artifact(
1630 "origin-a",
1631 &sample_case_issue_request(
1632 charter.clone(),
1633 listing.clone(),
1634 Some(activation.clone()),
1635 ),
1636 140,
1637 )
1638 .expect("build case"),
1639 &authority_keypair,
1640 )
1641 .expect("sign case");
1642 let mut tampered_case = case.clone();
1643 tampered_case.body.note = Some("tampered".to_string());
1644
1645 let evaluation = evaluate_generic_governance_case(
1646 &GenericGovernanceCaseEvaluationRequest {
1647 listing,
1648 current_publisher: sample_publisher(
1649 GenericRegistryPublisherRole::Origin,
1650 "origin-a",
1651 ),
1652 activation: Some(activation),
1653 charter,
1654 case: tampered_case,
1655 prior_case: None,
1656 evaluated_at: Some(150),
1657 },
1658 150,
1659 )
1660 .expect("evaluate governance");
1661
1662 assert_eq!(
1663 evaluation.findings[0].code,
1664 GenericGovernanceFindingCode::CaseUnverifiable
1665 );
1666 }
1667
1668 #[test]
1669 fn generic_governance_supersession_target_mismatch_fails_closed() {
1670 let listing_keypair = Keypair::generate();
1671 let authority_keypair = Keypair::generate();
1672 let listing = signed_sample_listing("origin-a", &listing_keypair);
1673 let activation = sample_activation(&listing);
1674 let charter = sample_charter("origin-a", &authority_keypair);
1675 let mut case_request =
1676 sample_case_issue_request(charter.clone(), listing.clone(), Some(activation.clone()));
1677 case_request.kind = GenericGovernanceCaseKind::Sanction;
1678 case_request.supersedes_case_id = Some("prior-case-1".to_string());
1679 case_request.evidence_refs[0].kind = GenericGovernanceEvidenceKind::External;
1680 case_request.evidence_refs[0].reference_id = "incident-1".to_string();
1681 let case = SignedGenericGovernanceCase::sign(
1682 build_generic_governance_case_artifact("origin-a", &case_request, 140)
1683 .expect("build case"),
1684 &authority_keypair,
1685 )
1686 .expect("sign case");
1687 let prior_case = SignedGenericGovernanceCase::sign(
1688 GenericGovernanceCaseArtifact {
1689 schema: GENERIC_GOVERNANCE_CASE_ARTIFACT_SCHEMA.to_string(),
1690 case_id: "prior-case-1".to_string(),
1691 charter_id: charter.body.charter_id.clone(),
1692 governing_operator_id: "origin-a".to_string(),
1693 kind: GenericGovernanceCaseKind::Freeze,
1694 state: GenericGovernanceCaseState::Resolved,
1695 namespace: "https://registry.chio.example".to_string(),
1696 listing_id: "different-listing".to_string(),
1697 activation_id: Some(activation.body.activation_id.clone()),
1698 subject_operator_id: Some("origin-a".to_string()),
1699 opened_at: 120,
1700 updated_at: 121,
1701 expires_at: Some(500),
1702 escalated_to_operator_ids: Vec::new(),
1703 evidence_refs: vec![GenericGovernanceEvidenceReference {
1704 kind: GenericGovernanceEvidenceKind::External,
1705 reference_id: "prior-incident".to_string(),
1706 uri: None,
1707 sha256: None,
1708 }],
1709 appeal_of_case_id: None,
1710 supersedes_case_id: None,
1711 issued_by: "governance@chio.example".to_string(),
1712 note: None,
1713 },
1714 &authority_keypair,
1715 )
1716 .expect("sign prior case");
1717
1718 let evaluation = evaluate_generic_governance_case(
1719 &GenericGovernanceCaseEvaluationRequest {
1720 listing,
1721 current_publisher: sample_publisher(
1722 GenericRegistryPublisherRole::Origin,
1723 "origin-a",
1724 ),
1725 activation: Some(activation),
1726 charter,
1727 case,
1728 prior_case: Some(prior_case),
1729 evaluated_at: Some(150),
1730 },
1731 150,
1732 )
1733 .expect("evaluate governance");
1734
1735 assert_eq!(
1736 evaluation.findings[0].code,
1737 GenericGovernanceFindingCode::SupersessionTargetInvalid
1738 );
1739 }
1740}