Skip to main content

chio_credit/
lib.rs

1//! Credit, capital, and bonded-execution contracts for the Chio protocol.
2//!
3//! This crate models credit limits, IOUs, and bonded execution for metered
4//! tool access. It defines the credit-evaluator hook and IOU envelope types, a
5//! local credit account that mints signed IOUs, an exposure ledger, and an IOU
6//! envelope store binding. It composes the appraisal and underwriting surfaces
7//! so credit decisions reference prior signed Chio truth rather than restating
8//! it.
9//!
10//! # Modules
11//!
12//! - [`hook`] -- credit-evaluator hook and the IOU envelope types.
13//! - [`local_account`] -- in-memory account that signs IOU envelopes.
14//! - [`risk_reports`] -- loss-lifecycle, backtest, and provider-risk reports.
15//! - [`store_binding`] -- durable-store trait for persisting IOU envelopes.
16
17#![forbid(unsafe_code)]
18
19pub use chio_appraisal as appraisal;
20pub use chio_core_types::{capability, crypto, receipt};
21pub use chio_underwriting as underwriting;
22
23pub mod clearing;
24pub mod factor;
25pub mod financial_credentials;
26pub mod hook;
27pub mod iou_v2;
28pub mod local_account;
29pub mod obligation;
30pub mod risk_reports;
31pub mod store_binding;
32
33pub use hook::{
34    CreditEvaluatorError, CreditEvaluatorHook, IouEnvelope, IouEnvelopeBody, IOU_ENVELOPE_SCHEMA,
35};
36pub use iou_v2::{
37    verify_iou_envelope_v2, IouEnvelopeBodyV2, IouEnvelopeCryptoFloorV2, IouEnvelopeIssuerTrustV2,
38    IouEnvelopeMintContextV2, IouEnvelopeReceiptTrustV2, IouEnvelopeV2Error,
39    IouEnvelopeVerificationContextV2, SignedIouEnvelopeV2, VerifiedIouEnvelopeV2,
40    CHIO_RECEIVABLE_IOU_ENVELOPE_V1_SCHEMA, RECEIVABLE_IOU_ENVELOPE_V1_SCHEMA,
41};
42pub use local_account::LocalCreditAccount;
43pub use risk_reports::{
44    CreditBacktestQuery, CreditBacktestReasonCode, CreditBacktestReport, CreditBacktestSummary,
45    CreditBacktestWindow, CreditCertificationState, CreditLossLifecycleArtifact,
46    CreditLossLifecycleEventKind, CreditLossLifecycleFinding, CreditLossLifecycleListQuery,
47    CreditLossLifecycleListReport, CreditLossLifecycleListSummary, CreditLossLifecycleQuery,
48    CreditLossLifecycleReasonCode, CreditLossLifecycleReport, CreditLossLifecycleRow,
49    CreditLossLifecycleSummary, CreditLossLifecycleSupportBoundary, CreditProviderFacilitySnapshot,
50    CreditProviderRiskPackage, CreditProviderRiskPackageQuery,
51    CreditProviderRiskPackageSupportBoundary, CreditRecentLossEntry, CreditRecentLossHistory,
52    CreditRecentLossSummary, CreditReserveControlAppealState, CreditReserveControlExecutionState,
53    CreditRuntimeAssuranceState, SignedCreditLossLifecycle, SignedCreditProviderRiskPackage,
54};
55pub use store_binding::{IouEnvelopeStore, IouEnvelopeStoreError};
56
57use serde::{Deserialize, Serialize};
58
59#[cfg(test)]
60use crate::appraisal::AttestationVerifierFamily;
61use crate::capability::{
62    governance::GovernedAutonomyTier, runtime_attestation::RuntimeAssuranceTier,
63    scope::MonetaryAmount,
64};
65use crate::receipt::{
66    decision::Decision, economics::SettlementStatus, lineage::SignedExportEnvelope,
67};
68use crate::underwriting::{
69    UnderwritingDecisionLifecycleState, UnderwritingDecisionOutcome, UnderwritingReviewState,
70    UnderwritingRiskClass,
71};
72
73pub const EXPOSURE_LEDGER_SCHEMA: &str = "chio.credit.exposure-ledger.v1";
74pub const CREDIT_SCORECARD_SCHEMA: &str = "chio.credit.scorecard.v1";
75pub const CREDIT_FACILITY_REPORT_SCHEMA: &str = "chio.credit.facility-report.v1";
76pub const CREDIT_FACILITY_ARTIFACT_SCHEMA: &str = "chio.credit.facility.v1";
77pub const CREDIT_FACILITY_LIST_REPORT_SCHEMA: &str = "chio.credit.facility-list.v1";
78pub const CREDIT_BOND_REPORT_SCHEMA: &str = "chio.credit.bond-report.v1";
79pub const CREDIT_BOND_ARTIFACT_SCHEMA: &str = "chio.credit.bond.v1";
80pub const CREDIT_BOND_LIST_REPORT_SCHEMA: &str = "chio.credit.bond-list.v1";
81pub const CREDIT_LOSS_LIFECYCLE_REPORT_SCHEMA: &str = "chio.credit.loss-lifecycle-report.v1";
82pub const CREDIT_LOSS_LIFECYCLE_ARTIFACT_SCHEMA: &str = "chio.credit.loss-lifecycle.v1";
83pub const CREDIT_LOSS_LIFECYCLE_LIST_REPORT_SCHEMA: &str = "chio.credit.loss-lifecycle-list.v1";
84pub const CREDIT_BACKTEST_REPORT_SCHEMA: &str = "chio.credit.backtest-report.v1";
85pub const CREDIT_PROVIDER_RISK_PACKAGE_SCHEMA: &str = "chio.credit.provider-risk-package.v1";
86pub const CAPITAL_BOOK_REPORT_SCHEMA: &str = "chio.credit.capital-book.v1";
87pub const CAPITAL_EXECUTION_INSTRUCTION_ARTIFACT_SCHEMA: &str =
88    "chio.credit.capital-instruction.v1";
89pub const CAPITAL_EXECUTION_AUTHORITY_STEP_PROOF_SCHEMA: &str =
90    "chio.credit.capital-authority-step-proof.v1";
91pub const CAPITAL_ALLOCATION_DECISION_ARTIFACT_SCHEMA: &str = "chio.credit.capital-allocation.v1";
92pub const CREDIT_BONDED_EXECUTION_SIMULATION_REPORT_SCHEMA: &str =
93    "chio.credit.bonded-execution-simulation-report.v1";
94pub const MAX_EXPOSURE_LEDGER_RECEIPT_LIMIT: usize = 200;
95pub const MAX_EXPOSURE_LEDGER_DECISION_LIMIT: usize = 200;
96pub const MAX_CREDIT_FACILITY_LIST_LIMIT: usize = 100;
97pub const MAX_CREDIT_BOND_LIST_LIMIT: usize = 100;
98pub const MAX_CREDIT_LOSS_LIFECYCLE_LIST_LIMIT: usize = 100;
99pub const MAX_CREDIT_BACKTEST_WINDOW_LIMIT: usize = 24;
100pub const MAX_CREDIT_PROVIDER_LOSS_LIMIT: usize = 25;
101
102pub(crate) fn bounded_limit_or_default(limit: Option<usize>, default: usize, max: usize) -> usize {
103    limit.unwrap_or(default).clamp(1, max)
104}
105
106fn validate_optional_query_filter(
107    value: &Option<String>,
108    flag: &'static str,
109) -> Result<(), String> {
110    if let Some(value) = value {
111        if value.trim().is_empty() {
112            return Err(format!(
113                "exposure ledger query filter {flag} must be non-empty"
114            ));
115        }
116        if value.trim() != value {
117            return Err(format!(
118                "exposure ledger query filter {flag} must not contain surrounding whitespace"
119            ));
120        }
121    }
122    Ok(())
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
126#[serde(rename_all = "camelCase")]
127pub struct ExposureLedgerQuery {
128    #[serde(default, skip_serializing_if = "Option::is_none")]
129    pub capability_id: Option<String>,
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub agent_subject: Option<String>,
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub tool_server: Option<String>,
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub tool_name: Option<String>,
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub since: Option<u64>,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub until: Option<u64>,
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub receipt_limit: Option<usize>,
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub decision_limit: Option<usize>,
144}
145
146impl Default for ExposureLedgerQuery {
147    fn default() -> Self {
148        Self {
149            capability_id: None,
150            agent_subject: None,
151            tool_server: None,
152            tool_name: None,
153            since: None,
154            until: None,
155            receipt_limit: Some(100),
156            decision_limit: Some(50),
157        }
158    }
159}
160
161impl ExposureLedgerQuery {
162    #[must_use]
163    pub fn receipt_limit_or_default(&self) -> usize {
164        bounded_limit_or_default(self.receipt_limit, 100, MAX_EXPOSURE_LEDGER_RECEIPT_LIMIT)
165    }
166
167    #[must_use]
168    pub fn decision_limit_or_default(&self) -> usize {
169        bounded_limit_or_default(self.decision_limit, 50, MAX_EXPOSURE_LEDGER_DECISION_LIMIT)
170    }
171
172    #[must_use]
173    pub fn normalized(&self) -> Self {
174        let mut normalized = self.clone();
175        normalized.receipt_limit = Some(self.receipt_limit_or_default());
176        normalized.decision_limit = Some(self.decision_limit_or_default());
177        normalized
178    }
179
180    pub fn validate(&self) -> Result<(), String> {
181        validate_optional_query_filter(&self.capability_id, "--capability")?;
182        validate_optional_query_filter(&self.agent_subject, "--agent-subject")?;
183        validate_optional_query_filter(&self.tool_server, "--tool-server")?;
184        validate_optional_query_filter(&self.tool_name, "--tool-name")?;
185        if self.capability_id.is_none()
186            && self.agent_subject.is_none()
187            && self.tool_server.is_none()
188        {
189            return Err(
190                "exposure ledger queries require at least one anchor: --capability, --agent-subject, or --tool-server".to_string(),
191            );
192        }
193        if self.tool_name.is_some() && self.tool_server.is_none() {
194            return Err(
195                "exposure ledger queries that specify --tool-name must also specify --tool-server"
196                    .to_string(),
197            );
198        }
199        if let (Some(since), Some(until)) = (self.since, self.until) {
200            if since > until {
201                return Err(
202                    "exposure ledger queries require --since to be less than or equal to --until"
203                        .to_string(),
204                );
205            }
206        }
207        Ok(())
208    }
209}
210
211#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
212#[serde(rename_all = "snake_case")]
213pub enum ExposureLedgerEvidenceKind {
214    Receipt,
215    SettlementReconciliation,
216    MeteredBillingReconciliation,
217    UnderwritingDecision,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
221#[serde(rename_all = "camelCase")]
222pub struct ExposureLedgerEvidenceReference {
223    pub kind: ExposureLedgerEvidenceKind,
224    pub reference_id: String,
225    #[serde(default, skip_serializing_if = "Option::is_none")]
226    pub observed_at: Option<u64>,
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub locator: Option<String>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
232#[serde(rename_all = "camelCase")]
233pub struct ExposureLedgerSupportBoundary {
234    pub governed_receipts_authoritative: bool,
235    pub underwriting_decisions_authoritative: bool,
236    pub settlement_reconciliation_authoritative: bool,
237    pub cross_currency_netting_supported: bool,
238    pub claim_adjudication_supported: bool,
239    pub recovery_lifecycle_supported: bool,
240}
241
242impl Default for ExposureLedgerSupportBoundary {
243    fn default() -> Self {
244        Self {
245            governed_receipts_authoritative: true,
246            underwriting_decisions_authoritative: true,
247            settlement_reconciliation_authoritative: true,
248            cross_currency_netting_supported: false,
249            claim_adjudication_supported: false,
250            recovery_lifecycle_supported: false,
251        }
252    }
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
256#[serde(rename_all = "camelCase")]
257pub struct ExposureLedgerCurrencyPosition {
258    pub currency: String,
259    pub governed_max_exposure_units: u64,
260    pub reserved_units: u64,
261    pub settled_units: u64,
262    pub pending_units: u64,
263    pub failed_units: u64,
264    pub provisional_loss_units: u64,
265    pub recovered_units: u64,
266    pub quoted_premium_units: u64,
267    pub active_quoted_premium_units: u64,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
271#[serde(rename_all = "camelCase")]
272pub struct ExposureLedgerReceiptEntry {
273    pub receipt_id: String,
274    pub timestamp: u64,
275    pub capability_id: String,
276    #[serde(default, skip_serializing_if = "Option::is_none")]
277    pub subject_key: Option<String>,
278    #[serde(default, skip_serializing_if = "Option::is_none")]
279    pub issuer_key: Option<String>,
280    pub tool_server: String,
281    pub tool_name: String,
282    pub decision: Decision,
283    pub settlement_status: SettlementStatus,
284    pub action_required: bool,
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub governed_max_amount: Option<MonetaryAmount>,
287    #[serde(default, skip_serializing_if = "Option::is_none")]
288    pub financial_amount: Option<MonetaryAmount>,
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub reserve_required_amount: Option<MonetaryAmount>,
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub provisional_loss_amount: Option<MonetaryAmount>,
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub recovered_amount: Option<MonetaryAmount>,
295    pub metered_action_required: bool,
296    #[serde(default, skip_serializing_if = "Vec::is_empty")]
297    pub evidence_refs: Vec<ExposureLedgerEvidenceReference>,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
301#[serde(rename_all = "camelCase")]
302pub struct ExposureLedgerDecisionEntry {
303    pub decision_id: String,
304    pub issued_at: u64,
305    #[serde(default, skip_serializing_if = "Option::is_none")]
306    pub capability_id: Option<String>,
307    #[serde(default, skip_serializing_if = "Option::is_none")]
308    pub agent_subject: Option<String>,
309    #[serde(default, skip_serializing_if = "Option::is_none")]
310    pub tool_server: Option<String>,
311    #[serde(default, skip_serializing_if = "Option::is_none")]
312    pub tool_name: Option<String>,
313    pub outcome: UnderwritingDecisionOutcome,
314    pub lifecycle_state: UnderwritingDecisionLifecycleState,
315    pub review_state: UnderwritingReviewState,
316    pub risk_class: UnderwritingRiskClass,
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    pub supersedes_decision_id: Option<String>,
319    #[serde(default, skip_serializing_if = "Option::is_none")]
320    pub quoted_premium_amount: Option<MonetaryAmount>,
321    #[serde(default, skip_serializing_if = "Vec::is_empty")]
322    pub evidence_refs: Vec<ExposureLedgerEvidenceReference>,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
326#[serde(rename_all = "camelCase")]
327pub struct ExposureLedgerSummary {
328    pub matching_receipts: u64,
329    pub returned_receipts: u64,
330    pub matching_decisions: u64,
331    pub returned_decisions: u64,
332    pub active_decisions: u64,
333    pub superseded_decisions: u64,
334    pub actionable_receipts: u64,
335    pub pending_settlement_receipts: u64,
336    pub failed_settlement_receipts: u64,
337    pub currencies: Vec<String>,
338    pub mixed_currency_book: bool,
339    pub truncated_receipts: bool,
340    pub truncated_decisions: bool,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
344#[serde(rename_all = "camelCase")]
345pub struct ExposureLedgerReport {
346    pub schema: String,
347    pub generated_at: u64,
348    pub filters: ExposureLedgerQuery,
349    pub support_boundary: ExposureLedgerSupportBoundary,
350    pub summary: ExposureLedgerSummary,
351    pub positions: Vec<ExposureLedgerCurrencyPosition>,
352    pub receipts: Vec<ExposureLedgerReceiptEntry>,
353    pub decisions: Vec<ExposureLedgerDecisionEntry>,
354}
355
356pub type SignedExposureLedgerReport = SignedExportEnvelope<ExposureLedgerReport>;
357
358#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
359#[serde(rename_all = "snake_case")]
360pub enum CreditScorecardConfidence {
361    Low,
362    Medium,
363    High,
364}
365
366#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
367#[serde(rename_all = "snake_case")]
368pub enum CreditScorecardBand {
369    Prime,
370    Standard,
371    Guarded,
372    Probationary,
373    Restricted,
374}
375
376#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
377#[serde(rename_all = "snake_case")]
378pub enum CreditScorecardDimensionKind {
379    ReputationSupport,
380    SettlementDiscipline,
381    LossPressure,
382    ExposureStewardship,
383}
384
385#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
386#[serde(rename_all = "snake_case")]
387pub enum CreditScorecardReasonCode {
388    SparseReceiptHistory,
389    SparseDayHistory,
390    LowConfidence,
391    PendingSettlementBacklog,
392    FailedSettlementBacklog,
393    ProvisionalLossPressure,
394    MixedCurrencyBook,
395    LowReputation,
396    ImportedTrustDependency,
397    MissingDecisionCoverage,
398}
399
400#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
401#[serde(rename_all = "snake_case")]
402pub enum CreditScorecardAnomalySeverity {
403    Info,
404    Warning,
405    Critical,
406}
407
408#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
409#[serde(rename_all = "snake_case")]
410pub enum CreditScorecardEvidenceKind {
411    Receipt,
412    SettlementReconciliation,
413    UnderwritingDecision,
414    ReputationInspection,
415    ComplianceScore,
416    ExposureLedger,
417    CreditBond,
418    CreditLossLifecycle,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
422#[serde(rename_all = "camelCase")]
423pub struct CreditScorecardEvidenceReference {
424    pub kind: CreditScorecardEvidenceKind,
425    pub reference_id: String,
426    #[serde(default, skip_serializing_if = "Option::is_none")]
427    pub observed_at: Option<u64>,
428    #[serde(default, skip_serializing_if = "Option::is_none")]
429    pub locator: Option<String>,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
433#[serde(rename_all = "camelCase")]
434pub struct CreditScorecardSupportBoundary {
435    pub subject_scoped_only: bool,
436    pub cross_currency_netting_supported: bool,
437    pub capital_allocation_supported: bool,
438    pub facility_policy_supported: bool,
439}
440
441impl Default for CreditScorecardSupportBoundary {
442    fn default() -> Self {
443        Self {
444            subject_scoped_only: true,
445            cross_currency_netting_supported: false,
446            capital_allocation_supported: false,
447            facility_policy_supported: false,
448        }
449    }
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
453#[serde(rename_all = "camelCase")]
454pub struct CreditScorecardDimension {
455    pub kind: CreditScorecardDimensionKind,
456    #[serde(default, skip_serializing_if = "Option::is_none")]
457    pub score: Option<f64>,
458    pub weight: f64,
459    pub description: String,
460    #[serde(default, skip_serializing_if = "Vec::is_empty")]
461    pub evidence_refs: Vec<CreditScorecardEvidenceReference>,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
465#[serde(rename_all = "camelCase")]
466pub struct CreditScorecardProbationStatus {
467    pub probationary: bool,
468    #[serde(default, skip_serializing_if = "Vec::is_empty")]
469    pub reasons: Vec<CreditScorecardReasonCode>,
470    pub receipt_count: u64,
471    pub span_days: u64,
472    pub target_receipt_count: u64,
473    pub target_span_days: u64,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
477#[serde(rename_all = "camelCase")]
478pub struct CreditScorecardAnomaly {
479    pub code: CreditScorecardReasonCode,
480    pub severity: CreditScorecardAnomalySeverity,
481    pub description: String,
482    #[serde(default, skip_serializing_if = "Vec::is_empty")]
483    pub evidence_refs: Vec<CreditScorecardEvidenceReference>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
487#[serde(rename_all = "camelCase")]
488pub struct CreditScorecardReputationContext {
489    pub effective_score: f64,
490    pub probationary: bool,
491    #[serde(default, skip_serializing_if = "Option::is_none")]
492    pub resolved_tier: Option<String>,
493    pub imported_signal_count: usize,
494    pub accepted_imported_signal_count: usize,
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
498#[serde(rename_all = "camelCase")]
499pub struct CreditScorecardSummary {
500    pub matching_receipts: u64,
501    pub returned_receipts: u64,
502    pub matching_decisions: u64,
503    pub returned_decisions: u64,
504    pub currencies: Vec<String>,
505    pub mixed_currency_book: bool,
506    pub confidence: CreditScorecardConfidence,
507    pub band: CreditScorecardBand,
508    pub overall_score: f64,
509    pub anomaly_count: u64,
510    pub probationary: bool,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
514#[serde(rename_all = "camelCase")]
515pub struct CreditScorecardReport {
516    pub schema: String,
517    pub generated_at: u64,
518    pub filters: ExposureLedgerQuery,
519    pub support_boundary: CreditScorecardSupportBoundary,
520    pub summary: CreditScorecardSummary,
521    pub reputation: CreditScorecardReputationContext,
522    pub positions: Vec<ExposureLedgerCurrencyPosition>,
523    pub probation: CreditScorecardProbationStatus,
524    pub dimensions: Vec<CreditScorecardDimension>,
525    #[serde(default, skip_serializing_if = "Vec::is_empty")]
526    pub anomalies: Vec<CreditScorecardAnomaly>,
527}
528
529pub type SignedCreditScorecardReport = SignedExportEnvelope<CreditScorecardReport>;
530
531#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
532#[serde(rename_all = "snake_case")]
533pub enum CreditFacilityDisposition {
534    Grant,
535    ManualReview,
536    Deny,
537}
538
539#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
540#[serde(rename_all = "snake_case")]
541pub enum CreditFacilityLifecycleState {
542    Active,
543    Superseded,
544    Denied,
545    Expired,
546}
547
548#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
549#[serde(rename_all = "snake_case")]
550pub enum CreditFacilityCapitalSource {
551    OperatorInternal,
552    ManualProviderReview,
553}
554
555#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
556#[serde(rename_all = "snake_case")]
557pub enum CreditFacilityReasonCode {
558    ScoreRestricted,
559    ProbationaryScore,
560    LowConfidence,
561    MixedCurrencyBook,
562    MixedRuntimeAssuranceProvenance,
563    MissingRuntimeAssurance,
564    CertificationNotActive,
565    FailedSettlementBacklog,
566    PendingSettlementBacklog,
567    FacilityGranted,
568}
569
570#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
571#[serde(rename_all = "camelCase")]
572pub struct CreditFacilityTerms {
573    pub credit_limit: MonetaryAmount,
574    pub utilization_ceiling_bps: u16,
575    pub reserve_ratio_bps: u16,
576    pub concentration_cap_bps: u16,
577    pub ttl_seconds: u64,
578    pub capital_source: CreditFacilityCapitalSource,
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
582#[serde(rename_all = "camelCase")]
583pub struct CreditFacilityPrerequisites {
584    pub minimum_runtime_assurance_tier: RuntimeAssuranceTier,
585    pub runtime_assurance_met: bool,
586    pub certification_required: bool,
587    pub certification_met: bool,
588    pub manual_review_required: bool,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
592#[serde(rename_all = "camelCase")]
593pub struct CreditFacilityFinding {
594    pub code: CreditFacilityReasonCode,
595    pub description: String,
596    #[serde(default, skip_serializing_if = "Vec::is_empty")]
597    pub evidence_refs: Vec<CreditScorecardEvidenceReference>,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
601#[serde(rename_all = "camelCase")]
602pub struct CreditFacilitySupportBoundary {
603    pub provider_neutral_policy: bool,
604    pub cross_currency_allocation_supported: bool,
605    pub bond_execution_supported: bool,
606}
607
608impl Default for CreditFacilitySupportBoundary {
609    fn default() -> Self {
610        Self {
611            provider_neutral_policy: true,
612            cross_currency_allocation_supported: false,
613            bond_execution_supported: false,
614        }
615    }
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619#[serde(rename_all = "camelCase")]
620pub struct CreditFacilityReport {
621    pub schema: String,
622    pub generated_at: u64,
623    pub filters: ExposureLedgerQuery,
624    pub scorecard: CreditScorecardSummary,
625    pub disposition: CreditFacilityDisposition,
626    pub prerequisites: CreditFacilityPrerequisites,
627    pub support_boundary: CreditFacilitySupportBoundary,
628    #[serde(default, skip_serializing_if = "Option::is_none")]
629    pub terms: Option<CreditFacilityTerms>,
630    #[serde(default, skip_serializing_if = "Vec::is_empty")]
631    pub findings: Vec<CreditFacilityFinding>,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
635#[serde(rename_all = "camelCase")]
636pub struct CreditFacilityArtifact {
637    pub schema: String,
638    pub facility_id: String,
639    pub issued_at: u64,
640    pub expires_at: u64,
641    pub lifecycle_state: CreditFacilityLifecycleState,
642    #[serde(default, skip_serializing_if = "Option::is_none")]
643    pub supersedes_facility_id: Option<String>,
644    pub report: CreditFacilityReport,
645}
646
647pub type SignedCreditFacility = SignedExportEnvelope<CreditFacilityArtifact>;
648
649#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
650#[serde(rename_all = "camelCase")]
651pub struct CreditFacilityListQuery {
652    #[serde(default, skip_serializing_if = "Option::is_none")]
653    pub facility_id: Option<String>,
654    #[serde(default, skip_serializing_if = "Option::is_none")]
655    pub capability_id: Option<String>,
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub agent_subject: Option<String>,
658    #[serde(default, skip_serializing_if = "Option::is_none")]
659    pub tool_server: Option<String>,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub tool_name: Option<String>,
662    #[serde(default, skip_serializing_if = "Option::is_none")]
663    pub disposition: Option<CreditFacilityDisposition>,
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub lifecycle_state: Option<CreditFacilityLifecycleState>,
666    #[serde(default, skip_serializing_if = "Option::is_none")]
667    pub limit: Option<usize>,
668}
669
670impl Default for CreditFacilityListQuery {
671    fn default() -> Self {
672        Self {
673            facility_id: None,
674            capability_id: None,
675            agent_subject: None,
676            tool_server: None,
677            tool_name: None,
678            disposition: None,
679            lifecycle_state: None,
680            limit: Some(50),
681        }
682    }
683}
684
685impl CreditFacilityListQuery {
686    #[must_use]
687    pub fn limit_or_default(&self) -> usize {
688        bounded_limit_or_default(self.limit, 50, MAX_CREDIT_FACILITY_LIST_LIMIT)
689    }
690
691    #[must_use]
692    pub fn normalized(&self) -> Self {
693        let mut normalized = self.clone();
694        normalized.limit = Some(self.limit_or_default());
695        normalized
696    }
697}
698
699#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
700#[serde(rename_all = "camelCase")]
701pub struct CreditFacilityRow {
702    pub facility: SignedCreditFacility,
703    pub lifecycle_state: CreditFacilityLifecycleState,
704    #[serde(default, skip_serializing_if = "Option::is_none")]
705    pub superseded_by_facility_id: Option<String>,
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
709#[serde(rename_all = "camelCase")]
710pub struct CreditFacilityListSummary {
711    pub matching_facilities: u64,
712    pub returned_facilities: u64,
713    pub active_facilities: u64,
714    pub superseded_facilities: u64,
715    pub denied_facilities: u64,
716    pub expired_facilities: u64,
717    pub granted_facilities: u64,
718    pub manual_review_facilities: u64,
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
722#[serde(rename_all = "camelCase")]
723pub struct CreditFacilityListReport {
724    pub schema: String,
725    pub generated_at: u64,
726    pub query: CreditFacilityListQuery,
727    pub summary: CreditFacilityListSummary,
728    pub facilities: Vec<CreditFacilityRow>,
729}
730
731#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
732#[serde(rename_all = "snake_case")]
733pub enum CreditBondDisposition {
734    Lock,
735    Hold,
736    Release,
737    Impair,
738}
739
740#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
741#[serde(rename_all = "snake_case")]
742pub enum CreditBondLifecycleState {
743    Active,
744    Superseded,
745    Released,
746    Impaired,
747    Expired,
748}
749
750#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
751#[serde(rename_all = "snake_case")]
752pub enum CreditBondReasonCode {
753    ActiveFacilityMissing,
754    MixedCurrencyBook,
755    PendingSettlementBacklog,
756    FailedSettlementBacklog,
757    ProvisionalLossOutstanding,
758    ReserveLocked,
759    ReserveHeld,
760    ReserveReleased,
761    UnderCollateralized,
762}
763
764#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
765#[serde(rename_all = "camelCase")]
766pub struct CreditBondTerms {
767    pub facility_id: String,
768    pub credit_limit: MonetaryAmount,
769    pub collateral_amount: MonetaryAmount,
770    pub reserve_requirement_amount: MonetaryAmount,
771    pub outstanding_exposure_amount: MonetaryAmount,
772    pub reserve_ratio_bps: u16,
773    pub coverage_ratio_bps: u16,
774    pub capital_source: CreditFacilityCapitalSource,
775}
776
777#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
778#[serde(rename_all = "camelCase")]
779pub struct CreditBondPrerequisites {
780    pub active_facility_required: bool,
781    pub active_facility_met: bool,
782    pub runtime_assurance_met: bool,
783    pub certification_required: bool,
784    pub certification_met: bool,
785    pub currency_coherent: bool,
786}
787
788#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
789#[serde(rename_all = "camelCase")]
790pub struct CreditBondFinding {
791    pub code: CreditBondReasonCode,
792    pub description: String,
793    #[serde(default, skip_serializing_if = "Vec::is_empty")]
794    pub evidence_refs: Vec<CreditScorecardEvidenceReference>,
795}
796
797#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
798#[serde(rename_all = "camelCase")]
799pub struct CreditBondSupportBoundary {
800    pub reserve_accounting_authoritative: bool,
801    pub external_escrow_execution_supported: bool,
802    pub autonomy_gating_supported: bool,
803}
804
805impl Default for CreditBondSupportBoundary {
806    fn default() -> Self {
807        Self {
808            reserve_accounting_authoritative: true,
809            external_escrow_execution_supported: false,
810            autonomy_gating_supported: false,
811        }
812    }
813}
814
815#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
816#[serde(rename_all = "camelCase")]
817pub struct CreditBondReport {
818    pub schema: String,
819    pub generated_at: u64,
820    pub filters: ExposureLedgerQuery,
821    pub exposure: ExposureLedgerSummary,
822    pub scorecard: CreditScorecardSummary,
823    pub disposition: CreditBondDisposition,
824    pub prerequisites: CreditBondPrerequisites,
825    pub support_boundary: CreditBondSupportBoundary,
826    #[serde(default, skip_serializing_if = "Option::is_none")]
827    pub latest_facility_id: Option<String>,
828    #[serde(default, skip_serializing_if = "Option::is_none")]
829    pub terms: Option<CreditBondTerms>,
830    #[serde(default, skip_serializing_if = "Vec::is_empty")]
831    pub findings: Vec<CreditBondFinding>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
835#[serde(rename_all = "camelCase")]
836pub struct CreditBondArtifact {
837    pub schema: String,
838    pub bond_id: String,
839    pub issued_at: u64,
840    pub expires_at: u64,
841    pub lifecycle_state: CreditBondLifecycleState,
842    #[serde(default, skip_serializing_if = "Option::is_none")]
843    pub supersedes_bond_id: Option<String>,
844    pub report: CreditBondReport,
845}
846
847pub type SignedCreditBond = SignedExportEnvelope<CreditBondArtifact>;
848
849#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
850#[serde(rename_all = "camelCase")]
851pub struct CreditBondListQuery {
852    #[serde(default, skip_serializing_if = "Option::is_none")]
853    pub bond_id: Option<String>,
854    #[serde(default, skip_serializing_if = "Option::is_none")]
855    pub facility_id: Option<String>,
856    #[serde(default, skip_serializing_if = "Option::is_none")]
857    pub capability_id: Option<String>,
858    #[serde(default, skip_serializing_if = "Option::is_none")]
859    pub agent_subject: Option<String>,
860    #[serde(default, skip_serializing_if = "Option::is_none")]
861    pub tool_server: Option<String>,
862    #[serde(default, skip_serializing_if = "Option::is_none")]
863    pub tool_name: Option<String>,
864    #[serde(default, skip_serializing_if = "Option::is_none")]
865    pub disposition: Option<CreditBondDisposition>,
866    #[serde(default, skip_serializing_if = "Option::is_none")]
867    pub lifecycle_state: Option<CreditBondLifecycleState>,
868    #[serde(default, skip_serializing_if = "Option::is_none")]
869    pub limit: Option<usize>,
870}
871
872impl Default for CreditBondListQuery {
873    fn default() -> Self {
874        Self {
875            bond_id: None,
876            facility_id: None,
877            capability_id: None,
878            agent_subject: None,
879            tool_server: None,
880            tool_name: None,
881            disposition: None,
882            lifecycle_state: None,
883            limit: Some(50),
884        }
885    }
886}
887
888impl CreditBondListQuery {
889    #[must_use]
890    pub fn limit_or_default(&self) -> usize {
891        bounded_limit_or_default(self.limit, 50, MAX_CREDIT_BOND_LIST_LIMIT)
892    }
893
894    #[must_use]
895    pub fn normalized(&self) -> Self {
896        let mut normalized = self.clone();
897        normalized.limit = Some(self.limit_or_default());
898        normalized
899    }
900}
901
902#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
903#[serde(rename_all = "camelCase")]
904pub struct CreditBondRow {
905    pub bond: SignedCreditBond,
906    pub lifecycle_state: CreditBondLifecycleState,
907    #[serde(default, skip_serializing_if = "Option::is_none")]
908    pub superseded_by_bond_id: Option<String>,
909}
910
911#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
912#[serde(rename_all = "camelCase")]
913pub struct CreditBondListSummary {
914    pub matching_bonds: u64,
915    pub returned_bonds: u64,
916    pub active_bonds: u64,
917    pub superseded_bonds: u64,
918    pub released_bonds: u64,
919    pub impaired_bonds: u64,
920    pub expired_bonds: u64,
921    pub locked_bonds: u64,
922    pub held_bonds: u64,
923}
924
925#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
926#[serde(rename_all = "camelCase")]
927pub struct CreditBondListReport {
928    pub schema: String,
929    pub generated_at: u64,
930    pub query: CreditBondListQuery,
931    pub summary: CreditBondListSummary,
932    pub bonds: Vec<CreditBondRow>,
933}
934
935include!("credit/capital_and_execution.rs");