1#![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");