1use serde::{Deserialize, Serialize};
2
3use crate::appraisal::AttestationVerifierFamily;
4use crate::capability::{runtime_attestation::RuntimeAssuranceTier, scope::MonetaryAmount};
5use crate::receipt::{economics::SettlementStatus, lineage::SignedExportEnvelope};
6use crate::underwriting::{UnderwritingCertificationState, UnderwritingComplianceEvidence};
7use crate::{
8 bounded_limit_or_default, CapitalExecutionAuthorityStep, CapitalExecutionObservation,
9 CapitalExecutionRail, CapitalExecutionReconciledState, CapitalExecutionWindow,
10 CreditBondLifecycleState, CreditFacilityDisposition, CreditFacilityLifecycleState,
11 CreditFacilityReport, CreditFacilityTerms, CreditScorecardBand,
12 CreditScorecardEvidenceReference, CreditScorecardSummary, ExposureLedgerEvidenceReference,
13 ExposureLedgerQuery, SignedCreditScorecardReport, SignedExposureLedgerReport,
14 MAX_CREDIT_BACKTEST_WINDOW_LIMIT, MAX_CREDIT_LOSS_LIFECYCLE_LIST_LIMIT,
15 MAX_CREDIT_PROVIDER_LOSS_LIMIT, MAX_EXPOSURE_LEDGER_DECISION_LIMIT,
16 MAX_EXPOSURE_LEDGER_RECEIPT_LIMIT,
17};
18
19#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "snake_case")]
21pub enum CreditLossLifecycleEventKind {
22 Delinquency,
23 Recovery,
24 ReserveRelease,
25 ReserveSlash,
26 WriteOff,
27}
28
29#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
30#[serde(rename_all = "snake_case")]
31pub enum CreditLossLifecycleReasonCode {
32 ActiveBondRequired,
33 BondNotActive,
34 DelinquencyEvidenceMissing,
35 OutstandingDelinquencyRequired,
36 AmountRequired,
37 AmountCurrencyMismatch,
38 AmountExceedsOutstandingDelinquency,
39 OutstandingExposureBlocksReserveRelease,
40 ReserveAlreadyReleased,
41 DelinquencyRecorded,
42 RecoveryRecorded,
43 ReserveReleased,
44 ReserveSlashed,
45 WriteOffRecorded,
46}
47
48#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "snake_case")]
50pub enum CreditReserveControlExecutionState {
51 PendingExecution,
52 Executed,
53}
54
55#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
56#[serde(rename_all = "snake_case")]
57pub enum CreditReserveControlAppealState {
58 Unsupported,
59 Open,
60 Closed,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
64#[serde(rename_all = "camelCase")]
65pub struct CreditLossLifecycleQuery {
66 pub bond_id: String,
67 pub event_kind: CreditLossLifecycleEventKind,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub amount: Option<MonetaryAmount>,
70}
71
72impl CreditLossLifecycleQuery {
73 pub fn validate(&self) -> Result<(), String> {
74 if self.bond_id.trim().is_empty() {
75 return Err("credit loss lifecycle requests require --bond-id".to_string());
76 }
77 if self.amount.as_ref().is_some_and(|amount| amount.units == 0) {
78 return Err("credit loss lifecycle amounts must be greater than zero".to_string());
79 }
80 Ok(())
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
85#[serde(rename_all = "camelCase")]
86pub struct CreditLossLifecycleSummary {
87 pub bond_id: String,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub facility_id: Option<String>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub capability_id: Option<String>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub agent_subject: Option<String>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub tool_server: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub tool_name: Option<String>,
98 pub current_bond_lifecycle_state: CreditBondLifecycleState,
99 pub projected_bond_lifecycle_state: CreditBondLifecycleState,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub current_delinquent_amount: Option<MonetaryAmount>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub current_recovered_amount: Option<MonetaryAmount>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub current_written_off_amount: Option<MonetaryAmount>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub current_released_reserve_amount: Option<MonetaryAmount>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub current_slashed_reserve_amount: Option<MonetaryAmount>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub outstanding_delinquent_amount: Option<MonetaryAmount>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub releaseable_reserve_amount: Option<MonetaryAmount>,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub reserve_control_source_id: Option<String>,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub execution_state: Option<CreditReserveControlExecutionState>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub appeal_state: Option<CreditReserveControlAppealState>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub appeal_window_ends_at: Option<u64>,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub event_amount: Option<MonetaryAmount>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
127#[serde(rename_all = "camelCase")]
128pub struct CreditLossLifecycleFinding {
129 pub code: CreditLossLifecycleReasonCode,
130 pub description: String,
131 #[serde(default, skip_serializing_if = "Vec::is_empty")]
132 pub evidence_refs: Vec<CreditScorecardEvidenceReference>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
136#[serde(rename_all = "camelCase")]
137pub struct CreditLossLifecycleSupportBoundary {
138 pub immutable_lifecycle_authoritative: bool,
139 pub bond_lifecycle_projection_authoritative: bool,
140 pub external_claim_adjudication_supported: bool,
141 pub automatic_capital_execution_supported: bool,
142 pub reserve_control_execution_supported: bool,
143 pub appeal_window_supported: bool,
144}
145
146impl Default for CreditLossLifecycleSupportBoundary {
147 fn default() -> Self {
148 Self {
149 immutable_lifecycle_authoritative: true,
150 bond_lifecycle_projection_authoritative: true,
151 external_claim_adjudication_supported: false,
152 automatic_capital_execution_supported: false,
153 reserve_control_execution_supported: true,
154 appeal_window_supported: true,
155 }
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
160#[serde(rename_all = "camelCase")]
161pub struct CreditLossLifecycleReport {
162 pub schema: String,
163 pub generated_at: u64,
164 pub query: CreditLossLifecycleQuery,
165 pub summary: CreditLossLifecycleSummary,
166 pub support_boundary: CreditLossLifecycleSupportBoundary,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 pub findings: Vec<CreditLossLifecycleFinding>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172#[serde(rename_all = "camelCase")]
173pub struct CreditLossLifecycleArtifact {
174 pub schema: String,
175 pub event_id: String,
176 pub issued_at: u64,
177 pub bond_id: String,
178 pub event_kind: CreditLossLifecycleEventKind,
179 pub projected_bond_lifecycle_state: CreditBondLifecycleState,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub reserve_control_source_id: Option<String>,
182 #[serde(default, skip_serializing_if = "Vec::is_empty")]
183 pub authority_chain: Vec<CapitalExecutionAuthorityStep>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub execution_window: Option<CapitalExecutionWindow>,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub rail: Option<CapitalExecutionRail>,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub observed_execution: Option<CapitalExecutionObservation>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub reconciled_state: Option<CapitalExecutionReconciledState>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub execution_state: Option<CreditReserveControlExecutionState>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub appeal_state: Option<CreditReserveControlAppealState>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub appeal_window_ends_at: Option<u64>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub description: Option<String>,
200 pub report: CreditLossLifecycleReport,
201}
202
203pub type SignedCreditLossLifecycle = SignedExportEnvelope<CreditLossLifecycleArtifact>;
204
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
206#[serde(rename_all = "camelCase")]
207pub struct CreditLossLifecycleListQuery {
208 #[serde(default, skip_serializing_if = "Option::is_none")]
209 pub event_id: Option<String>,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub bond_id: Option<String>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub facility_id: Option<String>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub capability_id: Option<String>,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub agent_subject: Option<String>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub tool_server: Option<String>,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub tool_name: Option<String>,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub event_kind: Option<CreditLossLifecycleEventKind>,
224 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub limit: Option<usize>,
226}
227
228impl Default for CreditLossLifecycleListQuery {
229 fn default() -> Self {
230 Self {
231 event_id: None,
232 bond_id: None,
233 facility_id: None,
234 capability_id: None,
235 agent_subject: None,
236 tool_server: None,
237 tool_name: None,
238 event_kind: None,
239 limit: Some(50),
240 }
241 }
242}
243
244impl CreditLossLifecycleListQuery {
245 #[must_use]
246 pub fn limit_or_default(&self) -> usize {
247 bounded_limit_or_default(self.limit, 50, MAX_CREDIT_LOSS_LIFECYCLE_LIST_LIMIT)
248 }
249
250 #[must_use]
251 pub fn normalized(&self) -> Self {
252 let mut normalized = self.clone();
253 normalized.limit = Some(self.limit_or_default());
254 normalized
255 }
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
259#[serde(rename_all = "camelCase")]
260pub struct CreditLossLifecycleRow {
261 pub event: SignedCreditLossLifecycle,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
265#[serde(rename_all = "camelCase")]
266pub struct CreditLossLifecycleListSummary {
267 pub matching_events: u64,
268 pub returned_events: u64,
269 pub delinquency_events: u64,
270 pub recovery_events: u64,
271 pub reserve_release_events: u64,
272 pub reserve_slash_events: u64,
273 pub write_off_events: u64,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
277#[serde(rename_all = "camelCase")]
278pub struct CreditLossLifecycleListReport {
279 pub schema: String,
280 pub generated_at: u64,
281 pub query: CreditLossLifecycleListQuery,
282 pub summary: CreditLossLifecycleListSummary,
283 pub events: Vec<CreditLossLifecycleRow>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
287#[serde(rename_all = "camelCase")]
288pub struct CreditBacktestQuery {
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub capability_id: Option<String>,
291 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub agent_subject: Option<String>,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub tool_server: Option<String>,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub tool_name: Option<String>,
297 #[serde(default, skip_serializing_if = "Option::is_none")]
298 pub since: Option<u64>,
299 #[serde(default, skip_serializing_if = "Option::is_none")]
300 pub until: Option<u64>,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub receipt_limit: Option<usize>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub decision_limit: Option<usize>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
306 pub window_seconds: Option<u64>,
307 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub window_count: Option<usize>,
309 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub stale_after_seconds: Option<u64>,
311}
312
313impl Default for CreditBacktestQuery {
314 fn default() -> Self {
315 Self {
316 capability_id: None,
317 agent_subject: None,
318 tool_server: None,
319 tool_name: None,
320 since: None,
321 until: None,
322 receipt_limit: Some(100),
323 decision_limit: Some(50),
324 window_seconds: Some(7 * 86_400),
325 window_count: Some(4),
326 stale_after_seconds: Some(30 * 86_400),
327 }
328 }
329}
330
331impl CreditBacktestQuery {
332 #[must_use]
333 pub fn receipt_limit_or_default(&self) -> usize {
334 bounded_limit_or_default(self.receipt_limit, 100, MAX_EXPOSURE_LEDGER_RECEIPT_LIMIT)
335 }
336
337 #[must_use]
338 pub fn decision_limit_or_default(&self) -> usize {
339 bounded_limit_or_default(self.decision_limit, 50, MAX_EXPOSURE_LEDGER_DECISION_LIMIT)
340 }
341
342 #[must_use]
343 pub fn window_seconds_or_default(&self) -> u64 {
344 self.window_seconds.unwrap_or(7 * 86_400).max(1)
345 }
346
347 #[must_use]
348 pub fn window_count_or_default(&self) -> usize {
349 bounded_limit_or_default(self.window_count, 4, MAX_CREDIT_BACKTEST_WINDOW_LIMIT)
350 }
351
352 #[must_use]
353 pub fn stale_after_seconds_or_default(&self) -> u64 {
354 self.stale_after_seconds.unwrap_or(30 * 86_400).max(1)
355 }
356
357 #[must_use]
358 pub fn normalized(&self) -> Self {
359 let mut normalized = self.clone();
360 normalized.receipt_limit = Some(self.receipt_limit_or_default());
361 normalized.decision_limit = Some(self.decision_limit_or_default());
362 normalized.window_seconds = Some(self.window_seconds_or_default());
363 normalized.window_count = Some(self.window_count_or_default());
364 normalized.stale_after_seconds = Some(self.stale_after_seconds_or_default());
365 normalized
366 }
367
368 #[must_use]
369 pub fn exposure_query(&self) -> ExposureLedgerQuery {
370 ExposureLedgerQuery {
371 capability_id: self.capability_id.clone(),
372 agent_subject: self.agent_subject.clone(),
373 tool_server: self.tool_server.clone(),
374 tool_name: self.tool_name.clone(),
375 since: self.since,
376 until: self.until,
377 receipt_limit: self.receipt_limit,
378 decision_limit: self.decision_limit,
379 }
380 }
381
382 pub fn validate(&self) -> Result<(), String> {
383 self.exposure_query().validate()?;
384 if self.agent_subject.is_none() {
385 return Err(
386 "credit backtests require --agent-subject because scorecards and facilities are subject-scoped"
387 .to_string(),
388 );
389 }
390 Ok(())
391 }
392}
393
394#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
395#[serde(rename_all = "snake_case")]
396pub enum CreditBacktestReasonCode {
397 ScoreBandShift,
398 FacilityDispositionShift,
399 MixedCurrencyBook,
400 StaleEvidence,
401 FacilityOverUtilization,
402 PendingSettlementBacklog,
403 FailedSettlementBacklog,
404 MissingRuntimeAssurance,
405 CertificationNotActive,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
409#[serde(rename_all = "camelCase")]
410pub struct CreditBacktestWindow {
411 pub index: u64,
412 pub window_started_at: u64,
413 pub window_ended_at: u64,
414 #[serde(default, skip_serializing_if = "Option::is_none")]
415 pub newest_receipt_at: Option<u64>,
416 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub expected_band: Option<CreditScorecardBand>,
418 #[serde(default, skip_serializing_if = "Option::is_none")]
419 pub expected_disposition: Option<CreditFacilityDisposition>,
420 pub simulated_scorecard: CreditScorecardSummary,
421 pub simulated_disposition: CreditFacilityDisposition,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub simulated_terms: Option<CreditFacilityTerms>,
424 pub stale_evidence: bool,
425 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub utilization_bps: Option<u32>,
427 #[serde(default, skip_serializing_if = "Vec::is_empty")]
428 pub reason_codes: Vec<CreditBacktestReasonCode>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
432#[serde(rename_all = "camelCase")]
433pub struct CreditBacktestSummary {
434 pub windows_evaluated: u64,
435 pub drift_windows: u64,
436 pub score_band_changes: u64,
437 pub facility_disposition_changes: u64,
438 pub manual_review_windows: u64,
439 pub denied_windows: u64,
440 pub stale_evidence_windows: u64,
441 pub mixed_currency_windows: u64,
442 pub over_utilized_windows: u64,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
446#[serde(rename_all = "camelCase")]
447pub struct CreditBacktestReport {
448 pub schema: String,
449 pub generated_at: u64,
450 pub query: CreditBacktestQuery,
451 pub summary: CreditBacktestSummary,
452 pub windows: Vec<CreditBacktestWindow>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
456#[serde(rename_all = "camelCase")]
457pub struct CreditProviderRiskPackageQuery {
458 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub capability_id: Option<String>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 pub agent_subject: Option<String>,
462 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub tool_server: Option<String>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub tool_name: Option<String>,
466 #[serde(default, skip_serializing_if = "Option::is_none")]
467 pub since: Option<u64>,
468 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub until: Option<u64>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub receipt_limit: Option<usize>,
472 #[serde(default, skip_serializing_if = "Option::is_none")]
473 pub decision_limit: Option<usize>,
474 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub recent_loss_limit: Option<usize>,
476}
477
478impl Default for CreditProviderRiskPackageQuery {
479 fn default() -> Self {
480 Self {
481 capability_id: None,
482 agent_subject: None,
483 tool_server: None,
484 tool_name: None,
485 since: None,
486 until: None,
487 receipt_limit: Some(100),
488 decision_limit: Some(50),
489 recent_loss_limit: Some(10),
490 }
491 }
492}
493
494impl CreditProviderRiskPackageQuery {
495 #[must_use]
496 pub fn recent_loss_limit_or_default(&self) -> usize {
497 bounded_limit_or_default(self.recent_loss_limit, 10, MAX_CREDIT_PROVIDER_LOSS_LIMIT)
498 }
499
500 #[must_use]
501 pub fn normalized(&self) -> Self {
502 let mut normalized = self.clone();
503 normalized.receipt_limit = Some(bounded_limit_or_default(
504 self.receipt_limit,
505 100,
506 MAX_EXPOSURE_LEDGER_RECEIPT_LIMIT,
507 ));
508 normalized.decision_limit = Some(bounded_limit_or_default(
509 self.decision_limit,
510 50,
511 MAX_EXPOSURE_LEDGER_DECISION_LIMIT,
512 ));
513 normalized.recent_loss_limit = Some(self.recent_loss_limit_or_default());
514 normalized
515 }
516
517 #[must_use]
518 pub fn exposure_query(&self) -> ExposureLedgerQuery {
519 ExposureLedgerQuery {
520 capability_id: self.capability_id.clone(),
521 agent_subject: self.agent_subject.clone(),
522 tool_server: self.tool_server.clone(),
523 tool_name: self.tool_name.clone(),
524 since: self.since,
525 until: self.until,
526 receipt_limit: self.receipt_limit,
527 decision_limit: self.decision_limit,
528 }
529 }
530
531 pub fn validate(&self) -> Result<(), String> {
532 self.exposure_query().validate()?;
533 if self.agent_subject.is_none() {
534 return Err(
535 "provider risk packages require --agent-subject because the package is subject-scoped"
536 .to_string(),
537 );
538 }
539 Ok(())
540 }
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
544#[serde(rename_all = "camelCase")]
545pub struct CreditRecentLossEntry {
546 pub receipt_id: String,
547 pub observed_at: u64,
548 pub settlement_status: SettlementStatus,
549 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub financial_amount: Option<MonetaryAmount>,
551 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub provisional_loss_amount: Option<MonetaryAmount>,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 pub recovered_amount: Option<MonetaryAmount>,
555 #[serde(default, skip_serializing_if = "Vec::is_empty")]
556 pub evidence_refs: Vec<ExposureLedgerEvidenceReference>,
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
560#[serde(rename_all = "camelCase")]
561pub struct CreditRecentLossSummary {
562 pub matching_loss_events: u64,
563 pub returned_loss_events: u64,
564 pub failed_settlement_events: u64,
565 pub provisional_loss_events: u64,
566 pub recovered_events: u64,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
570#[serde(rename_all = "camelCase")]
571pub struct CreditRecentLossHistory {
572 pub summary: CreditRecentLossSummary,
573 pub entries: Vec<CreditRecentLossEntry>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
577#[serde(rename_all = "camelCase")]
578pub struct CreditRuntimeAssuranceState {
579 pub governed_receipts: u64,
580 pub runtime_assurance_receipts: u64,
581 #[serde(default, skip_serializing_if = "Option::is_none")]
582 pub highest_tier: Option<RuntimeAssuranceTier>,
583 #[serde(default, skip_serializing_if = "Option::is_none")]
584 pub latest_schema: Option<String>,
585 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub latest_verifier_family: Option<AttestationVerifierFamily>,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
588 pub latest_verifier: Option<String>,
589 #[serde(default, skip_serializing_if = "Option::is_none")]
590 pub latest_evidence_sha256: Option<String>,
591 #[serde(default, skip_serializing_if = "Vec::is_empty")]
592 pub observed_verifier_families: Vec<AttestationVerifierFamily>,
593 pub stale: bool,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
597#[serde(rename_all = "camelCase")]
598pub struct CreditCertificationState {
599 pub required: bool,
600 #[serde(default, skip_serializing_if = "Option::is_none")]
601 pub state: Option<UnderwritingCertificationState>,
602 #[serde(default, skip_serializing_if = "Option::is_none")]
603 pub artifact_id: Option<String>,
604 #[serde(default, skip_serializing_if = "Option::is_none")]
605 pub checked_at: Option<u64>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub published_at: Option<u64>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
611#[serde(rename_all = "camelCase")]
612pub struct CreditProviderRiskPackageSupportBoundary {
613 pub signed_exposure_authoritative: bool,
614 pub signed_scorecard_authoritative: bool,
615 pub facility_policy_authoritative: bool,
616 pub compliance_score_reference_supported: bool,
617 pub external_capital_review_supported: bool,
618 pub autonomous_pricing_supported: bool,
619 pub liability_market_supported: bool,
620}
621
622impl Default for CreditProviderRiskPackageSupportBoundary {
623 fn default() -> Self {
624 Self {
625 signed_exposure_authoritative: true,
626 signed_scorecard_authoritative: true,
627 facility_policy_authoritative: true,
628 compliance_score_reference_supported: true,
629 external_capital_review_supported: true,
630 autonomous_pricing_supported: false,
631 liability_market_supported: false,
632 }
633 }
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
637#[serde(rename_all = "camelCase")]
638pub struct CreditProviderFacilitySnapshot {
639 pub facility_id: String,
640 pub issued_at: u64,
641 pub expires_at: u64,
642 pub disposition: CreditFacilityDisposition,
643 pub lifecycle_state: CreditFacilityLifecycleState,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub credit_limit: Option<MonetaryAmount>,
646 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub supersedes_facility_id: Option<String>,
648 pub signer_key: String,
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
652#[serde(rename_all = "camelCase")]
653pub struct CreditProviderRiskPackage {
654 pub schema: String,
655 pub generated_at: u64,
656 pub subject_key: String,
657 pub filters: CreditProviderRiskPackageQuery,
658 pub support_boundary: CreditProviderRiskPackageSupportBoundary,
659 pub exposure: SignedExposureLedgerReport,
660 pub scorecard: SignedCreditScorecardReport,
661 pub facility_report: CreditFacilityReport,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
663 pub compliance_score: Option<UnderwritingComplianceEvidence>,
664 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub latest_facility: Option<CreditProviderFacilitySnapshot>,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
667 pub runtime_assurance: Option<CreditRuntimeAssuranceState>,
668 pub certification: CreditCertificationState,
669 pub recent_loss_history: CreditRecentLossHistory,
670 #[serde(default, skip_serializing_if = "Vec::is_empty")]
671 pub evidence_refs: Vec<CreditScorecardEvidenceReference>,
672}
673
674pub type SignedCreditProviderRiskPackage = SignedExportEnvelope<CreditProviderRiskPackage>;