1pub use chio_core_types::{capability, receipt};
9pub use chio_market as market;
10pub use chio_web3 as web3;
11
12use std::collections::HashSet;
13
14use serde::{Deserialize, Serialize};
15
16use crate::capability::{MonetaryAmount, RuntimeAssuranceTier};
17use crate::market::LiabilityCoverageClass;
18use crate::receipt::SignedExportEnvelope;
19use crate::web3::Web3SettlementLifecycleState;
20
21pub const CHIO_AUTONOMOUS_PRICING_INPUT_SCHEMA: &str = "chio.autonomous-pricing-input.v1";
22pub const CHIO_AUTONOMOUS_PRICING_AUTHORITY_ENVELOPE_SCHEMA: &str =
23 "chio.autonomous-pricing-authority-envelope.v1";
24pub const CHIO_AUTONOMOUS_PRICING_DECISION_SCHEMA: &str = "chio.autonomous-pricing-decision.v1";
25pub const CHIO_CAPITAL_POOL_OPTIMIZATION_SCHEMA: &str = "chio.capital-pool-optimization.v1";
26pub const CHIO_CAPITAL_POOL_SIMULATION_REPORT_SCHEMA: &str =
27 "chio.capital-pool-simulation-report.v1";
28pub const CHIO_AUTONOMOUS_EXECUTION_DECISION_SCHEMA: &str = "chio.autonomous-execution-decision.v1";
29pub const CHIO_AUTONOMOUS_ROLLBACK_PLAN_SCHEMA: &str = "chio.autonomous-rollback-plan.v1";
30pub const CHIO_AUTONOMOUS_COMPARISON_REPORT_SCHEMA: &str = "chio.autonomous-comparison-report.v1";
31pub const CHIO_AUTONOMOUS_DRIFT_REPORT_SCHEMA: &str = "chio.autonomous-drift-report.v1";
32pub const CHIO_AUTONOMOUS_QUALIFICATION_MATRIX_SCHEMA: &str =
33 "chio.autonomous-qualification-matrix.v1";
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum AutonomousEvidenceKind {
38 UnderwritingDecision,
39 ExposureLedger,
40 CreditScorecard,
41 CapitalBook,
42 CreditFacility,
43 CreditLossLifecycle,
44 Web3SettlementReceipt,
45 LiabilityQuoteResponse,
46 LiabilityAutoBindDecision,
47 ClaimWorkflow,
48 RuntimeAssuranceAppraisal,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum AutonomousPricingAction {
54 Reprice,
55 Renew,
56 Decline,
57 Bind,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum AutonomousPricingDisposition {
63 Reprice,
64 Renew,
65 Decline,
66 BindWithinEnvelope,
67 ManualReview,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72pub enum AutonomousDecisionReviewState {
73 AutoApproved,
74 HumanReviewRequired,
75 ShadowOnly,
76 Blocked,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub enum AutonomousAutomationMode {
82 Shadow,
83 Advisory,
84 Active,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum AutonomousAuthorityEnvelopeKind {
90 OperatorPolicy,
91 RegulatedRole,
92 DelegatedMarketAuthority,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
96#[serde(rename_all = "snake_case")]
97pub enum AutonomousPricingExplanationDirection {
98 Increase,
99 Decrease,
100 Hold,
101 Escalate,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum CapitalOptimizationAction {
107 IncreaseReserve,
108 DecreaseReserve,
109 ShiftCapacity,
110 HoldCapacity,
111 DeferClaim,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum CapitalPoolSimulationMode {
117 WhatIf,
118 Shadow,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
122#[serde(rename_all = "snake_case")]
123pub enum AutonomousExecutionAction {
124 Reprice,
125 Renew,
126 Decline,
127 Bind,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
131#[serde(rename_all = "snake_case")]
132pub enum AutonomousExecutionLifecycleState {
133 Prepared,
134 Executed,
135 Interrupted,
136 RolledBack,
137 Blocked,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142pub enum AutonomousDriftKind {
143 LossRatioSpike,
144 PremiumVariance,
145 CapitalUtilization,
146 SettlementFailureRate,
147 OverrideRate,
148 ModelVersionMismatch,
149 EvidenceStaleness,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub enum AutonomousDriftSeverity {
155 Warning,
156 Critical,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
160#[serde(rename_all = "snake_case")]
161pub enum AutonomousSafeState {
162 ShadowModeOnly,
163 DelegatedOnly,
164 BindDisabled,
165 FullPause,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
169#[serde(rename_all = "snake_case")]
170pub enum AutonomousRollbackAction {
171 SwitchToSafeState,
172 CancelPendingExecution,
173 RequireHumanApproval,
174 RevertToDelegatedAuthority,
175 FreezeModelVersion,
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum AutonomousComparisonDisposition {
181 Match,
182 NarrowerThanManual,
183 WiderThanManual,
184 ManualOverride,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
188#[serde(rename_all = "snake_case")]
189pub enum AutonomousQualificationOutcome {
190 Pass,
191 FailClosed,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(deny_unknown_fields)]
196pub struct AutonomousEvidenceReference {
197 pub kind: AutonomousEvidenceKind,
198 pub reference_id: String,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub observed_at: Option<u64>,
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub locator: Option<String>,
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(deny_unknown_fields)]
207pub struct AutonomousPricingSupportBoundary {
208 pub delegated_authority_required: bool,
209 pub live_bind_supported: bool,
210 pub reserve_optimization_required: bool,
211 pub operator_override_supported: bool,
212}
213
214impl Default for AutonomousPricingSupportBoundary {
215 fn default() -> Self {
216 Self {
217 delegated_authority_required: true,
218 live_bind_supported: true,
219 reserve_optimization_required: true,
220 operator_override_supported: true,
221 }
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(deny_unknown_fields)]
227pub struct AutonomousModelProvenance {
228 pub model_id: String,
229 pub model_version: String,
230 pub engine_family: String,
231 pub published_at: u64,
232 pub training_cutoff: u64,
233 pub input_hash: String,
234 pub explanation_version: String,
235 pub supports_counterfactuals: bool,
236 pub supports_shadow_evaluation: bool,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(deny_unknown_fields)]
241pub struct AutonomousPricingInputArtifact {
242 pub schema: String,
243 pub input_id: String,
244 pub generated_at: u64,
245 pub subject_key: String,
246 pub provider_id: String,
247 pub coverage_class: LiabilityCoverageClass,
248 pub currency: String,
249 pub requested_coverage_amount: MonetaryAmount,
250 pub receipt_history_window_secs: u64,
251 pub reputation_score_bps: u32,
252 pub runtime_assurance_tier: RuntimeAssuranceTier,
253 pub pending_loss_units: u64,
254 pub settled_loss_units: u64,
255 pub available_capital_units: u64,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub latest_web3_settlement_state: Option<Web3SettlementLifecycleState>,
258 pub evidence_refs: Vec<AutonomousEvidenceReference>,
259 pub support_boundary: AutonomousPricingSupportBoundary,
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub note: Option<String>,
262}
263
264pub type SignedAutonomousPricingInput = SignedExportEnvelope<AutonomousPricingInputArtifact>;
265
266#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
267#[serde(deny_unknown_fields)]
268pub struct AutonomousPricingAuthorityEnvelopeArtifact {
269 pub schema: String,
270 pub envelope_id: String,
271 pub issued_at: u64,
272 pub subject_key: String,
273 pub provider_id: String,
274 pub currency: String,
275 pub kind: AutonomousAuthorityEnvelopeKind,
276 pub automation_mode: AutonomousAutomationMode,
277 pub permitted_actions: Vec<AutonomousPricingAction>,
278 pub authority_chain_refs: Vec<String>,
279 pub max_coverage_amount: MonetaryAmount,
280 pub max_premium_amount: MonetaryAmount,
281 pub max_rate_change_bps: u32,
282 pub max_daily_decisions: u32,
283 pub requires_human_review_for_bind: bool,
284 #[serde(default, skip_serializing_if = "Option::is_none")]
285 pub requires_human_review_above_premium: Option<MonetaryAmount>,
286 #[serde(default, skip_serializing_if = "Option::is_none")]
287 pub regulated_role: Option<String>,
288 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub delegated_authority_reference: Option<String>,
290 pub not_before: u64,
291 pub not_after: u64,
292 pub support_boundary: AutonomousPricingSupportBoundary,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub note: Option<String>,
295}
296
297pub type SignedAutonomousPricingAuthorityEnvelope =
298 SignedExportEnvelope<AutonomousPricingAuthorityEnvelopeArtifact>;
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(deny_unknown_fields)]
302pub struct AutonomousPricingExplanationFactor {
303 pub code: String,
304 pub description: String,
305 pub direction: AutonomousPricingExplanationDirection,
306 pub weight_bps: u32,
307 #[serde(default, skip_serializing_if = "Vec::is_empty")]
308 pub evidence_refs: Vec<AutonomousEvidenceReference>,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
312#[serde(deny_unknown_fields)]
313pub struct AutonomousPricingDecisionArtifact {
314 pub schema: String,
315 pub decision_id: String,
316 pub issued_at: u64,
317 pub pricing_input: AutonomousPricingInputArtifact,
318 pub model: AutonomousModelProvenance,
319 pub authority_envelope: AutonomousPricingAuthorityEnvelopeArtifact,
320 pub disposition: AutonomousPricingDisposition,
321 pub review_state: AutonomousDecisionReviewState,
322 pub suggested_coverage_amount: MonetaryAmount,
323 pub suggested_premium_amount: MonetaryAmount,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub suggested_ceiling_factor_bps: Option<u32>,
326 pub confidence_bps: u32,
327 pub explanation_factors: Vec<AutonomousPricingExplanationFactor>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub comparison_baseline_ref: Option<String>,
330 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub note: Option<String>,
332}
333
334pub type SignedAutonomousPricingDecision = SignedExportEnvelope<AutonomousPricingDecisionArtifact>;
335
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337#[serde(deny_unknown_fields)]
338pub struct CapitalPoolOptimizationSupportBoundary {
339 pub live_mutation_supported: bool,
340 pub scenario_comparison_supported: bool,
341 pub cross_currency_optimization_supported: bool,
342 pub web3_reconciliation_required: bool,
343 pub operator_override_required: bool,
344}
345
346impl Default for CapitalPoolOptimizationSupportBoundary {
347 fn default() -> Self {
348 Self {
349 live_mutation_supported: false,
350 scenario_comparison_supported: true,
351 cross_currency_optimization_supported: false,
352 web3_reconciliation_required: true,
353 operator_override_required: true,
354 }
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359#[serde(deny_unknown_fields)]
360pub struct CapitalPoolRecommendation {
361 pub action: CapitalOptimizationAction,
362 pub source_ref: String,
363 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub destination_ref: Option<String>,
365 pub amount: MonetaryAmount,
366 pub rationale: String,
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
370#[serde(deny_unknown_fields)]
371pub struct CapitalPoolOptimizationArtifact {
372 pub schema: String,
373 pub optimization_id: String,
374 pub issued_at: u64,
375 pub subject_key: String,
376 pub currency: String,
377 pub pricing_decision_ref: String,
378 pub capital_book_ref: String,
379 pub facility_refs: Vec<String>,
380 #[serde(default, skip_serializing_if = "Vec::is_empty")]
381 pub pending_claim_refs: Vec<String>,
382 pub target_reserve_ratio_bps: u32,
383 pub max_facility_utilization_bps: u32,
384 pub max_bind_capacity_units: u64,
385 pub recommendations: Vec<CapitalPoolRecommendation>,
386 pub support_boundary: CapitalPoolOptimizationSupportBoundary,
387 #[serde(default, skip_serializing_if = "Option::is_none")]
388 pub note: Option<String>,
389}
390
391pub type SignedCapitalPoolOptimization = SignedExportEnvelope<CapitalPoolOptimizationArtifact>;
392
393#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
394#[serde(deny_unknown_fields)]
395pub struct CapitalPoolSimulationDelta {
396 pub metric_name: String,
397 pub baseline_units: u64,
398 pub candidate_units: u64,
399 pub description: String,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
403#[serde(deny_unknown_fields)]
404pub struct CapitalPoolSimulationReport {
405 pub schema: String,
406 pub simulation_id: String,
407 pub generated_at: u64,
408 pub subject_key: String,
409 pub currency: String,
410 pub baseline_optimization: CapitalPoolOptimizationArtifact,
411 pub candidate_optimization: CapitalPoolOptimizationArtifact,
412 pub simulation_mode: CapitalPoolSimulationMode,
413 pub deltas: Vec<CapitalPoolSimulationDelta>,
414 pub recommended_operator_action: String,
415 #[serde(default, skip_serializing_if = "Option::is_none")]
416 pub note: Option<String>,
417}
418
419pub type SignedCapitalPoolSimulationReport = SignedExportEnvelope<CapitalPoolSimulationReport>;
420
421#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
422#[serde(deny_unknown_fields)]
423pub struct AutonomousExecutionSafetyGate {
424 pub name: String,
425 pub passed: bool,
426 pub description: String,
427}
428
429#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
430#[serde(deny_unknown_fields)]
431pub struct AutonomousExecutionRollbackControl {
432 pub rollback_plan_ref: String,
433 pub interruptible: bool,
434 pub human_interrupt_contact: String,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
438#[serde(deny_unknown_fields)]
439pub struct AutonomousExecutionDecisionArtifact {
440 pub schema: String,
441 pub execution_id: String,
442 pub issued_at: u64,
443 pub pricing_decision_ref: String,
444 pub optimization_ref: String,
445 pub authority_envelope_ref: String,
446 pub subject_key: String,
447 pub provider_id: String,
448 pub currency: String,
449 pub action: AutonomousExecutionAction,
450 pub lifecycle_state: AutonomousExecutionLifecycleState,
451 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub quote_response_ref: Option<String>,
453 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub auto_bind_decision_ref: Option<String>,
455 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub bound_coverage_ref: Option<String>,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
458 pub settlement_dispatch_ref: Option<String>,
459 pub safety_gates: Vec<AutonomousExecutionSafetyGate>,
460 pub rollback_control: AutonomousExecutionRollbackControl,
461 #[serde(default, skip_serializing_if = "Option::is_none")]
462 pub note: Option<String>,
463}
464
465pub type SignedAutonomousExecutionDecision =
466 SignedExportEnvelope<AutonomousExecutionDecisionArtifact>;
467
468#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469#[serde(deny_unknown_fields)]
470pub struct AutonomousRollbackPlanArtifact {
471 pub schema: String,
472 pub plan_id: String,
473 pub issued_at: u64,
474 pub subject_key: String,
475 pub safe_state: AutonomousSafeState,
476 pub triggers: Vec<AutonomousDriftKind>,
477 pub actions: Vec<AutonomousRollbackAction>,
478 pub requires_operator_ack: bool,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub note: Option<String>,
481}
482
483pub type SignedAutonomousRollbackPlan = SignedExportEnvelope<AutonomousRollbackPlanArtifact>;
484
485#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
486#[serde(deny_unknown_fields)]
487pub struct AutonomousComparisonDelta {
488 pub field: String,
489 pub automated_value: String,
490 pub manual_value: String,
491 pub description: String,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
495#[serde(deny_unknown_fields)]
496pub struct AutonomousComparisonReport {
497 pub schema: String,
498 pub comparison_id: String,
499 pub generated_at: u64,
500 pub pricing_decision_ref: String,
501 pub manual_decision_ref: String,
502 pub disposition: AutonomousComparisonDisposition,
503 #[serde(default, skip_serializing_if = "Vec::is_empty")]
504 pub deltas: Vec<AutonomousComparisonDelta>,
505 #[serde(default, skip_serializing_if = "Option::is_none")]
506 pub override_reference: Option<String>,
507 #[serde(default, skip_serializing_if = "Option::is_none")]
508 pub note: Option<String>,
509}
510
511pub type SignedAutonomousComparisonReport = SignedExportEnvelope<AutonomousComparisonReport>;
512
513#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
514#[serde(deny_unknown_fields)]
515pub struct AutonomousDriftSignal {
516 pub kind: AutonomousDriftKind,
517 pub severity: AutonomousDriftSeverity,
518 pub metric_name: String,
519 pub observed_value: u64,
520 pub threshold_value: u64,
521 pub description: String,
522 #[serde(default, skip_serializing_if = "Vec::is_empty")]
523 pub evidence_refs: Vec<AutonomousEvidenceReference>,
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
527#[serde(deny_unknown_fields)]
528pub struct AutonomousDriftReport {
529 pub schema: String,
530 pub drift_report_id: String,
531 pub generated_at: u64,
532 pub subject_key: String,
533 pub pricing_decision_ref: String,
534 pub optimization_ref: String,
535 pub drift_signals: Vec<AutonomousDriftSignal>,
536 pub rollback_plan: AutonomousRollbackPlanArtifact,
537 pub comparison_report: AutonomousComparisonReport,
538 pub fail_safe_engaged: bool,
539 #[serde(default, skip_serializing_if = "Option::is_none")]
540 pub note: Option<String>,
541}
542
543pub type SignedAutonomousDriftReport = SignedExportEnvelope<AutonomousDriftReport>;
544
545#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
546#[serde(deny_unknown_fields)]
547pub struct AutonomousQualificationCase {
548 pub id: String,
549 pub name: String,
550 pub requirement_ids: Vec<String>,
551 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub drift_kind: Option<AutonomousDriftKind>,
553 pub expected_outcome: AutonomousQualificationOutcome,
554 pub observed_outcome: AutonomousQualificationOutcome,
555 pub notes: String,
556}
557
558#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
559#[serde(deny_unknown_fields)]
560pub struct AutonomousQualificationMatrix {
561 pub schema: String,
562 pub profile_id: String,
563 pub cases: Vec<AutonomousQualificationCase>,
564}
565
566#[derive(Debug, thiserror::Error, PartialEq, Eq)]
567pub enum AutonomyContractError {
568 #[error("unsupported schema: {0}")]
569 UnsupportedSchema(String),
570
571 #[error("missing field: {0}")]
572 MissingField(&'static str),
573
574 #[error("duplicate value: {0}")]
575 DuplicateValue(String),
576
577 #[error("unknown reference: {0}")]
578 UnknownReference(String),
579
580 #[error("invalid envelope: {0}")]
581 InvalidEnvelope(String),
582
583 #[error("invalid decision: {0}")]
584 InvalidDecision(String),
585
586 #[error("invalid optimization: {0}")]
587 InvalidOptimization(String),
588
589 #[error("invalid execution: {0}")]
590 InvalidExecution(String),
591
592 #[error("invalid drift: {0}")]
593 InvalidDrift(String),
594
595 #[error("invalid qualification case: {0}")]
596 InvalidQualificationCase(String),
597}
598
599pub fn validate_autonomous_pricing_input(
600 input: &AutonomousPricingInputArtifact,
601) -> Result<(), AutonomyContractError> {
602 if input.schema != CHIO_AUTONOMOUS_PRICING_INPUT_SCHEMA {
603 return Err(AutonomyContractError::UnsupportedSchema(
604 input.schema.clone(),
605 ));
606 }
607 ensure_non_empty(&input.input_id, "autonomous_pricing_input.input_id")?;
608 ensure_non_empty(&input.subject_key, "autonomous_pricing_input.subject_key")?;
609 ensure_non_empty(&input.provider_id, "autonomous_pricing_input.provider_id")?;
610 validate_currency_code(&input.currency, "autonomous_pricing_input.currency")?;
611 validate_positive_money(
612 &input.requested_coverage_amount,
613 "autonomous_pricing_input.requested_coverage_amount",
614 )?;
615 if input
616 .requested_coverage_amount
617 .currency
618 .trim()
619 .to_ascii_uppercase()
620 != input.currency
621 {
622 return Err(AutonomyContractError::InvalidDecision(
623 "autonomous pricing input coverage amount currency must match currency".to_string(),
624 ));
625 }
626 if input.receipt_history_window_secs == 0 {
627 return Err(AutonomyContractError::InvalidDecision(
628 "autonomous pricing input receipt_history_window_secs must be non-zero".to_string(),
629 ));
630 }
631 if input.reputation_score_bps > 10_000 {
632 return Err(AutonomyContractError::InvalidDecision(
633 "autonomous pricing input reputation_score_bps must be <= 10000".to_string(),
634 ));
635 }
636 if input.available_capital_units == 0 {
637 return Err(AutonomyContractError::InvalidDecision(
638 "autonomous pricing input available_capital_units must be non-zero".to_string(),
639 ));
640 }
641 if input.evidence_refs.is_empty() {
642 return Err(AutonomyContractError::MissingField(
643 "autonomous_pricing_input.evidence_refs",
644 ));
645 }
646
647 let mut ids = HashSet::new();
648 let mut kinds = HashSet::new();
649 for evidence in &input.evidence_refs {
650 ensure_non_empty(
651 &evidence.reference_id,
652 "autonomous_pricing_input.evidence_refs.reference_id",
653 )?;
654 if !ids.insert(evidence.reference_id.as_str()) {
655 return Err(AutonomyContractError::DuplicateValue(
656 evidence.reference_id.clone(),
657 ));
658 }
659 kinds.insert(evidence.kind);
660 }
661 for required in [
662 AutonomousEvidenceKind::UnderwritingDecision,
663 AutonomousEvidenceKind::ExposureLedger,
664 AutonomousEvidenceKind::CreditScorecard,
665 AutonomousEvidenceKind::CapitalBook,
666 ] {
667 if !kinds.contains(&required) {
668 return Err(AutonomyContractError::UnknownReference(format!(
669 "autonomous pricing input missing required evidence {:?}",
670 required
671 )));
672 }
673 }
674 if (input.pending_loss_units > 0 || input.settled_loss_units > 0)
675 && !kinds.contains(&AutonomousEvidenceKind::CreditLossLifecycle)
676 {
677 return Err(AutonomyContractError::UnknownReference(
678 "autonomous pricing input with loss units must include credit_loss_lifecycle evidence"
679 .to_string(),
680 ));
681 }
682 if input.latest_web3_settlement_state.is_some()
683 && !kinds.contains(&AutonomousEvidenceKind::Web3SettlementReceipt)
684 {
685 return Err(AutonomyContractError::UnknownReference(
686 "autonomous pricing input with latest_web3_settlement_state must include web3_settlement_receipt evidence"
687 .to_string(),
688 ));
689 }
690 Ok(())
691}
692
693pub fn validate_autonomous_pricing_authority_envelope(
694 envelope: &AutonomousPricingAuthorityEnvelopeArtifact,
695) -> Result<(), AutonomyContractError> {
696 if envelope.schema != CHIO_AUTONOMOUS_PRICING_AUTHORITY_ENVELOPE_SCHEMA {
697 return Err(AutonomyContractError::UnsupportedSchema(
698 envelope.schema.clone(),
699 ));
700 }
701 ensure_non_empty(
702 &envelope.envelope_id,
703 "autonomous_authority_envelope.envelope_id",
704 )?;
705 ensure_non_empty(
706 &envelope.subject_key,
707 "autonomous_authority_envelope.subject_key",
708 )?;
709 ensure_non_empty(
710 &envelope.provider_id,
711 "autonomous_authority_envelope.provider_id",
712 )?;
713 validate_currency_code(&envelope.currency, "autonomous_authority_envelope.currency")?;
714 if envelope.permitted_actions.is_empty() {
715 return Err(AutonomyContractError::MissingField(
716 "autonomous_authority_envelope.permitted_actions",
717 ));
718 }
719 ensure_unique_copy_values(
720 &envelope.permitted_actions,
721 "autonomous_authority_envelope.permitted_actions",
722 )?;
723 if envelope.authority_chain_refs.is_empty() {
724 return Err(AutonomyContractError::MissingField(
725 "autonomous_authority_envelope.authority_chain_refs",
726 ));
727 }
728 ensure_unique_strings(
729 &envelope.authority_chain_refs,
730 "autonomous_authority_envelope.authority_chain_refs",
731 )?;
732 validate_positive_money(
733 &envelope.max_coverage_amount,
734 "autonomous_authority_envelope.max_coverage_amount",
735 )?;
736 validate_positive_money(
737 &envelope.max_premium_amount,
738 "autonomous_authority_envelope.max_premium_amount",
739 )?;
740 if envelope
741 .max_coverage_amount
742 .currency
743 .trim()
744 .to_ascii_uppercase()
745 != envelope.currency
746 {
747 return Err(AutonomyContractError::InvalidEnvelope(
748 "autonomous authority max_coverage_amount currency must match envelope currency"
749 .to_string(),
750 ));
751 }
752 if envelope
753 .max_premium_amount
754 .currency
755 .trim()
756 .to_ascii_uppercase()
757 != envelope.currency
758 {
759 return Err(AutonomyContractError::InvalidEnvelope(
760 "autonomous authority max_premium_amount currency must match envelope currency"
761 .to_string(),
762 ));
763 }
764 if envelope.max_rate_change_bps == 0 || envelope.max_rate_change_bps > 10_000 {
765 return Err(AutonomyContractError::InvalidEnvelope(
766 "autonomous authority max_rate_change_bps must be between 1 and 10000".to_string(),
767 ));
768 }
769 if envelope.max_daily_decisions == 0 {
770 return Err(AutonomyContractError::InvalidEnvelope(
771 "autonomous authority max_daily_decisions must be non-zero".to_string(),
772 ));
773 }
774 if let Some(threshold) = envelope.requires_human_review_above_premium.as_ref() {
775 validate_positive_money(
776 threshold,
777 "autonomous_authority_envelope.requires_human_review_above_premium",
778 )?;
779 if threshold.currency.trim().to_ascii_uppercase() != envelope.currency {
780 return Err(AutonomyContractError::InvalidEnvelope(
781 "autonomous authority premium review threshold currency must match envelope currency"
782 .to_string(),
783 ));
784 }
785 if threshold.units > envelope.max_premium_amount.units {
786 return Err(AutonomyContractError::InvalidEnvelope(
787 "autonomous authority premium review threshold cannot exceed max_premium_amount"
788 .to_string(),
789 ));
790 }
791 }
792 if envelope.not_before >= envelope.not_after {
793 return Err(AutonomyContractError::InvalidEnvelope(
794 "autonomous authority not_before must be earlier than not_after".to_string(),
795 ));
796 }
797 match envelope.kind {
798 AutonomousAuthorityEnvelopeKind::OperatorPolicy => {}
799 AutonomousAuthorityEnvelopeKind::RegulatedRole => {
800 ensure_non_empty(
801 envelope.regulated_role.as_deref().unwrap_or_default(),
802 "autonomous_authority_envelope.regulated_role",
803 )?;
804 }
805 AutonomousAuthorityEnvelopeKind::DelegatedMarketAuthority => {
806 ensure_non_empty(
807 envelope
808 .delegated_authority_reference
809 .as_deref()
810 .unwrap_or_default(),
811 "autonomous_authority_envelope.delegated_authority_reference",
812 )?;
813 }
814 }
815 if envelope.automation_mode != AutonomousAutomationMode::Active
816 && envelope
817 .permitted_actions
818 .contains(&AutonomousPricingAction::Bind)
819 {
820 return Err(AutonomyContractError::InvalidEnvelope(
821 "only active automation envelopes may permit bind".to_string(),
822 ));
823 }
824 Ok(())
825}
826
827pub fn validate_autonomous_pricing_decision(
828 decision: &AutonomousPricingDecisionArtifact,
829) -> Result<(), AutonomyContractError> {
830 if decision.schema != CHIO_AUTONOMOUS_PRICING_DECISION_SCHEMA {
831 return Err(AutonomyContractError::UnsupportedSchema(
832 decision.schema.clone(),
833 ));
834 }
835 ensure_non_empty(
836 &decision.decision_id,
837 "autonomous_pricing_decision.decision_id",
838 )?;
839 validate_autonomous_pricing_input(&decision.pricing_input)?;
840 validate_autonomous_pricing_authority_envelope(&decision.authority_envelope)?;
841 validate_model_provenance(&decision.model)?;
842 validate_positive_money(
843 &decision.suggested_coverage_amount,
844 "autonomous_pricing_decision.suggested_coverage_amount",
845 )?;
846 validate_positive_money(
847 &decision.suggested_premium_amount,
848 "autonomous_pricing_decision.suggested_premium_amount",
849 )?;
850 if decision
851 .suggested_coverage_amount
852 .currency
853 .trim()
854 .to_ascii_uppercase()
855 != decision.authority_envelope.currency
856 || decision
857 .suggested_premium_amount
858 .currency
859 .trim()
860 .to_ascii_uppercase()
861 != decision.authority_envelope.currency
862 {
863 return Err(AutonomyContractError::InvalidDecision(
864 "autonomous pricing decision money fields must match envelope currency".to_string(),
865 ));
866 }
867 if decision.pricing_input.subject_key != decision.authority_envelope.subject_key
868 || decision.pricing_input.provider_id != decision.authority_envelope.provider_id
869 || decision.pricing_input.currency != decision.authority_envelope.currency
870 {
871 return Err(AutonomyContractError::InvalidDecision(
872 "autonomous pricing decision input must match authority envelope subject/provider/currency"
873 .to_string(),
874 ));
875 }
876 if decision.suggested_coverage_amount.units
877 > decision.authority_envelope.max_coverage_amount.units
878 {
879 return Err(AutonomyContractError::InvalidDecision(
880 "autonomous pricing decision coverage exceeds authority envelope".to_string(),
881 ));
882 }
883 if decision.suggested_premium_amount.units
884 > decision.authority_envelope.max_premium_amount.units
885 {
886 return Err(AutonomyContractError::InvalidDecision(
887 "autonomous pricing decision premium exceeds authority envelope".to_string(),
888 ));
889 }
890 if let Some(ceiling_factor_bps) = decision.suggested_ceiling_factor_bps {
891 if ceiling_factor_bps == 0 || ceiling_factor_bps > 10_000 {
892 return Err(AutonomyContractError::InvalidDecision(
893 "autonomous pricing decision suggested_ceiling_factor_bps must be between 1 and 10000"
894 .to_string(),
895 ));
896 }
897 }
898 if decision.confidence_bps == 0 || decision.confidence_bps > 10_000 {
899 return Err(AutonomyContractError::InvalidDecision(
900 "autonomous pricing decision confidence_bps must be between 1 and 10000".to_string(),
901 ));
902 }
903 if decision.explanation_factors.is_empty() {
904 return Err(AutonomyContractError::MissingField(
905 "autonomous_pricing_decision.explanation_factors",
906 ));
907 }
908 let mut factor_codes = HashSet::new();
909 for factor in &decision.explanation_factors {
910 ensure_non_empty(
911 &factor.code,
912 "autonomous_pricing_decision.explanation_factors.code",
913 )?;
914 ensure_non_empty(
915 &factor.description,
916 "autonomous_pricing_decision.explanation_factors.description",
917 )?;
918 if factor.weight_bps == 0 || factor.weight_bps > 10_000 {
919 return Err(AutonomyContractError::InvalidDecision(
920 "autonomous pricing explanation factor weight_bps must be between 1 and 10000"
921 .to_string(),
922 ));
923 }
924 if !factor_codes.insert(factor.code.as_str()) {
925 return Err(AutonomyContractError::DuplicateValue(factor.code.clone()));
926 }
927 }
928 if decision.authority_envelope.automation_mode == AutonomousAutomationMode::Shadow
929 && decision.review_state != AutonomousDecisionReviewState::ShadowOnly
930 {
931 return Err(AutonomyContractError::InvalidDecision(
932 "shadow automation decisions must use review_state shadow_only".to_string(),
933 ));
934 }
935 if decision.review_state == AutonomousDecisionReviewState::ShadowOnly
936 && decision.authority_envelope.automation_mode != AutonomousAutomationMode::Shadow
937 {
938 return Err(AutonomyContractError::InvalidDecision(
939 "review_state shadow_only requires a shadow automation envelope".to_string(),
940 ));
941 }
942 if decision.disposition == AutonomousPricingDisposition::ManualReview
943 && decision.review_state == AutonomousDecisionReviewState::AutoApproved
944 {
945 return Err(AutonomyContractError::InvalidDecision(
946 "manual-review pricing decisions cannot be auto approved".to_string(),
947 ));
948 }
949 if let Some(threshold) = decision
950 .authority_envelope
951 .requires_human_review_above_premium
952 .as_ref()
953 {
954 if decision.review_state == AutonomousDecisionReviewState::AutoApproved
955 && decision.suggested_premium_amount.units > threshold.units
956 {
957 return Err(AutonomyContractError::InvalidDecision(
958 "auto-approved pricing decisions cannot exceed the premium review threshold"
959 .to_string(),
960 ));
961 }
962 }
963 if decision.disposition == AutonomousPricingDisposition::BindWithinEnvelope {
964 if !decision
965 .authority_envelope
966 .permitted_actions
967 .contains(&AutonomousPricingAction::Bind)
968 {
969 return Err(AutonomyContractError::InvalidDecision(
970 "bind-within-envelope decisions require bind permission in the authority envelope"
971 .to_string(),
972 ));
973 }
974 if !decision
975 .authority_envelope
976 .support_boundary
977 .live_bind_supported
978 {
979 return Err(AutonomyContractError::InvalidDecision(
980 "bind-within-envelope decisions require live_bind_supported".to_string(),
981 ));
982 }
983 if decision.authority_envelope.requires_human_review_for_bind
984 && decision.review_state == AutonomousDecisionReviewState::AutoApproved
985 {
986 return Err(AutonomyContractError::InvalidDecision(
987 "bind-within-envelope decisions cannot be auto approved when human review is required for bind"
988 .to_string(),
989 ));
990 }
991 }
992 Ok(())
993}
994
995pub fn validate_capital_pool_optimization(
996 optimization: &CapitalPoolOptimizationArtifact,
997) -> Result<(), AutonomyContractError> {
998 if optimization.schema != CHIO_CAPITAL_POOL_OPTIMIZATION_SCHEMA {
999 return Err(AutonomyContractError::UnsupportedSchema(
1000 optimization.schema.clone(),
1001 ));
1002 }
1003 ensure_non_empty(
1004 &optimization.optimization_id,
1005 "capital_pool_optimization.optimization_id",
1006 )?;
1007 ensure_non_empty(
1008 &optimization.subject_key,
1009 "capital_pool_optimization.subject_key",
1010 )?;
1011 validate_currency_code(&optimization.currency, "capital_pool_optimization.currency")?;
1012 ensure_non_empty(
1013 &optimization.pricing_decision_ref,
1014 "capital_pool_optimization.pricing_decision_ref",
1015 )?;
1016 ensure_non_empty(
1017 &optimization.capital_book_ref,
1018 "capital_pool_optimization.capital_book_ref",
1019 )?;
1020 if optimization.facility_refs.is_empty() {
1021 return Err(AutonomyContractError::MissingField(
1022 "capital_pool_optimization.facility_refs",
1023 ));
1024 }
1025 ensure_unique_strings(
1026 &optimization.facility_refs,
1027 "capital_pool_optimization.facility_refs",
1028 )?;
1029 ensure_unique_strings(
1030 &optimization.pending_claim_refs,
1031 "capital_pool_optimization.pending_claim_refs",
1032 )?;
1033 if optimization.target_reserve_ratio_bps == 0 || optimization.target_reserve_ratio_bps > 10_000
1034 {
1035 return Err(AutonomyContractError::InvalidOptimization(
1036 "capital pool optimization target_reserve_ratio_bps must be between 1 and 10000"
1037 .to_string(),
1038 ));
1039 }
1040 if optimization.max_facility_utilization_bps == 0
1041 || optimization.max_facility_utilization_bps > 10_000
1042 {
1043 return Err(AutonomyContractError::InvalidOptimization(
1044 "capital pool optimization max_facility_utilization_bps must be between 1 and 10000"
1045 .to_string(),
1046 ));
1047 }
1048 if optimization.max_bind_capacity_units == 0 {
1049 return Err(AutonomyContractError::InvalidOptimization(
1050 "capital pool optimization max_bind_capacity_units must be non-zero".to_string(),
1051 ));
1052 }
1053 if optimization.recommendations.is_empty() {
1054 return Err(AutonomyContractError::MissingField(
1055 "capital_pool_optimization.recommendations",
1056 ));
1057 }
1058 for recommendation in &optimization.recommendations {
1059 ensure_non_empty(
1060 &recommendation.source_ref,
1061 "capital_pool_optimization.recommendations.source_ref",
1062 )?;
1063 ensure_non_empty(
1064 &recommendation.rationale,
1065 "capital_pool_optimization.recommendations.rationale",
1066 )?;
1067 validate_positive_money(
1068 &recommendation.amount,
1069 "capital_pool_optimization.recommendations.amount",
1070 )?;
1071 if recommendation.amount.currency.trim().to_ascii_uppercase() != optimization.currency {
1072 return Err(AutonomyContractError::InvalidOptimization(
1073 "capital pool optimization recommendation amounts must match optimization currency"
1074 .to_string(),
1075 ));
1076 }
1077 if recommendation.action == CapitalOptimizationAction::ShiftCapacity
1078 && recommendation
1079 .destination_ref
1080 .as_deref()
1081 .is_none_or(|value| value.trim().is_empty())
1082 {
1083 return Err(AutonomyContractError::InvalidOptimization(
1084 "shift-capacity recommendations require destination_ref".to_string(),
1085 ));
1086 }
1087 }
1088 Ok(())
1089}
1090
1091pub fn validate_capital_pool_simulation_report(
1092 report: &CapitalPoolSimulationReport,
1093) -> Result<(), AutonomyContractError> {
1094 if report.schema != CHIO_CAPITAL_POOL_SIMULATION_REPORT_SCHEMA {
1095 return Err(AutonomyContractError::UnsupportedSchema(
1096 report.schema.clone(),
1097 ));
1098 }
1099 ensure_non_empty(
1100 &report.simulation_id,
1101 "capital_pool_simulation.simulation_id",
1102 )?;
1103 ensure_non_empty(&report.subject_key, "capital_pool_simulation.subject_key")?;
1104 validate_currency_code(&report.currency, "capital_pool_simulation.currency")?;
1105 validate_capital_pool_optimization(&report.baseline_optimization)?;
1106 validate_capital_pool_optimization(&report.candidate_optimization)?;
1107 if report.baseline_optimization.subject_key != report.subject_key
1108 || report.candidate_optimization.subject_key != report.subject_key
1109 {
1110 return Err(AutonomyContractError::InvalidOptimization(
1111 "capital pool simulation subject_key must match both baseline and candidate optimizations"
1112 .to_string(),
1113 ));
1114 }
1115 if report.baseline_optimization.currency != report.currency
1116 || report.candidate_optimization.currency != report.currency
1117 {
1118 return Err(AutonomyContractError::InvalidOptimization(
1119 "capital pool simulation currency must match both baseline and candidate optimizations"
1120 .to_string(),
1121 ));
1122 }
1123 if report.baseline_optimization.optimization_id == report.candidate_optimization.optimization_id
1124 {
1125 return Err(AutonomyContractError::DuplicateValue(
1126 report.baseline_optimization.optimization_id.clone(),
1127 ));
1128 }
1129 if !report
1130 .baseline_optimization
1131 .support_boundary
1132 .scenario_comparison_supported
1133 || !report
1134 .candidate_optimization
1135 .support_boundary
1136 .scenario_comparison_supported
1137 {
1138 return Err(AutonomyContractError::InvalidOptimization(
1139 "capital pool simulation requires scenario_comparison_supported on both optimizations"
1140 .to_string(),
1141 ));
1142 }
1143 if report.deltas.is_empty() {
1144 return Err(AutonomyContractError::MissingField(
1145 "capital_pool_simulation.deltas",
1146 ));
1147 }
1148 for delta in &report.deltas {
1149 ensure_non_empty(
1150 &delta.metric_name,
1151 "capital_pool_simulation.deltas.metric_name",
1152 )?;
1153 ensure_non_empty(
1154 &delta.description,
1155 "capital_pool_simulation.deltas.description",
1156 )?;
1157 }
1158 ensure_non_empty(
1159 &report.recommended_operator_action,
1160 "capital_pool_simulation.recommended_operator_action",
1161 )?;
1162 Ok(())
1163}
1164
1165pub fn validate_autonomous_execution_decision(
1166 decision: &AutonomousExecutionDecisionArtifact,
1167) -> Result<(), AutonomyContractError> {
1168 if decision.schema != CHIO_AUTONOMOUS_EXECUTION_DECISION_SCHEMA {
1169 return Err(AutonomyContractError::UnsupportedSchema(
1170 decision.schema.clone(),
1171 ));
1172 }
1173 for (value, field) in [
1174 (&decision.execution_id, "autonomous_execution.execution_id"),
1175 (
1176 &decision.pricing_decision_ref,
1177 "autonomous_execution.pricing_decision_ref",
1178 ),
1179 (
1180 &decision.optimization_ref,
1181 "autonomous_execution.optimization_ref",
1182 ),
1183 (
1184 &decision.authority_envelope_ref,
1185 "autonomous_execution.authority_envelope_ref",
1186 ),
1187 (&decision.subject_key, "autonomous_execution.subject_key"),
1188 (&decision.provider_id, "autonomous_execution.provider_id"),
1189 ] {
1190 ensure_non_empty(value, field)?;
1191 }
1192 validate_currency_code(&decision.currency, "autonomous_execution.currency")?;
1193 if decision.safety_gates.is_empty() {
1194 return Err(AutonomyContractError::MissingField(
1195 "autonomous_execution.safety_gates",
1196 ));
1197 }
1198 let mut gate_names = HashSet::new();
1199 for gate in &decision.safety_gates {
1200 ensure_non_empty(&gate.name, "autonomous_execution.safety_gates.name")?;
1201 ensure_non_empty(
1202 &gate.description,
1203 "autonomous_execution.safety_gates.description",
1204 )?;
1205 if !gate_names.insert(gate.name.as_str()) {
1206 return Err(AutonomyContractError::DuplicateValue(gate.name.clone()));
1207 }
1208 }
1209 validate_rollback_control(&decision.rollback_control)?;
1210
1211 let all_gates_passed = decision.safety_gates.iter().all(|gate| gate.passed);
1212 if decision.lifecycle_state == AutonomousExecutionLifecycleState::Executed && !all_gates_passed
1213 {
1214 return Err(AutonomyContractError::InvalidExecution(
1215 "executed autonomous actions require all safety gates to pass".to_string(),
1216 ));
1217 }
1218 if decision.lifecycle_state == AutonomousExecutionLifecycleState::Blocked && all_gates_passed {
1219 return Err(AutonomyContractError::InvalidExecution(
1220 "blocked autonomous actions require at least one failed safety gate".to_string(),
1221 ));
1222 }
1223
1224 match decision.action {
1225 AutonomousExecutionAction::Reprice | AutonomousExecutionAction::Renew => {
1226 ensure_non_empty(
1227 decision.quote_response_ref.as_deref().unwrap_or_default(),
1228 "autonomous_execution.quote_response_ref",
1229 )?;
1230 if decision.auto_bind_decision_ref.is_some()
1231 || decision.bound_coverage_ref.is_some()
1232 || decision.settlement_dispatch_ref.is_some()
1233 {
1234 return Err(AutonomyContractError::InvalidExecution(
1235 "reprice and renew automation cannot embed bind or settlement references"
1236 .to_string(),
1237 ));
1238 }
1239 }
1240 AutonomousExecutionAction::Decline => {
1241 if decision.auto_bind_decision_ref.is_some()
1242 || decision.bound_coverage_ref.is_some()
1243 || decision.settlement_dispatch_ref.is_some()
1244 {
1245 return Err(AutonomyContractError::InvalidExecution(
1246 "decline automation cannot embed bind or settlement references".to_string(),
1247 ));
1248 }
1249 }
1250 AutonomousExecutionAction::Bind => {
1251 ensure_non_empty(
1252 decision.quote_response_ref.as_deref().unwrap_or_default(),
1253 "autonomous_execution.quote_response_ref",
1254 )?;
1255 ensure_non_empty(
1256 decision
1257 .auto_bind_decision_ref
1258 .as_deref()
1259 .unwrap_or_default(),
1260 "autonomous_execution.auto_bind_decision_ref",
1261 )?;
1262 ensure_non_empty(
1263 decision.bound_coverage_ref.as_deref().unwrap_or_default(),
1264 "autonomous_execution.bound_coverage_ref",
1265 )?;
1266 if decision.lifecycle_state == AutonomousExecutionLifecycleState::Executed {
1267 ensure_non_empty(
1268 decision
1269 .settlement_dispatch_ref
1270 .as_deref()
1271 .unwrap_or_default(),
1272 "autonomous_execution.settlement_dispatch_ref",
1273 )?;
1274 }
1275 }
1276 }
1277
1278 Ok(())
1279}
1280
1281pub fn validate_autonomous_comparison_report(
1282 report: &AutonomousComparisonReport,
1283) -> Result<(), AutonomyContractError> {
1284 if report.schema != CHIO_AUTONOMOUS_COMPARISON_REPORT_SCHEMA {
1285 return Err(AutonomyContractError::UnsupportedSchema(
1286 report.schema.clone(),
1287 ));
1288 }
1289 ensure_non_empty(&report.comparison_id, "autonomous_comparison.comparison_id")?;
1290 ensure_non_empty(
1291 &report.pricing_decision_ref,
1292 "autonomous_comparison.pricing_decision_ref",
1293 )?;
1294 ensure_non_empty(
1295 &report.manual_decision_ref,
1296 "autonomous_comparison.manual_decision_ref",
1297 )?;
1298 if report.disposition != AutonomousComparisonDisposition::Match && report.deltas.is_empty() {
1299 return Err(AutonomyContractError::MissingField(
1300 "autonomous_comparison.deltas",
1301 ));
1302 }
1303 if report.disposition == AutonomousComparisonDisposition::ManualOverride {
1304 ensure_non_empty(
1305 report.override_reference.as_deref().unwrap_or_default(),
1306 "autonomous_comparison.override_reference",
1307 )?;
1308 }
1309 for delta in &report.deltas {
1310 for (value, field) in [
1311 (&delta.field, "autonomous_comparison.deltas.field"),
1312 (
1313 &delta.automated_value,
1314 "autonomous_comparison.deltas.automated_value",
1315 ),
1316 (
1317 &delta.manual_value,
1318 "autonomous_comparison.deltas.manual_value",
1319 ),
1320 (
1321 &delta.description,
1322 "autonomous_comparison.deltas.description",
1323 ),
1324 ] {
1325 ensure_non_empty(value, field)?;
1326 }
1327 }
1328 Ok(())
1329}
1330
1331pub fn validate_autonomous_drift_report(
1332 report: &AutonomousDriftReport,
1333) -> Result<(), AutonomyContractError> {
1334 if report.schema != CHIO_AUTONOMOUS_DRIFT_REPORT_SCHEMA {
1335 return Err(AutonomyContractError::UnsupportedSchema(
1336 report.schema.clone(),
1337 ));
1338 }
1339 ensure_non_empty(&report.drift_report_id, "autonomous_drift.drift_report_id")?;
1340 ensure_non_empty(&report.subject_key, "autonomous_drift.subject_key")?;
1341 ensure_non_empty(
1342 &report.pricing_decision_ref,
1343 "autonomous_drift.pricing_decision_ref",
1344 )?;
1345 ensure_non_empty(
1346 &report.optimization_ref,
1347 "autonomous_drift.optimization_ref",
1348 )?;
1349 validate_autonomous_rollback_plan(&report.rollback_plan)?;
1350 validate_autonomous_comparison_report(&report.comparison_report)?;
1351 if report.drift_signals.is_empty() {
1352 return Err(AutonomyContractError::MissingField(
1353 "autonomous_drift.drift_signals",
1354 ));
1355 }
1356 let mut critical_kinds = HashSet::new();
1357 for signal in &report.drift_signals {
1358 ensure_non_empty(&signal.metric_name, "autonomous_drift.metric_name")?;
1359 ensure_non_empty(&signal.description, "autonomous_drift.description")?;
1360 if signal.threshold_value == 0 {
1361 return Err(AutonomyContractError::InvalidDrift(
1362 "autonomous drift threshold_value must be non-zero".to_string(),
1363 ));
1364 }
1365 if signal.severity == AutonomousDriftSeverity::Critical {
1366 critical_kinds.insert(signal.kind);
1367 }
1368 }
1369 if !critical_kinds.is_empty() && !report.fail_safe_engaged {
1370 return Err(AutonomyContractError::InvalidDrift(
1371 "critical drift signals require fail_safe_engaged".to_string(),
1372 ));
1373 }
1374 for kind in critical_kinds {
1375 if !report.rollback_plan.triggers.contains(&kind) {
1376 return Err(AutonomyContractError::InvalidDrift(format!(
1377 "rollback plan does not cover critical drift trigger {:?}",
1378 kind
1379 )));
1380 }
1381 }
1382 Ok(())
1383}
1384
1385pub fn validate_autonomous_rollback_plan(
1386 plan: &AutonomousRollbackPlanArtifact,
1387) -> Result<(), AutonomyContractError> {
1388 if plan.schema != CHIO_AUTONOMOUS_ROLLBACK_PLAN_SCHEMA {
1389 return Err(AutonomyContractError::UnsupportedSchema(
1390 plan.schema.clone(),
1391 ));
1392 }
1393 ensure_non_empty(&plan.plan_id, "autonomous_rollback.plan_id")?;
1394 ensure_non_empty(&plan.subject_key, "autonomous_rollback.subject_key")?;
1395 if plan.triggers.is_empty() {
1396 return Err(AutonomyContractError::MissingField(
1397 "autonomous_rollback.triggers",
1398 ));
1399 }
1400 if plan.actions.is_empty() {
1401 return Err(AutonomyContractError::MissingField(
1402 "autonomous_rollback.actions",
1403 ));
1404 }
1405 ensure_unique_copy_values(&plan.triggers, "autonomous_rollback.triggers")?;
1406 ensure_unique_copy_values(&plan.actions, "autonomous_rollback.actions")?;
1407 Ok(())
1408}
1409
1410pub fn validate_autonomous_qualification_matrix(
1411 matrix: &AutonomousQualificationMatrix,
1412) -> Result<(), AutonomyContractError> {
1413 if matrix.schema != CHIO_AUTONOMOUS_QUALIFICATION_MATRIX_SCHEMA {
1414 return Err(AutonomyContractError::UnsupportedSchema(
1415 matrix.schema.clone(),
1416 ));
1417 }
1418 ensure_non_empty(&matrix.profile_id, "autonomous_qualification.profile_id")?;
1419 if matrix.cases.is_empty() {
1420 return Err(AutonomyContractError::MissingField(
1421 "autonomous_qualification.cases",
1422 ));
1423 }
1424 let mut seen_ids = HashSet::new();
1425 for case in &matrix.cases {
1426 ensure_non_empty(&case.id, "autonomous_qualification.cases.id")?;
1427 ensure_non_empty(&case.name, "autonomous_qualification.cases.name")?;
1428 ensure_non_empty(&case.notes, "autonomous_qualification.cases.notes")?;
1429 if case.requirement_ids.is_empty() {
1430 return Err(AutonomyContractError::InvalidQualificationCase(format!(
1431 "qualification case `{}` requires at least one requirement id",
1432 case.id
1433 )));
1434 }
1435 ensure_unique_strings(
1436 &case.requirement_ids,
1437 "autonomous_qualification.cases.requirement_ids",
1438 )?;
1439 if !seen_ids.insert(case.id.as_str()) {
1440 return Err(AutonomyContractError::DuplicateValue(case.id.clone()));
1441 }
1442 }
1443 Ok(())
1444}
1445
1446fn validate_model_provenance(
1447 model: &AutonomousModelProvenance,
1448) -> Result<(), AutonomyContractError> {
1449 for (value, field) in [
1450 (&model.model_id, "autonomous_model.model_id"),
1451 (&model.model_version, "autonomous_model.model_version"),
1452 (&model.engine_family, "autonomous_model.engine_family"),
1453 (&model.input_hash, "autonomous_model.input_hash"),
1454 (
1455 &model.explanation_version,
1456 "autonomous_model.explanation_version",
1457 ),
1458 ] {
1459 ensure_non_empty(value, field)?;
1460 }
1461 if model.training_cutoff > model.published_at {
1462 return Err(AutonomyContractError::InvalidDecision(
1463 "autonomous model training_cutoff must be <= published_at".to_string(),
1464 ));
1465 }
1466 Ok(())
1467}
1468
1469fn validate_rollback_control(
1470 control: &AutonomousExecutionRollbackControl,
1471) -> Result<(), AutonomyContractError> {
1472 ensure_non_empty(
1473 &control.rollback_plan_ref,
1474 "autonomous_execution.rollback_control.rollback_plan_ref",
1475 )?;
1476 ensure_non_empty(
1477 &control.human_interrupt_contact,
1478 "autonomous_execution.rollback_control.human_interrupt_contact",
1479 )?;
1480 Ok(())
1481}
1482
1483fn validate_positive_money(
1484 amount: &MonetaryAmount,
1485 field: &'static str,
1486) -> Result<(), AutonomyContractError> {
1487 if amount.units == 0 {
1488 return Err(AutonomyContractError::InvalidDecision(format!(
1489 "{field} must be greater than zero"
1490 )));
1491 }
1492 validate_currency_code(&amount.currency, field)
1493}
1494
1495fn validate_currency_code(
1496 currency: &str,
1497 field: &'static str,
1498) -> Result<(), AutonomyContractError> {
1499 let normalized = currency.trim().to_ascii_uppercase();
1500 if normalized.len() != 3
1501 || !normalized
1502 .chars()
1503 .all(|character| character.is_ascii_uppercase())
1504 {
1505 return Err(AutonomyContractError::InvalidDecision(format!(
1506 "{field} must be a 3-letter uppercase currency code"
1507 )));
1508 }
1509 Ok(())
1510}
1511
1512fn ensure_non_empty(value: &str, field: &'static str) -> Result<(), AutonomyContractError> {
1513 if value.trim().is_empty() {
1514 return Err(AutonomyContractError::MissingField(field));
1515 }
1516 Ok(())
1517}
1518
1519fn ensure_unique_strings(
1520 values: &[String],
1521 field: &'static str,
1522) -> Result<(), AutonomyContractError> {
1523 let mut seen = HashSet::new();
1524 for value in values {
1525 if !seen.insert(value.as_str()) {
1526 return Err(AutonomyContractError::DuplicateValue(format!(
1527 "{field}:{value}"
1528 )));
1529 }
1530 }
1531 Ok(())
1532}
1533
1534fn ensure_unique_copy_values<T>(
1535 values: &[T],
1536 field: &'static str,
1537) -> Result<(), AutonomyContractError>
1538where
1539 T: Copy + Eq + std::hash::Hash + std::fmt::Debug,
1540{
1541 let mut seen = HashSet::new();
1542 for value in values {
1543 if !seen.insert(*value) {
1544 return Err(AutonomyContractError::DuplicateValue(format!(
1545 "{field}:{value:?}"
1546 )));
1547 }
1548 }
1549 Ok(())
1550}
1551
1552#[cfg(test)]
1553mod tests {
1554 use super::*;
1555
1556 fn sample_input() -> AutonomousPricingInputArtifact {
1557 AutonomousPricingInputArtifact {
1558 schema: CHIO_AUTONOMOUS_PRICING_INPUT_SCHEMA.to_string(),
1559 input_id: "api-1".to_string(),
1560 generated_at: 1_743_379_200,
1561 subject_key: "subject-1".to_string(),
1562 provider_id: "carrier-1".to_string(),
1563 coverage_class: LiabilityCoverageClass::ProfessionalLiability,
1564 currency: "USD".to_string(),
1565 requested_coverage_amount: MonetaryAmount {
1566 units: 120_000,
1567 currency: "USD".to_string(),
1568 },
1569 receipt_history_window_secs: 2_592_000,
1570 reputation_score_bps: 8_200,
1571 runtime_assurance_tier: RuntimeAssuranceTier::Verified,
1572 pending_loss_units: 0,
1573 settled_loss_units: 2_500,
1574 available_capital_units: 600_000,
1575 latest_web3_settlement_state: Some(Web3SettlementLifecycleState::Settled),
1576 evidence_refs: vec![
1577 AutonomousEvidenceReference {
1578 kind: AutonomousEvidenceKind::UnderwritingDecision,
1579 reference_id: "uwd-1".to_string(),
1580 observed_at: Some(1_743_379_100),
1581 locator: Some("underwriting:uwd-1".to_string()),
1582 },
1583 AutonomousEvidenceReference {
1584 kind: AutonomousEvidenceKind::ExposureLedger,
1585 reference_id: "eld-1".to_string(),
1586 observed_at: Some(1_743_379_100),
1587 locator: Some("ledger:eld-1".to_string()),
1588 },
1589 AutonomousEvidenceReference {
1590 kind: AutonomousEvidenceKind::CreditScorecard,
1591 reference_id: "score-1".to_string(),
1592 observed_at: Some(1_743_379_050),
1593 locator: Some("scorecard:score-1".to_string()),
1594 },
1595 AutonomousEvidenceReference {
1596 kind: AutonomousEvidenceKind::CapitalBook,
1597 reference_id: "cb-1".to_string(),
1598 observed_at: Some(1_743_379_150),
1599 locator: Some("capital-book:cb-1".to_string()),
1600 },
1601 AutonomousEvidenceReference {
1602 kind: AutonomousEvidenceKind::CreditLossLifecycle,
1603 reference_id: "loss-1".to_string(),
1604 observed_at: Some(1_743_378_900),
1605 locator: Some("loss:loss-1".to_string()),
1606 },
1607 AutonomousEvidenceReference {
1608 kind: AutonomousEvidenceKind::Web3SettlementReceipt,
1609 reference_id: "receipt-web3-1".to_string(),
1610 observed_at: Some(1_743_379_000),
1611 locator: Some("web3-settlement:receipt-web3-1".to_string()),
1612 },
1613 ],
1614 support_boundary: AutonomousPricingSupportBoundary::default(),
1615 note: Some(
1616 "Feeds one bounded autonomous pricing decision over Chio truth.".to_string(),
1617 ),
1618 }
1619 }
1620
1621 fn sample_authority_envelope() -> AutonomousPricingAuthorityEnvelopeArtifact {
1622 AutonomousPricingAuthorityEnvelopeArtifact {
1623 schema: CHIO_AUTONOMOUS_PRICING_AUTHORITY_ENVELOPE_SCHEMA.to_string(),
1624 envelope_id: "ape-1".to_string(),
1625 issued_at: 1_743_379_200,
1626 subject_key: "subject-1".to_string(),
1627 provider_id: "carrier-1".to_string(),
1628 currency: "USD".to_string(),
1629 kind: AutonomousAuthorityEnvelopeKind::DelegatedMarketAuthority,
1630 automation_mode: AutonomousAutomationMode::Active,
1631 permitted_actions: vec![
1632 AutonomousPricingAction::Reprice,
1633 AutonomousPricingAction::Renew,
1634 AutonomousPricingAction::Decline,
1635 AutonomousPricingAction::Bind,
1636 ],
1637 authority_chain_refs: vec![
1638 "underwriting-committee-approval".to_string(),
1639 "operator-treasury-approval".to_string(),
1640 ],
1641 max_coverage_amount: MonetaryAmount {
1642 units: 150_000,
1643 currency: "USD".to_string(),
1644 },
1645 max_premium_amount: MonetaryAmount {
1646 units: 6_000,
1647 currency: "USD".to_string(),
1648 },
1649 max_rate_change_bps: 750,
1650 max_daily_decisions: 20,
1651 requires_human_review_for_bind: false,
1652 requires_human_review_above_premium: Some(MonetaryAmount {
1653 units: 5_000,
1654 currency: "USD".to_string(),
1655 }),
1656 regulated_role: None,
1657 delegated_authority_reference: Some("lpa-1".to_string()),
1658 not_before: 1_743_379_200,
1659 not_after: 1_743_465_600,
1660 support_boundary: AutonomousPricingSupportBoundary::default(),
1661 note: Some("Binds automation to one delegated pricing authority envelope.".to_string()),
1662 }
1663 }
1664
1665 fn sample_decision() -> AutonomousPricingDecisionArtifact {
1666 AutonomousPricingDecisionArtifact {
1667 schema: CHIO_AUTONOMOUS_PRICING_DECISION_SCHEMA.to_string(),
1668 decision_id: "apd-1".to_string(),
1669 issued_at: 1_743_379_260,
1670 pricing_input: sample_input(),
1671 model: AutonomousModelProvenance {
1672 model_id: "pricing-model-chio-1".to_string(),
1673 model_version: "2026.03.31".to_string(),
1674 engine_family: "gradient_boosted_policy".to_string(),
1675 published_at: 1_743_379_000,
1676 training_cutoff: 1_743_292_800,
1677 input_hash: "4e4efc0ad4f8c80ad4c76f2f3ae2122e9b6cf407cdb2d43516c8f8e4dfd2c1df"
1678 .to_string(),
1679 explanation_version: "counterfactual-v1".to_string(),
1680 supports_counterfactuals: true,
1681 supports_shadow_evaluation: true,
1682 },
1683 authority_envelope: sample_authority_envelope(),
1684 disposition: AutonomousPricingDisposition::BindWithinEnvelope,
1685 review_state: AutonomousDecisionReviewState::AutoApproved,
1686 suggested_coverage_amount: MonetaryAmount {
1687 units: 110_000,
1688 currency: "USD".to_string(),
1689 },
1690 suggested_premium_amount: MonetaryAmount {
1691 units: 4_800,
1692 currency: "USD".to_string(),
1693 },
1694 suggested_ceiling_factor_bps: Some(9_000),
1695 confidence_bps: 8_700,
1696 explanation_factors: vec![
1697 AutonomousPricingExplanationFactor {
1698 code: "strong-runtime-assurance".to_string(),
1699 description: "Verified runtime assurance supports automated bind posture."
1700 .to_string(),
1701 direction: AutonomousPricingExplanationDirection::Decrease,
1702 weight_bps: 2_500,
1703 evidence_refs: vec![AutonomousEvidenceReference {
1704 kind: AutonomousEvidenceKind::RuntimeAssuranceAppraisal,
1705 reference_id: "raa-1".to_string(),
1706 observed_at: Some(1_743_379_000),
1707 locator: Some("appraisal:raa-1".to_string()),
1708 }],
1709 },
1710 AutonomousPricingExplanationFactor {
1711 code: "settled-web3-history".to_string(),
1712 description:
1713 "Recent settled web3 history reduces uncertainty for automated renewal and bind."
1714 .to_string(),
1715 direction: AutonomousPricingExplanationDirection::Decrease,
1716 weight_bps: 2_000,
1717 evidence_refs: vec![AutonomousEvidenceReference {
1718 kind: AutonomousEvidenceKind::Web3SettlementReceipt,
1719 reference_id: "receipt-web3-1".to_string(),
1720 observed_at: Some(1_743_379_000),
1721 locator: Some("web3-settlement:receipt-web3-1".to_string()),
1722 }],
1723 },
1724 ],
1725 comparison_baseline_ref: Some("uwd-1".to_string()),
1726 note: Some("Auto-approves one renewal/bind decision inside the active envelope.".to_string()),
1727 }
1728 }
1729
1730 fn sample_optimization() -> CapitalPoolOptimizationArtifact {
1731 CapitalPoolOptimizationArtifact {
1732 schema: CHIO_CAPITAL_POOL_OPTIMIZATION_SCHEMA.to_string(),
1733 optimization_id: "cpo-1".to_string(),
1734 issued_at: 1_743_379_320,
1735 subject_key: "subject-1".to_string(),
1736 currency: "USD".to_string(),
1737 pricing_decision_ref: "apd-1".to_string(),
1738 capital_book_ref: "cb-1".to_string(),
1739 facility_refs: vec!["facility-1".to_string(), "facility-2".to_string()],
1740 pending_claim_refs: vec!["claim-1".to_string()],
1741 target_reserve_ratio_bps: 3_500,
1742 max_facility_utilization_bps: 7_000,
1743 max_bind_capacity_units: 250_000,
1744 recommendations: vec![
1745 CapitalPoolRecommendation {
1746 action: CapitalOptimizationAction::IncreaseReserve,
1747 source_ref: "pool:primary".to_string(),
1748 destination_ref: None,
1749 amount: MonetaryAmount {
1750 units: 25_000,
1751 currency: "USD".to_string(),
1752 },
1753 rationale: "Raise reserve coverage before new autonomous binds.".to_string(),
1754 },
1755 CapitalPoolRecommendation {
1756 action: CapitalOptimizationAction::ShiftCapacity,
1757 source_ref: "facility-2".to_string(),
1758 destination_ref: Some("facility-1".to_string()),
1759 amount: MonetaryAmount {
1760 units: 15_000,
1761 currency: "USD".to_string(),
1762 },
1763 rationale: "Move capacity toward the lower-loss facility.".to_string(),
1764 },
1765 ],
1766 support_boundary: CapitalPoolOptimizationSupportBoundary::default(),
1767 note: Some("Keeps optimization bounded and override-ready.".to_string()),
1768 }
1769 }
1770
1771 fn sample_simulation() -> CapitalPoolSimulationReport {
1772 let baseline = sample_optimization();
1773 let mut candidate = sample_optimization();
1774 candidate.optimization_id = "cpo-2".to_string();
1775 candidate.target_reserve_ratio_bps = 4_000;
1776 candidate.max_bind_capacity_units = 230_000;
1777 CapitalPoolSimulationReport {
1778 schema: CHIO_CAPITAL_POOL_SIMULATION_REPORT_SCHEMA.to_string(),
1779 simulation_id: "cps-1".to_string(),
1780 generated_at: 1_743_379_380,
1781 subject_key: "subject-1".to_string(),
1782 currency: "USD".to_string(),
1783 baseline_optimization: baseline,
1784 candidate_optimization: candidate,
1785 simulation_mode: CapitalPoolSimulationMode::WhatIf,
1786 deltas: vec![
1787 CapitalPoolSimulationDelta {
1788 metric_name: "reserve_ratio_bps".to_string(),
1789 baseline_units: 3_500,
1790 candidate_units: 4_000,
1791 description: "Candidate scenario raises the reserve floor by 500 bps."
1792 .to_string(),
1793 },
1794 CapitalPoolSimulationDelta {
1795 metric_name: "max_bind_capacity_units".to_string(),
1796 baseline_units: 250_000,
1797 candidate_units: 230_000,
1798 description:
1799 "Candidate scenario trims bind capacity to create more reserve headroom."
1800 .to_string(),
1801 },
1802 ],
1803 recommended_operator_action:
1804 "Adopt the candidate reserve posture for the next renewal cohort.".to_string(),
1805 note: Some(
1806 "Compares baseline and candidate capital strategies without mutating live state."
1807 .to_string(),
1808 ),
1809 }
1810 }
1811
1812 fn sample_execution_decision() -> AutonomousExecutionDecisionArtifact {
1813 AutonomousExecutionDecisionArtifact {
1814 schema: CHIO_AUTONOMOUS_EXECUTION_DECISION_SCHEMA.to_string(),
1815 execution_id: "aed-1".to_string(),
1816 issued_at: 1_743_379_440,
1817 pricing_decision_ref: "apd-1".to_string(),
1818 optimization_ref: "cpo-1".to_string(),
1819 authority_envelope_ref: "ape-1".to_string(),
1820 subject_key: "subject-1".to_string(),
1821 provider_id: "carrier-1".to_string(),
1822 currency: "USD".to_string(),
1823 action: AutonomousExecutionAction::Bind,
1824 lifecycle_state: AutonomousExecutionLifecycleState::Executed,
1825 quote_response_ref: Some("quote-response-1".to_string()),
1826 auto_bind_decision_ref: Some("auto-bind-1".to_string()),
1827 bound_coverage_ref: Some("bound-coverage-1".to_string()),
1828 settlement_dispatch_ref: Some("dispatch-web3-1".to_string()),
1829 safety_gates: vec![
1830 AutonomousExecutionSafetyGate {
1831 name: "authority-within-envelope".to_string(),
1832 passed: true,
1833 description:
1834 "Coverage and premium remain inside the active authority envelope."
1835 .to_string(),
1836 },
1837 AutonomousExecutionSafetyGate {
1838 name: "capital-headroom".to_string(),
1839 passed: true,
1840 description: "Capital-pool optimization preserved minimum reserve headroom."
1841 .to_string(),
1842 },
1843 ],
1844 rollback_control: AutonomousExecutionRollbackControl {
1845 rollback_plan_ref: "arp-1".to_string(),
1846 interruptible: true,
1847 human_interrupt_contact: "ops@chio.example".to_string(),
1848 },
1849 note: Some(
1850 "Executes one bounded autonomous bind over the official web3 lane.".to_string(),
1851 ),
1852 }
1853 }
1854
1855 fn sample_comparison_report() -> AutonomousComparisonReport {
1856 AutonomousComparisonReport {
1857 schema: CHIO_AUTONOMOUS_COMPARISON_REPORT_SCHEMA.to_string(),
1858 comparison_id: "acr-1".to_string(),
1859 generated_at: 1_743_379_500,
1860 pricing_decision_ref: "apd-1".to_string(),
1861 manual_decision_ref: "uwd-manual-1".to_string(),
1862 disposition: AutonomousComparisonDisposition::NarrowerThanManual,
1863 deltas: vec![AutonomousComparisonDelta {
1864 field: "premium_units".to_string(),
1865 automated_value: "4800".to_string(),
1866 manual_value: "5100".to_string(),
1867 description: "Automation priced inside the manual ceiling.".to_string(),
1868 }],
1869 override_reference: None,
1870 note: Some(
1871 "Shows automation staying narrower than the comparable manual decision."
1872 .to_string(),
1873 ),
1874 }
1875 }
1876
1877 fn sample_rollback_plan() -> AutonomousRollbackPlanArtifact {
1878 AutonomousRollbackPlanArtifact {
1879 schema: CHIO_AUTONOMOUS_ROLLBACK_PLAN_SCHEMA.to_string(),
1880 plan_id: "arp-1".to_string(),
1881 issued_at: 1_743_379_560,
1882 subject_key: "subject-1".to_string(),
1883 safe_state: AutonomousSafeState::DelegatedOnly,
1884 triggers: vec![
1885 AutonomousDriftKind::SettlementFailureRate,
1886 AutonomousDriftKind::PremiumVariance,
1887 ],
1888 actions: vec![
1889 AutonomousRollbackAction::SwitchToSafeState,
1890 AutonomousRollbackAction::CancelPendingExecution,
1891 AutonomousRollbackAction::RequireHumanApproval,
1892 ],
1893 requires_operator_ack: true,
1894 note: Some("Falls back to delegated pricing when automation drifts beyond the accepted envelope.".to_string()),
1895 }
1896 }
1897
1898 fn sample_drift_report() -> AutonomousDriftReport {
1899 AutonomousDriftReport {
1900 schema: CHIO_AUTONOMOUS_DRIFT_REPORT_SCHEMA.to_string(),
1901 drift_report_id: "adr-1".to_string(),
1902 generated_at: 1_743_379_620,
1903 subject_key: "subject-1".to_string(),
1904 pricing_decision_ref: "apd-1".to_string(),
1905 optimization_ref: "cpo-1".to_string(),
1906 drift_signals: vec![AutonomousDriftSignal {
1907 kind: AutonomousDriftKind::SettlementFailureRate,
1908 severity: AutonomousDriftSeverity::Critical,
1909 metric_name: "failed_settlement_rate_bps".to_string(),
1910 observed_value: 275,
1911 threshold_value: 100,
1912 description: "Settlement failures exceeded the automation safe-state threshold."
1913 .to_string(),
1914 evidence_refs: vec![AutonomousEvidenceReference {
1915 kind: AutonomousEvidenceKind::Web3SettlementReceipt,
1916 reference_id: "receipt-web3-1".to_string(),
1917 observed_at: Some(1_743_379_000),
1918 locator: Some("web3-settlement:receipt-web3-1".to_string()),
1919 }],
1920 }],
1921 rollback_plan: sample_rollback_plan(),
1922 comparison_report: sample_comparison_report(),
1923 fail_safe_engaged: true,
1924 note: Some(
1925 "Fail-safe engaged after settlement drift breached the critical threshold."
1926 .to_string(),
1927 ),
1928 }
1929 }
1930
1931 #[test]
1932 fn shadow_mode_requires_shadow_review_state() {
1933 let mut decision = sample_decision();
1934 decision.authority_envelope.automation_mode = AutonomousAutomationMode::Shadow;
1935 decision.authority_envelope.permitted_actions = vec![AutonomousPricingAction::Reprice];
1936 decision.disposition = AutonomousPricingDisposition::Reprice;
1937 assert!(matches!(
1938 validate_autonomous_pricing_decision(&decision),
1939 Err(AutonomyContractError::InvalidDecision(_))
1940 ));
1941 }
1942
1943 #[test]
1944 fn capital_pool_simulation_requires_matching_subject() {
1945 let mut report = sample_simulation();
1946 report.candidate_optimization.subject_key = "subject-2".to_string();
1947 assert!(matches!(
1948 validate_capital_pool_simulation_report(&report),
1949 Err(AutonomyContractError::InvalidOptimization(_))
1950 ));
1951 }
1952
1953 #[test]
1954 fn bind_execution_requires_settlement_dispatch_when_executed() {
1955 let mut execution = sample_execution_decision();
1956 execution.settlement_dispatch_ref = None;
1957 assert!(matches!(
1958 validate_autonomous_execution_decision(&execution),
1959 Err(AutonomyContractError::MissingField(_))
1960 ));
1961 }
1962
1963 #[test]
1964 fn critical_drift_requires_fail_safe() {
1965 let mut report = sample_drift_report();
1966 report.fail_safe_engaged = false;
1967 assert!(matches!(
1968 validate_autonomous_drift_report(&report),
1969 Err(AutonomyContractError::InvalidDrift(_))
1970 ));
1971 }
1972
1973 #[test]
1974 fn reference_artifacts_parse_and_validate() {
1975 let envelope: AutonomousPricingAuthorityEnvelopeArtifact = serde_json::from_str(
1976 include_str!("../../../docs/standards/CHIO_AUTONOMOUS_PRICING_AUTHORITY_ENVELOPE.json"),
1977 )
1978 .unwrap();
1979 let decision: AutonomousPricingDecisionArtifact = serde_json::from_str(include_str!(
1980 "../../../docs/standards/CHIO_AUTONOMOUS_PRICING_DECISION_EXAMPLE.json"
1981 ))
1982 .unwrap();
1983 let optimization: CapitalPoolOptimizationArtifact = serde_json::from_str(include_str!(
1984 "../../../docs/standards/CHIO_CAPITAL_POOL_OPTIMIZATION_EXAMPLE.json"
1985 ))
1986 .unwrap();
1987 let simulation: CapitalPoolSimulationReport = serde_json::from_str(include_str!(
1988 "../../../docs/standards/CHIO_CAPITAL_POOL_SIMULATION_EXAMPLE.json"
1989 ))
1990 .unwrap();
1991 let execution: AutonomousExecutionDecisionArtifact = serde_json::from_str(include_str!(
1992 "../../../docs/standards/CHIO_AUTONOMOUS_EXECUTION_EXAMPLE.json"
1993 ))
1994 .unwrap();
1995 let comparison: AutonomousComparisonReport = serde_json::from_str(include_str!(
1996 "../../../docs/standards/CHIO_AUTONOMOUS_COMPARISON_REPORT_EXAMPLE.json"
1997 ))
1998 .unwrap();
1999 let drift: AutonomousDriftReport = serde_json::from_str(include_str!(
2000 "../../../docs/standards/CHIO_AUTONOMOUS_DRIFT_REPORT_EXAMPLE.json"
2001 ))
2002 .unwrap();
2003 let matrix: AutonomousQualificationMatrix = serde_json::from_str(include_str!(
2004 "../../../docs/standards/CHIO_AUTONOMOUS_QUALIFICATION_MATRIX.json"
2005 ))
2006 .unwrap();
2007
2008 validate_autonomous_pricing_authority_envelope(&envelope).unwrap();
2009 validate_autonomous_pricing_decision(&decision).unwrap();
2010 validate_capital_pool_optimization(&optimization).unwrap();
2011 validate_capital_pool_simulation_report(&simulation).unwrap();
2012 validate_autonomous_execution_decision(&execution).unwrap();
2013 validate_autonomous_comparison_report(&comparison).unwrap();
2014 validate_autonomous_drift_report(&drift).unwrap();
2015 validate_autonomous_qualification_matrix(&matrix).unwrap();
2016 }
2017
2018 #[test]
2019 fn pricing_input_requires_capital_book_evidence() {
2020 let mut input = sample_input();
2021 input
2022 .evidence_refs
2023 .retain(|evidence| evidence.kind != AutonomousEvidenceKind::CapitalBook);
2024 assert!(matches!(
2025 validate_autonomous_pricing_input(&input),
2026 Err(AutonomyContractError::UnknownReference(_))
2027 ));
2028 }
2029
2030 #[test]
2031 fn pricing_input_requires_web3_evidence_when_settlement_state_present() {
2032 let mut input = sample_input();
2033 input
2034 .evidence_refs
2035 .retain(|evidence| evidence.kind != AutonomousEvidenceKind::Web3SettlementReceipt);
2036 assert!(matches!(
2037 validate_autonomous_pricing_input(&input),
2038 Err(AutonomyContractError::UnknownReference(_))
2039 ));
2040 }
2041
2042 #[test]
2043 fn delegated_authority_requires_reference() {
2044 let mut envelope = sample_authority_envelope();
2045 envelope.delegated_authority_reference = None;
2046 assert!(matches!(
2047 validate_autonomous_pricing_authority_envelope(&envelope),
2048 Err(AutonomyContractError::MissingField(
2049 "autonomous_authority_envelope.delegated_authority_reference"
2050 ))
2051 ));
2052 }
2053
2054 #[test]
2055 fn non_active_authority_cannot_permit_bind() {
2056 let mut envelope = sample_authority_envelope();
2057 envelope.automation_mode = AutonomousAutomationMode::Advisory;
2058 assert!(matches!(
2059 validate_autonomous_pricing_authority_envelope(&envelope),
2060 Err(AutonomyContractError::InvalidEnvelope(_))
2061 ));
2062 }
2063
2064 #[test]
2065 fn pricing_decision_rejects_duplicate_explanation_codes() {
2066 let mut decision = sample_decision();
2067 decision
2068 .explanation_factors
2069 .push(decision.explanation_factors[0].clone());
2070 assert!(matches!(
2071 validate_autonomous_pricing_decision(&decision),
2072 Err(AutonomyContractError::DuplicateValue(_))
2073 ));
2074 }
2075
2076 #[test]
2077 fn bind_decision_cannot_auto_approve_when_human_review_required() {
2078 let mut decision = sample_decision();
2079 decision.authority_envelope.requires_human_review_for_bind = true;
2080 assert!(matches!(
2081 validate_autonomous_pricing_decision(&decision),
2082 Err(AutonomyContractError::InvalidDecision(_))
2083 ));
2084 }
2085
2086 #[test]
2087 fn optimization_shift_capacity_requires_destination_ref() {
2088 let mut optimization = sample_optimization();
2089 optimization.recommendations[1].destination_ref = None;
2090 assert!(matches!(
2091 validate_capital_pool_optimization(&optimization),
2092 Err(AutonomyContractError::InvalidOptimization(_))
2093 ));
2094 }
2095
2096 #[test]
2097 fn capital_pool_simulation_requires_scenario_comparison_support() {
2098 let mut report = sample_simulation();
2099 report
2100 .candidate_optimization
2101 .support_boundary
2102 .scenario_comparison_supported = false;
2103 assert!(matches!(
2104 validate_capital_pool_simulation_report(&report),
2105 Err(AutonomyContractError::InvalidOptimization(_))
2106 ));
2107 }
2108
2109 #[test]
2110 fn comparison_report_manual_override_requires_reference() {
2111 let mut report = sample_comparison_report();
2112 report.disposition = AutonomousComparisonDisposition::ManualOverride;
2113 report.override_reference = None;
2114 assert!(matches!(
2115 validate_autonomous_comparison_report(&report),
2116 Err(AutonomyContractError::MissingField(
2117 "autonomous_comparison.override_reference"
2118 ))
2119 ));
2120 }
2121
2122 #[test]
2123 fn rollback_plan_rejects_duplicate_triggers() {
2124 let mut plan = sample_rollback_plan();
2125 plan.triggers.push(plan.triggers[0]);
2126 assert!(matches!(
2127 validate_autonomous_rollback_plan(&plan),
2128 Err(AutonomyContractError::DuplicateValue(_))
2129 ));
2130 }
2131
2132 #[test]
2133 fn qualification_matrix_requires_requirement_ids() {
2134 let mut matrix: AutonomousQualificationMatrix = serde_json::from_str(include_str!(
2135 "../../../docs/standards/CHIO_AUTONOMOUS_QUALIFICATION_MATRIX.json"
2136 ))
2137 .unwrap();
2138 matrix.cases[0].requirement_ids.clear();
2139 assert!(matches!(
2140 validate_autonomous_qualification_matrix(&matrix),
2141 Err(AutonomyContractError::InvalidQualificationCase(_))
2142 ));
2143 }
2144
2145 #[test]
2146 fn pricing_decision_rejects_future_training_cutoff() {
2147 let mut decision = sample_decision();
2148 decision.model.training_cutoff = decision.model.published_at + 1;
2149 assert!(matches!(
2150 validate_autonomous_pricing_decision(&decision),
2151 Err(AutonomyContractError::InvalidDecision(_))
2152 ));
2153 }
2154}