Skip to main content

chio_governance/
lib.rs

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}