1use serde::{Deserialize, Serialize};
2
3use chio_core::appraisal::AttestationVerifierFamily;
4use chio_core::capability::{
5 GovernedCallChainProvenance, MeteredSettlementMode, MonetaryAmount, RuntimeAssuranceTier,
6};
7use chio_core::receipt::{
8 ChioReceipt, Decision, EconomicAuthorizationReceiptMetadata,
9 FinancialBudgetAuthorityReceiptMetadata, GovernedTransactionReceiptMetadata,
10 MeteredUsageEvidenceReceiptMetadata, SettlementStatus, SignedExportEnvelope,
11};
12use chio_core::session::ChioIdentityAssertion;
13use chio_core::{
14 ChioGovernedAuthorizationBinding, ChioPortableClaimCatalog, ChioPortableIdentityBinding,
15};
16use chio_credit::{
17 CreditBondDisposition, CreditBondListReport, CreditFacilityDisposition,
18 CreditFacilityListReport, ExposureLedgerQuery,
19};
20use chio_underwriting::{UnderwritingDecisionListReport, UnderwritingDecisionOutcome};
21
22use crate::cost_attribution::CostAttributionQuery;
23use crate::evidence_export::{
24 EvidenceChildReceiptScope, EvidenceExportQuery, EvidenceLineageReferences,
25};
26use crate::receipt_analytics::{AnalyticsTimeBucket, ReceiptAnalyticsResponse};
27use crate::receipt_query::ReceiptQuery;
28use crate::receipt_store::FederatedEvidenceShareSummary;
29use crate::CostAttributionReport;
30
31pub const MAX_OPERATOR_BUDGET_LIMIT: usize = 200;
33pub const MAX_SHARED_EVIDENCE_LIMIT: usize = 200;
35pub const MAX_SETTLEMENT_BACKLOG_LIMIT: usize = 200;
37pub const MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT: usize = 200;
39pub const MAX_METERED_BILLING_LIMIT: usize = 200;
41pub const MAX_AUTHORIZATION_CONTEXT_LIMIT: usize = 200;
43pub const MAX_ECONOMIC_RECEIPT_LIMIT: usize = 200;
45pub const CHIO_OAUTH_AUTHORIZATION_PROFILE_SCHEMA: &str = "chio.oauth.authorization-profile.v1";
47pub const CHIO_OAUTH_SENDER_CONSTRAINT_SCHEMA: &str = "chio.oauth.sender-constraint.v1";
49pub const CHIO_OAUTH_AUTHORIZATION_CONTEXT_REPORT_SCHEMA: &str =
51 "chio.oauth.authorization-context-report.v1";
52pub const ECONOMIC_COMPLETION_FLOW_SCHEMA: &str = "chio.economic-completion-flow.v1";
54pub const CHIO_OAUTH_AUTHORIZATION_METADATA_SCHEMA: &str = "chio.oauth.authorization-metadata.v1";
56pub const CHIO_OAUTH_AUTHORIZATION_REVIEW_PACK_SCHEMA: &str =
58 "chio.oauth.authorization-review-pack.v1";
59pub const CHIO_OAUTH_AUTHORIZATION_PROFILE_ID: &str = "chio-governed-rar-v1";
61pub const CHIO_OAUTH_AUTHORIZATION_TOOL_DETAIL_TYPE: &str = "chio_governed_tool";
63pub const CHIO_OAUTH_AUTHORIZATION_COMMERCE_DETAIL_TYPE: &str = "chio_governed_commerce";
65pub const CHIO_OAUTH_AUTHORIZATION_METERED_BILLING_DETAIL_TYPE: &str =
67 "chio_governed_metered_billing";
68pub const CHIO_OAUTH_SENDER_BINDING_CAPABILITY_SUBJECT: &str = "capability_subject";
70pub const CHIO_OAUTH_SENDER_PROOF_CHIO_DPOP: &str = "chio_dpop_v1";
72pub const CHIO_OAUTH_SENDER_PROOF_CHIO_MTLS: &str = "chio_mtls_thumbprint_v1";
74pub const CHIO_OAUTH_SENDER_PROOF_CHIO_ATTESTATION: &str = "chio_attestation_binding_v1";
76pub const CHIO_OAUTH_REQUEST_TIME_AUTHORIZATION_DETAILS_PARAMETER: &str = "authorization_details";
78pub const CHIO_OAUTH_REQUEST_TIME_TRANSACTION_CONTEXT_PARAMETER: &str = "chio_transaction_context";
80pub const CHIO_OAUTH_REQUEST_TIME_AUTHORIZATION_DETAILS_CLAIM: &str = "authorization_details";
82pub const CHIO_OAUTH_REQUEST_TIME_TRANSACTION_CONTEXT_CLAIM: &str = "chio_transaction_context";
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "camelCase")]
88pub struct OperatorReportQuery {
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub capability_id: Option<String>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub agent_subject: Option<String>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub tool_server: Option<String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub tool_name: Option<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub since: Option<u64>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub until: Option<u64>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub group_limit: Option<usize>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub time_bucket: Option<AnalyticsTimeBucket>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub attribution_limit: Option<usize>,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub budget_limit: Option<usize>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub settlement_limit: Option<usize>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub metered_limit: Option<usize>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub authorization_limit: Option<usize>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub economic_limit: Option<usize>,
117}
118
119impl Default for OperatorReportQuery {
120 fn default() -> Self {
121 Self {
122 capability_id: None,
123 agent_subject: None,
124 tool_server: None,
125 tool_name: None,
126 since: None,
127 until: None,
128 group_limit: Some(50),
129 time_bucket: Some(AnalyticsTimeBucket::Day),
130 attribution_limit: Some(100),
131 budget_limit: Some(50),
132 settlement_limit: Some(50),
133 metered_limit: Some(50),
134 authorization_limit: Some(50),
135 economic_limit: Some(50),
136 }
137 }
138}
139
140impl OperatorReportQuery {
141 #[must_use]
142 pub fn to_receipt_analytics_query(&self) -> crate::ReceiptAnalyticsQuery {
143 crate::ReceiptAnalyticsQuery {
144 capability_id: self.capability_id.clone(),
145 agent_subject: self.agent_subject.clone(),
146 tool_server: self.tool_server.clone(),
147 tool_name: self.tool_name.clone(),
148 since: self.since,
149 until: self.until,
150 group_limit: self.group_limit,
151 time_bucket: self.time_bucket,
152 }
153 }
154
155 #[must_use]
156 pub fn to_cost_attribution_query(&self) -> CostAttributionQuery {
157 CostAttributionQuery {
158 capability_id: self.capability_id.clone(),
159 agent_subject: self.agent_subject.clone(),
160 tool_server: self.tool_server.clone(),
161 tool_name: self.tool_name.clone(),
162 since: self.since,
163 until: self.until,
164 limit: self.attribution_limit,
165 }
166 }
167
168 #[must_use]
169 pub fn to_evidence_export_query(&self) -> EvidenceExportQuery {
170 EvidenceExportQuery {
171 capability_id: self.capability_id.clone(),
172 agent_subject: self.agent_subject.clone(),
173 since: self.since,
174 until: self.until,
175 tenant: None,
179 }
180 }
181
182 #[must_use]
183 pub fn direct_evidence_export_supported(&self) -> bool {
184 self.tool_server.is_none() && self.tool_name.is_none()
185 }
186
187 #[must_use]
188 pub fn budget_limit_or_default(&self) -> usize {
189 self.budget_limit
190 .unwrap_or(50)
191 .clamp(1, MAX_OPERATOR_BUDGET_LIMIT)
192 }
193
194 #[must_use]
195 pub fn settlement_limit_or_default(&self) -> usize {
196 self.settlement_limit
197 .unwrap_or(50)
198 .clamp(1, MAX_SETTLEMENT_BACKLOG_LIMIT)
199 }
200
201 #[must_use]
202 pub fn metered_limit_or_default(&self) -> usize {
203 self.metered_limit
204 .unwrap_or(50)
205 .clamp(1, MAX_METERED_BILLING_LIMIT)
206 }
207
208 #[must_use]
209 pub fn authorization_limit_or_default(&self) -> usize {
210 self.authorization_limit
211 .unwrap_or(50)
212 .clamp(1, MAX_AUTHORIZATION_CONTEXT_LIMIT)
213 }
214
215 #[must_use]
216 pub fn economic_limit_or_default(&self) -> usize {
217 self.economic_limit
218 .unwrap_or(50)
219 .clamp(1, MAX_ECONOMIC_RECEIPT_LIMIT)
220 }
221
222 #[must_use]
223 pub fn to_shared_evidence_query(&self) -> SharedEvidenceQuery {
224 SharedEvidenceQuery {
225 capability_id: self.capability_id.clone(),
226 agent_subject: self.agent_subject.clone(),
227 tool_server: self.tool_server.clone(),
228 tool_name: self.tool_name.clone(),
229 since: self.since,
230 until: self.until,
231 issuer: None,
232 partner: None,
233 limit: self.group_limit,
234 }
235 }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
240#[serde(rename_all = "camelCase")]
241pub struct BehavioralFeedQuery {
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub capability_id: Option<String>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub agent_subject: Option<String>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
247 pub tool_server: Option<String>,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub tool_name: Option<String>,
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub since: Option<u64>,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub until: Option<u64>,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub receipt_limit: Option<usize>,
256}
257
258impl Default for BehavioralFeedQuery {
259 fn default() -> Self {
260 Self {
261 capability_id: None,
262 agent_subject: None,
263 tool_server: None,
264 tool_name: None,
265 since: None,
266 until: None,
267 receipt_limit: Some(100),
268 }
269 }
270}
271
272impl BehavioralFeedQuery {
273 #[must_use]
274 pub fn receipt_limit_or_default(&self) -> usize {
275 self.receipt_limit
276 .unwrap_or(100)
277 .clamp(1, MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT)
278 }
279
280 #[must_use]
281 pub fn normalized(&self) -> Self {
282 let mut normalized = self.clone();
283 normalized.receipt_limit = Some(self.receipt_limit_or_default());
284 normalized
285 }
286
287 #[must_use]
288 pub fn to_operator_report_query(&self) -> OperatorReportQuery {
289 OperatorReportQuery {
290 capability_id: self.capability_id.clone(),
291 agent_subject: self.agent_subject.clone(),
292 tool_server: self.tool_server.clone(),
293 tool_name: self.tool_name.clone(),
294 since: self.since,
295 until: self.until,
296 ..OperatorReportQuery::default()
297 }
298 }
299
300 #[must_use]
301 pub fn to_receipt_query(&self) -> ReceiptQuery {
302 ReceiptQuery {
303 capability_id: self.capability_id.clone(),
304 tool_server: self.tool_server.clone(),
305 tool_name: self.tool_name.clone(),
306 outcome: None,
307 since: self.since,
308 until: self.until,
309 min_cost: None,
310 max_cost: None,
311 cursor: None,
312 limit: self.receipt_limit_or_default(),
313 agent_subject: self.agent_subject.clone(),
314 tenant_filter: None,
318 }
319 }
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
323#[serde(rename_all = "camelCase")]
324pub struct SharedEvidenceQuery {
325 #[serde(default, skip_serializing_if = "Option::is_none")]
326 pub capability_id: Option<String>,
327 #[serde(default, skip_serializing_if = "Option::is_none")]
328 pub agent_subject: Option<String>,
329 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub tool_server: Option<String>,
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub tool_name: Option<String>,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub since: Option<u64>,
335 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub until: Option<u64>,
337 #[serde(default, skip_serializing_if = "Option::is_none")]
338 pub issuer: Option<String>,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub partner: Option<String>,
341 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub limit: Option<usize>,
343}
344
345impl Default for SharedEvidenceQuery {
346 fn default() -> Self {
347 Self {
348 capability_id: None,
349 agent_subject: None,
350 tool_server: None,
351 tool_name: None,
352 since: None,
353 until: None,
354 issuer: None,
355 partner: None,
356 limit: Some(50),
357 }
358 }
359}
360
361impl SharedEvidenceQuery {
362 #[must_use]
363 pub fn limit_or_default(&self) -> usize {
364 self.limit.unwrap_or(50).clamp(1, MAX_SHARED_EVIDENCE_LIMIT)
365 }
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
369#[serde(rename_all = "camelCase")]
370pub struct BudgetUtilizationSummary {
371 pub matching_grants: u64,
372 pub returned_grants: u64,
373 pub distinct_capabilities: u64,
374 pub distinct_subjects: u64,
375 pub total_invocations: u64,
376 pub total_cost_charged: u64,
377 pub near_limit_count: u64,
378 pub exhausted_count: u64,
379 pub rows_missing_scope: u64,
380 pub rows_missing_lineage: u64,
381 pub truncated: bool,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
385#[serde(rename_all = "camelCase")]
386pub struct BudgetDimensionUsage {
387 pub used: u64,
388 pub limit: u64,
389 pub remaining: u64,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub utilization_rate: Option<f64>,
392 pub near_limit: bool,
393 pub exhausted: bool,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
397#[serde(rename_all = "camelCase")]
398pub struct BudgetDimensionProfile {
399 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub invocations: Option<BudgetDimensionUsage>,
401 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub money: Option<BudgetDimensionUsage>,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
406#[serde(rename_all = "camelCase")]
407pub struct BudgetUtilizationRow {
408 pub capability_id: String,
409 pub grant_index: u32,
410 #[serde(default, skip_serializing_if = "Option::is_none")]
411 pub subject_key: Option<String>,
412 #[serde(default, skip_serializing_if = "Option::is_none")]
413 pub tool_server: Option<String>,
414 #[serde(default, skip_serializing_if = "Option::is_none")]
415 pub tool_name: Option<String>,
416 pub invocation_count: u32,
417 #[serde(default, skip_serializing_if = "Option::is_none")]
418 pub max_invocations: Option<u32>,
419 pub total_cost_charged: u64,
420 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub currency: Option<String>,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub max_total_cost_units: Option<u64>,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub remaining_cost_units: Option<u64>,
426 #[serde(default, skip_serializing_if = "Option::is_none")]
427 pub invocation_utilization_rate: Option<f64>,
428 #[serde(default, skip_serializing_if = "Option::is_none")]
429 pub cost_utilization_rate: Option<f64>,
430 pub near_limit: bool,
431 pub exhausted: bool,
432 pub updated_at: i64,
433 pub scope_resolved: bool,
434 #[serde(default, skip_serializing_if = "Option::is_none")]
435 pub scope_resolution_error: Option<String>,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub dimensions: Option<BudgetDimensionProfile>,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
441#[serde(rename_all = "camelCase")]
442pub struct BudgetUtilizationReport {
443 pub summary: BudgetUtilizationSummary,
444 pub rows: Vec<BudgetUtilizationRow>,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
448#[serde(rename_all = "camelCase")]
449pub struct ComplianceReport {
450 pub matching_receipts: u64,
451 pub evidence_ready_receipts: u64,
452 pub uncheckpointed_receipts: u64,
453 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub checkpoint_coverage_rate: Option<f64>,
455 pub lineage_covered_receipts: u64,
456 pub lineage_gap_receipts: u64,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
458 pub lineage_coverage_rate: Option<f64>,
459 pub pending_settlement_receipts: u64,
460 pub failed_settlement_receipts: u64,
461 pub direct_evidence_export_supported: bool,
462 pub child_receipt_scope: EvidenceChildReceiptScope,
463 pub proofs_complete: bool,
464 pub export_query: EvidenceExportQuery,
465 #[serde(default, skip_serializing_if = "Option::is_none")]
466 pub export_scope_note: Option<String>,
467}
468
469#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
470#[serde(rename_all = "snake_case")]
471pub enum SettlementReconciliationState {
472 Open,
473 Reconciled,
474 Ignored,
475 RetryScheduled,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
479#[serde(rename_all = "camelCase")]
480pub struct SettlementReconciliationSummary {
481 pub matching_receipts: u64,
482 pub returned_receipts: u64,
483 pub pending_receipts: u64,
484 pub failed_receipts: u64,
485 pub actionable_receipts: u64,
486 pub reconciled_receipts: u64,
487 pub truncated: bool,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
491#[serde(rename_all = "camelCase")]
492pub struct SettlementReconciliationRow {
493 pub receipt_id: String,
494 pub timestamp: u64,
495 pub capability_id: String,
496 #[serde(default, skip_serializing_if = "Option::is_none")]
497 pub subject_key: Option<String>,
498 pub tool_server: String,
499 pub tool_name: String,
500 #[serde(default, skip_serializing_if = "Option::is_none")]
501 pub payment_reference: Option<String>,
502 pub settlement_status: SettlementStatus,
503 #[serde(default, skip_serializing_if = "Option::is_none")]
504 pub cost_charged: Option<u64>,
505 #[serde(default, skip_serializing_if = "Option::is_none")]
506 pub currency: Option<String>,
507 #[serde(default, skip_serializing_if = "Option::is_none")]
508 pub budget_authority: Option<FinancialBudgetAuthorityReceiptMetadata>,
509 pub reconciliation_state: SettlementReconciliationState,
510 pub action_required: bool,
511 #[serde(default, skip_serializing_if = "Option::is_none")]
512 pub note: Option<String>,
513 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub updated_at: Option<u64>,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
518#[serde(rename_all = "camelCase")]
519pub struct SettlementReconciliationReport {
520 pub summary: SettlementReconciliationSummary,
521 pub receipts: Vec<SettlementReconciliationRow>,
522}
523
524#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
525#[serde(rename_all = "snake_case")]
526pub enum MeteredBillingReconciliationState {
527 Open,
528 Reconciled,
529 Ignored,
530 RetryScheduled,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
534#[serde(rename_all = "camelCase")]
535pub struct MeteredBillingEvidenceRecord {
536 pub usage_evidence: MeteredUsageEvidenceReceiptMetadata,
537 pub billed_cost: MonetaryAmount,
538 pub recorded_at: u64,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
542#[serde(rename_all = "camelCase")]
543pub struct MeteredBillingReconciliationSummary {
544 pub matching_receipts: u64,
545 pub returned_receipts: u64,
546 pub evidence_attached_receipts: u64,
547 pub missing_evidence_receipts: u64,
548 pub over_quoted_units_receipts: u64,
549 pub over_max_billed_units_receipts: u64,
550 pub over_quoted_cost_receipts: u64,
551 pub financial_mismatch_receipts: u64,
552 pub actionable_receipts: u64,
553 pub reconciled_receipts: u64,
554 pub truncated: bool,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
558#[serde(rename_all = "camelCase")]
559pub struct MeteredBillingReconciliationRow {
560 pub receipt_id: String,
561 pub timestamp: u64,
562 pub capability_id: String,
563 #[serde(default, skip_serializing_if = "Option::is_none")]
564 pub subject_key: Option<String>,
565 pub tool_server: String,
566 pub tool_name: String,
567 pub settlement_mode: MeteredSettlementMode,
568 pub provider: String,
569 pub quote_id: String,
570 pub billing_unit: String,
571 pub quoted_units: u64,
572 pub quoted_cost: MonetaryAmount,
573 #[serde(default, skip_serializing_if = "Option::is_none")]
574 pub max_billed_units: Option<u64>,
575 #[serde(default, skip_serializing_if = "Option::is_none")]
576 pub financial_cost_charged: Option<u64>,
577 #[serde(default, skip_serializing_if = "Option::is_none")]
578 pub financial_currency: Option<String>,
579 #[serde(default, skip_serializing_if = "Option::is_none")]
580 pub budget_authority: Option<FinancialBudgetAuthorityReceiptMetadata>,
581 #[serde(default, skip_serializing_if = "Option::is_none")]
582 pub evidence: Option<MeteredBillingEvidenceRecord>,
583 pub reconciliation_state: MeteredBillingReconciliationState,
584 pub action_required: bool,
585 pub evidence_missing: bool,
586 pub exceeds_quoted_units: bool,
587 pub exceeds_max_billed_units: bool,
588 pub exceeds_quoted_cost: bool,
589 pub financial_mismatch: bool,
590 #[serde(default, skip_serializing_if = "Option::is_none")]
591 pub note: Option<String>,
592 #[serde(default, skip_serializing_if = "Option::is_none")]
593 pub updated_at: Option<u64>,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
597#[serde(rename_all = "camelCase")]
598pub struct MeteredBillingReconciliationReport {
599 pub summary: MeteredBillingReconciliationSummary,
600 pub receipts: Vec<MeteredBillingReconciliationRow>,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
604#[serde(rename_all = "camelCase")]
605pub struct EconomicReceiptProjectionSummary {
606 pub matching_receipts: u64,
607 pub returned_receipts: u64,
608 pub metered_receipts: u64,
609 pub pending_settlement_receipts: u64,
610 pub failed_settlement_receipts: u64,
611 pub settlement_actionable_receipts: u64,
612 pub metering_actionable_receipts: u64,
613 pub metering_evidence_missing_receipts: u64,
614 pub metering_financial_mismatch_receipts: u64,
615 pub truncated: bool,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
619#[serde(rename_all = "camelCase")]
620pub struct EconomicReceiptSettlementProjection {
621 pub settlement_status: SettlementStatus,
622 pub reconciliation_state: SettlementReconciliationState,
623 pub action_required: bool,
624 #[serde(default, skip_serializing_if = "Option::is_none")]
625 pub note: Option<String>,
626 #[serde(default, skip_serializing_if = "Option::is_none")]
627 pub updated_at: Option<u64>,
628}
629
630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
631#[serde(rename_all = "camelCase")]
632pub struct EconomicReceiptMeteringProjection {
633 pub reconciliation_state: MeteredBillingReconciliationState,
634 pub action_required: bool,
635 pub evidence_missing: bool,
636 pub exceeds_quoted_units: bool,
637 pub exceeds_max_billed_units: bool,
638 pub exceeds_quoted_cost: bool,
639 pub financial_mismatch: bool,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub evidence: Option<MeteredBillingEvidenceRecord>,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 pub note: Option<String>,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub updated_at: Option<u64>,
646}
647
648#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
649#[serde(rename_all = "camelCase")]
650pub struct EconomicReceiptProjectionRow {
651 pub receipt_id: String,
652 pub timestamp: u64,
653 pub capability_id: String,
654 #[serde(default, skip_serializing_if = "Option::is_none")]
655 pub subject_key: Option<String>,
656 pub tool_server: String,
657 pub tool_name: String,
658 pub economic_authorization: EconomicAuthorizationReceiptMetadata,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
660 pub budget_authority: Option<FinancialBudgetAuthorityReceiptMetadata>,
661 pub settlement: EconomicReceiptSettlementProjection,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
663 pub metering: Option<EconomicReceiptMeteringProjection>,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
667#[serde(rename_all = "camelCase")]
668pub struct EconomicReceiptProjectionReport {
669 pub summary: EconomicReceiptProjectionSummary,
670 pub receipts: Vec<EconomicReceiptProjectionRow>,
671}
672
673#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
674#[serde(rename_all = "camelCase")]
675pub struct EconomicCompletionFlowSummary {
676 pub matching_receipts: u64,
677 pub returned_receipts: u64,
678 pub matching_underwriting_decisions: u64,
679 pub returned_underwriting_decisions: u64,
680 pub matching_credit_facilities: u64,
681 pub returned_credit_facilities: u64,
682 pub matching_credit_bonds: u64,
683 pub returned_credit_bonds: u64,
684 pub pending_settlement_receipts: u64,
685 pub failed_settlement_receipts: u64,
686 pub metering_actionable_receipts: u64,
687 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub latest_underwriting_decision_id: Option<String>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub latest_underwriting_outcome: Option<UnderwritingDecisionOutcome>,
691 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub latest_credit_facility_id: Option<String>,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub latest_credit_facility_disposition: Option<CreditFacilityDisposition>,
695 #[serde(default, skip_serializing_if = "Option::is_none")]
696 pub latest_credit_bond_id: Option<String>,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub latest_credit_bond_disposition: Option<CreditBondDisposition>,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
702#[serde(rename_all = "camelCase")]
703pub struct EconomicCompletionFlowReport {
704 pub schema: String,
705 pub generated_at: u64,
706 pub filters: ExposureLedgerQuery,
707 pub summary: EconomicCompletionFlowSummary,
708 pub economic_receipts: EconomicReceiptProjectionReport,
709 pub underwriting_decisions: UnderwritingDecisionListReport,
710 pub credit_facilities: CreditFacilityListReport,
711 pub credit_bonds: CreditBondListReport,
712}
713
714#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
715#[serde(rename_all = "camelCase")]
716pub struct GovernedAuthorizationCommerceDetail {
717 pub seller: String,
718 pub shared_payment_token_id: String,
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
722#[serde(rename_all = "camelCase")]
723pub struct GovernedAuthorizationMeteredBillingDetail {
724 pub settlement_mode: MeteredSettlementMode,
725 pub provider: String,
726 pub quote_id: String,
727 pub billing_unit: String,
728 pub quoted_units: u64,
729 pub quoted_cost: MonetaryAmount,
730 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub max_billed_units: Option<u64>,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
735#[serde(rename_all = "camelCase")]
736pub struct GovernedAuthorizationDetail {
737 #[serde(rename = "type")]
738 pub detail_type: String,
739 #[serde(default, skip_serializing_if = "Vec::is_empty")]
740 pub locations: Vec<String>,
741 #[serde(default, skip_serializing_if = "Vec::is_empty")]
742 pub actions: Vec<String>,
743 #[serde(default, skip_serializing_if = "Option::is_none")]
744 pub purpose: Option<String>,
745 #[serde(default, skip_serializing_if = "Option::is_none")]
746 pub max_amount: Option<MonetaryAmount>,
747 #[serde(default, skip_serializing_if = "Option::is_none")]
748 pub commerce: Option<GovernedAuthorizationCommerceDetail>,
749 #[serde(default, skip_serializing_if = "Option::is_none")]
750 pub metered_billing: Option<GovernedAuthorizationMeteredBillingDetail>,
751}
752
753#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
754#[serde(rename_all = "camelCase")]
755pub struct GovernedAuthorizationTransactionContext {
756 pub intent_id: String,
757 pub intent_hash: String,
758 #[serde(default, skip_serializing_if = "Option::is_none")]
759 pub approval_token_id: Option<String>,
760 #[serde(default, skip_serializing_if = "Option::is_none")]
761 pub approval_approved: Option<bool>,
762 #[serde(default, skip_serializing_if = "Option::is_none")]
763 pub approver_key: Option<String>,
764 #[serde(default, skip_serializing_if = "Option::is_none")]
765 pub runtime_assurance_tier: Option<RuntimeAssuranceTier>,
766 #[serde(default, skip_serializing_if = "Option::is_none")]
767 pub runtime_assurance_schema: Option<String>,
768 #[serde(default, skip_serializing_if = "Option::is_none")]
769 pub runtime_assurance_verifier_family: Option<AttestationVerifierFamily>,
770 #[serde(default, skip_serializing_if = "Option::is_none")]
771 pub runtime_assurance_verifier: Option<String>,
772 #[serde(default, skip_serializing_if = "Option::is_none")]
773 pub runtime_assurance_evidence_sha256: Option<String>,
774 #[serde(default, skip_serializing_if = "Option::is_none")]
775 pub call_chain: Option<GovernedCallChainProvenance>,
776 #[serde(default, skip_serializing_if = "Option::is_none")]
777 pub identity_assertion: Option<ChioIdentityAssertion>,
778}
779
780#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
781#[serde(rename_all = "camelCase")]
782pub struct GovernedTransactionDiagnostics {
783 #[serde(default, skip_serializing_if = "Option::is_none")]
784 pub asserted_call_chain: Option<GovernedCallChainProvenance>,
785 #[serde(default, skip_serializing_if = "EvidenceLineageReferences::is_empty")]
786 pub lineage_references: EvidenceLineageReferences,
787}
788
789impl GovernedTransactionDiagnostics {
790 #[must_use]
791 pub fn is_empty(&self) -> bool {
792 self.asserted_call_chain.is_none() && self.lineage_references.is_empty()
793 }
794}
795
796fn default_authorization_context_report_schema() -> String {
797 CHIO_OAUTH_AUTHORIZATION_CONTEXT_REPORT_SCHEMA.to_string()
798}
799
800fn default_chio_oauth_authorization_profile() -> ChioOAuthAuthorizationProfile {
801 ChioOAuthAuthorizationProfile::default()
802}
803
804#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
805#[serde(rename_all = "camelCase")]
806pub struct ChioOAuthAuthorizationProfile {
807 pub schema: String,
808 pub id: String,
809 pub authoritative_source: String,
810 pub authorization_detail_types: Vec<String>,
811 pub transaction_context_fields: Vec<String>,
812 #[serde(default)]
813 pub portable_claim_catalog: ChioPortableClaimCatalog,
814 #[serde(default)]
815 pub portable_identity_binding: ChioPortableIdentityBinding,
816 #[serde(default)]
817 pub governed_auth_binding: ChioGovernedAuthorizationBinding,
818 #[serde(default)]
819 pub request_time_contract: ChioOAuthRequestTimeContract,
820 #[serde(default)]
821 pub resource_binding: ChioOAuthResourceBinding,
822 #[serde(default)]
823 pub artifact_boundary: ChioOAuthArtifactBoundary,
824 pub sender_constraints: ChioOAuthSenderConstraintProfile,
825 pub unsupported_shapes_fail_closed: bool,
826}
827
828#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
829#[serde(rename_all = "camelCase")]
830pub struct ChioOAuthRequestTimeContract {
831 pub authorization_details_parameter: String,
832 pub transaction_context_parameter: String,
833 pub access_token_authorization_details_claim: String,
834 pub access_token_transaction_context_claim: String,
835 pub request_time_authorization_details_supported: bool,
836 pub request_time_transaction_context_supported: bool,
837 pub governed_receipts_authoritative_post_execution: bool,
838}
839
840impl Default for ChioOAuthRequestTimeContract {
841 fn default() -> Self {
842 Self {
843 authorization_details_parameter:
844 CHIO_OAUTH_REQUEST_TIME_AUTHORIZATION_DETAILS_PARAMETER.to_string(),
845 transaction_context_parameter: CHIO_OAUTH_REQUEST_TIME_TRANSACTION_CONTEXT_PARAMETER
846 .to_string(),
847 access_token_authorization_details_claim:
848 CHIO_OAUTH_REQUEST_TIME_AUTHORIZATION_DETAILS_CLAIM.to_string(),
849 access_token_transaction_context_claim:
850 CHIO_OAUTH_REQUEST_TIME_TRANSACTION_CONTEXT_CLAIM.to_string(),
851 request_time_authorization_details_supported: true,
852 request_time_transaction_context_supported: true,
853 governed_receipts_authoritative_post_execution: true,
854 }
855 }
856}
857
858#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
859#[serde(rename_all = "camelCase")]
860pub struct ChioOAuthResourceBinding {
861 pub protected_resource_field: String,
862 pub request_resource_parameter: String,
863 pub access_token_audience_claim: String,
864 pub access_token_resource_claim: String,
865 pub request_resource_must_match_protected_resource: bool,
866 pub bearer_token_must_include_audience_or_resource: bool,
867}
868
869impl Default for ChioOAuthResourceBinding {
870 fn default() -> Self {
871 Self {
872 protected_resource_field: "resource".to_string(),
873 request_resource_parameter: "resource".to_string(),
874 access_token_audience_claim: "aud".to_string(),
875 access_token_resource_claim: "resource".to_string(),
876 request_resource_must_match_protected_resource: true,
877 bearer_token_must_include_audience_or_resource: true,
878 }
879 }
880}
881
882#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
883#[serde(rename_all = "camelCase")]
884pub struct ChioOAuthArtifactBoundary {
885 pub access_tokens_runtime_admission_supported: bool,
886 pub approval_tokens_runtime_admission_supported: bool,
887 pub capabilities_runtime_admission_supported: bool,
888 pub reviewer_evidence_runtime_admission_supported: bool,
889 pub governed_receipts_audit_evidence_supported: bool,
890}
891
892impl Default for ChioOAuthArtifactBoundary {
893 fn default() -> Self {
894 Self {
895 access_tokens_runtime_admission_supported: true,
896 approval_tokens_runtime_admission_supported: false,
897 capabilities_runtime_admission_supported: false,
898 reviewer_evidence_runtime_admission_supported: false,
899 governed_receipts_audit_evidence_supported: true,
900 }
901 }
902}
903
904#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
905#[serde(rename_all = "camelCase")]
906pub struct ChioOAuthSenderConstraintProfile {
907 pub schema: String,
908 pub subject_binding: String,
909 pub proof_types_supported: Vec<String>,
910 pub proof_required_when: String,
911 pub runtime_assurance_binding_fields: Vec<String>,
912 pub delegated_call_chain_field: String,
913 pub unsupported_sender_shapes_fail_closed: bool,
914}
915
916impl Default for ChioOAuthSenderConstraintProfile {
917 fn default() -> Self {
918 Self {
919 schema: CHIO_OAUTH_SENDER_CONSTRAINT_SCHEMA.to_string(),
920 subject_binding: CHIO_OAUTH_SENDER_BINDING_CAPABILITY_SUBJECT.to_string(),
921 proof_types_supported: vec![
922 CHIO_OAUTH_SENDER_PROOF_CHIO_DPOP.to_string(),
923 CHIO_OAUTH_SENDER_PROOF_CHIO_MTLS.to_string(),
924 CHIO_OAUTH_SENDER_PROOF_CHIO_ATTESTATION.to_string(),
925 ],
926 proof_required_when: "matchedGrant.dpopRequired == true".to_string(),
927 runtime_assurance_binding_fields: vec![
928 "runtimeAssuranceTier".to_string(),
929 "runtimeAssuranceSchema".to_string(),
930 "runtimeAssuranceVerifierFamily".to_string(),
931 "runtimeAssuranceVerifier".to_string(),
932 "runtimeAssuranceEvidenceSha256".to_string(),
933 ],
934 delegated_call_chain_field: "callChain".to_string(),
935 unsupported_sender_shapes_fail_closed: true,
936 }
937 }
938}
939
940impl Default for ChioOAuthAuthorizationProfile {
941 fn default() -> Self {
942 Self {
943 schema: CHIO_OAUTH_AUTHORIZATION_PROFILE_SCHEMA.to_string(),
944 id: CHIO_OAUTH_AUTHORIZATION_PROFILE_ID.to_string(),
945 authoritative_source: "governed_receipt_projection".to_string(),
946 authorization_detail_types: vec![
947 CHIO_OAUTH_AUTHORIZATION_TOOL_DETAIL_TYPE.to_string(),
948 CHIO_OAUTH_AUTHORIZATION_COMMERCE_DETAIL_TYPE.to_string(),
949 CHIO_OAUTH_AUTHORIZATION_METERED_BILLING_DETAIL_TYPE.to_string(),
950 ],
951 transaction_context_fields: vec![
952 "intentId".to_string(),
953 "intentHash".to_string(),
954 "approvalTokenId".to_string(),
955 "approvalApproved".to_string(),
956 "approverKey".to_string(),
957 "runtimeAssuranceTier".to_string(),
958 "runtimeAssuranceSchema".to_string(),
959 "runtimeAssuranceVerifierFamily".to_string(),
960 "runtimeAssuranceVerifier".to_string(),
961 "runtimeAssuranceEvidenceSha256".to_string(),
962 "callChain".to_string(),
963 "identityAssertion".to_string(),
964 ],
965 portable_claim_catalog: ChioPortableClaimCatalog::default(),
966 portable_identity_binding: ChioPortableIdentityBinding::default(),
967 governed_auth_binding: ChioGovernedAuthorizationBinding::default(),
968 request_time_contract: ChioOAuthRequestTimeContract::default(),
969 resource_binding: ChioOAuthResourceBinding::default(),
970 artifact_boundary: ChioOAuthArtifactBoundary::default(),
971 sender_constraints: ChioOAuthSenderConstraintProfile::default(),
972 unsupported_shapes_fail_closed: true,
973 }
974 }
975}
976
977#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
978#[serde(rename_all = "camelCase")]
979pub struct AuthorizationContextSummary {
980 pub matching_receipts: u64,
981 pub returned_receipts: u64,
982 pub approval_receipts: u64,
983 pub approved_receipts: u64,
984 pub commerce_receipts: u64,
985 pub metered_billing_receipts: u64,
986 pub runtime_assurance_receipts: u64,
987 pub call_chain_receipts: u64,
988 pub asserted_call_chain_receipts: u64,
989 pub observed_call_chain_receipts: u64,
990 pub verified_call_chain_receipts: u64,
991 pub max_amount_receipts: u64,
992 pub sender_bound_receipts: u64,
993 pub dpop_bound_receipts: u64,
994 pub runtime_assurance_bound_receipts: u64,
995 pub delegated_sender_bound_receipts: u64,
996 pub session_anchor_receipts: u64,
997 pub request_lineage_receipts: u64,
998 pub receipt_lineage_statement_receipts: u64,
999 pub truncated: bool,
1000}
1001
1002#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1003#[serde(rename_all = "camelCase")]
1004pub struct AuthorizationContextSenderConstraint {
1005 pub subject_key: String,
1006 pub subject_key_source: String,
1007 #[serde(default, skip_serializing_if = "String::is_empty")]
1008 pub issuer_key: String,
1009 #[serde(default, skip_serializing_if = "String::is_empty")]
1010 pub issuer_key_source: String,
1011 pub matched_grant_index: u32,
1012 pub proof_required: bool,
1013 #[serde(default, skip_serializing_if = "Option::is_none")]
1014 pub proof_type: Option<String>,
1015 #[serde(default, skip_serializing_if = "Option::is_none")]
1016 pub proof_schema: Option<String>,
1017 pub runtime_assurance_bound: bool,
1018 pub delegated_call_chain_bound: bool,
1019}
1020
1021#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1022#[serde(rename_all = "camelCase")]
1023pub struct AuthorizationContextRow {
1024 pub receipt_id: String,
1025 pub timestamp: u64,
1026 pub capability_id: String,
1027 #[serde(default, skip_serializing_if = "Option::is_none")]
1028 pub subject_key: Option<String>,
1029 pub tool_server: String,
1030 pub tool_name: String,
1031 pub decision: Decision,
1032 pub authorization_details: Vec<GovernedAuthorizationDetail>,
1033 pub transaction_context: GovernedAuthorizationTransactionContext,
1034 #[serde(default, skip_serializing_if = "Option::is_none")]
1035 pub governed_transaction_diagnostics: Option<GovernedTransactionDiagnostics>,
1036 pub sender_constraint: AuthorizationContextSenderConstraint,
1037}
1038
1039#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1040#[serde(rename_all = "camelCase")]
1041pub struct AuthorizationContextReport {
1042 #[serde(default = "default_authorization_context_report_schema")]
1043 pub schema: String,
1044 #[serde(default = "default_chio_oauth_authorization_profile")]
1045 pub profile: ChioOAuthAuthorizationProfile,
1046 pub summary: AuthorizationContextSummary,
1047 pub receipts: Vec<AuthorizationContextRow>,
1048}
1049
1050#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1051#[serde(rename_all = "camelCase")]
1052pub struct ChioOAuthAuthorizationDiscoveryMetadata {
1053 pub protected_resource_metadata_paths: Vec<String>,
1054 pub authorization_server_metadata_path_template: String,
1055 pub discovery_informational_only: bool,
1056}
1057
1058#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1059#[serde(rename_all = "camelCase")]
1060pub struct ChioOAuthAuthorizationSupportBoundary {
1061 pub governed_receipts_authoritative: bool,
1062 pub hosted_request_time_authorization_supported: bool,
1063 pub resource_indicator_binding_supported: bool,
1064 pub sender_constrained_projection: bool,
1065 pub runtime_assurance_projection: bool,
1066 pub delegated_call_chain_projection: bool,
1067 pub generic_token_issuance_supported: bool,
1068 pub oidc_identity_assertions_supported: bool,
1069 pub mtls_transport_binding_in_profile: bool,
1070 pub approval_tokens_runtime_authorization_supported: bool,
1071 pub capabilities_runtime_authorization_supported: bool,
1072 pub reviewer_evidence_runtime_authorization_supported: bool,
1073}
1074
1075#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1076#[serde(rename_all = "camelCase")]
1077pub struct ChioOAuthAuthorizationExampleMapping {
1078 pub authorization_detail_types: Vec<String>,
1079 pub transaction_context_fields: Vec<String>,
1080 pub sender_constraint_fields: Vec<String>,
1081}
1082
1083#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1084#[serde(rename_all = "camelCase")]
1085pub struct ChioOAuthAuthorizationMetadataReport {
1086 pub schema: String,
1087 pub generated_at: u64,
1088 pub profile: ChioOAuthAuthorizationProfile,
1089 pub report_schema: String,
1090 pub discovery: ChioOAuthAuthorizationDiscoveryMetadata,
1091 pub support_boundary: ChioOAuthAuthorizationSupportBoundary,
1092 pub example_mapping: ChioOAuthAuthorizationExampleMapping,
1093}
1094
1095#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1096#[serde(rename_all = "camelCase")]
1097pub struct ChioOAuthAuthorizationReviewPackSummary {
1098 pub matching_receipts: u64,
1099 pub returned_receipts: u64,
1100 pub dpop_required_receipts: u64,
1101 pub runtime_assurance_receipts: u64,
1102 pub delegated_call_chain_receipts: u64,
1103 pub asserted_call_chain_receipts: u64,
1104 pub observed_call_chain_receipts: u64,
1105 pub verified_call_chain_receipts: u64,
1106 pub session_anchor_receipts: u64,
1107 pub request_lineage_receipts: u64,
1108 pub receipt_lineage_statement_receipts: u64,
1109 pub truncated: bool,
1110}
1111
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113#[serde(rename_all = "camelCase")]
1114pub struct ChioOAuthAuthorizationReviewPackRecord {
1115 pub receipt_id: String,
1116 pub capability_id: String,
1117 pub authorization_context: AuthorizationContextRow,
1118 pub governed_transaction: GovernedTransactionReceiptMetadata,
1119 #[serde(default, skip_serializing_if = "Option::is_none")]
1120 pub governed_transaction_diagnostics: Option<GovernedTransactionDiagnostics>,
1121 pub signed_receipt: ChioReceipt,
1122}
1123
1124#[derive(Debug, Clone, Serialize, Deserialize)]
1125#[serde(rename_all = "camelCase")]
1126pub struct ChioOAuthAuthorizationReviewPack {
1127 pub schema: String,
1128 pub generated_at: u64,
1129 pub filters: OperatorReportQuery,
1130 pub metadata: ChioOAuthAuthorizationMetadataReport,
1131 pub summary: ChioOAuthAuthorizationReviewPackSummary,
1132 pub records: Vec<ChioOAuthAuthorizationReviewPackRecord>,
1133}
1134
1135#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1136#[serde(rename_all = "camelCase")]
1137pub struct SharedEvidenceReferenceSummary {
1138 pub matching_shares: u64,
1139 pub matching_references: u64,
1140 pub matching_local_receipts: u64,
1141 pub remote_tool_receipts: u64,
1142 pub remote_lineage_records: u64,
1143 pub distinct_remote_subjects: u64,
1144 pub proof_required_shares: u64,
1145 pub truncated: bool,
1146}
1147
1148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1149#[serde(rename_all = "camelCase")]
1150pub struct SharedEvidenceReferenceRow {
1151 pub share: FederatedEvidenceShareSummary,
1152 pub capability_id: String,
1153 pub subject_key: String,
1154 pub issuer_key: String,
1155 pub delegation_depth: u64,
1156 #[serde(default, skip_serializing_if = "Option::is_none")]
1157 pub parent_capability_id: Option<String>,
1158 #[serde(default, skip_serializing_if = "Option::is_none")]
1159 pub local_anchor_capability_id: Option<String>,
1160 pub matched_local_receipts: u64,
1161 pub allow_count: u64,
1162 pub deny_count: u64,
1163 pub cancelled_count: u64,
1164 pub incomplete_count: u64,
1165 #[serde(default, skip_serializing_if = "Option::is_none")]
1166 pub first_seen: Option<u64>,
1167 #[serde(default, skip_serializing_if = "Option::is_none")]
1168 pub last_seen: Option<u64>,
1169}
1170
1171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1172#[serde(rename_all = "camelCase")]
1173pub struct SharedEvidenceReferenceReport {
1174 pub summary: SharedEvidenceReferenceSummary,
1175 pub references: Vec<SharedEvidenceReferenceRow>,
1176}
1177
1178pub const BEHAVIORAL_FEED_SCHEMA: &str = "chio.behavioral-feed.v1";
1180
1181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1182#[serde(rename_all = "camelCase")]
1183pub struct BehavioralFeedPrivacyBoundary {
1184 pub matching_receipts: u64,
1185 pub returned_receipts: u64,
1186 pub direct_evidence_export_supported: bool,
1187 pub child_receipt_scope: EvidenceChildReceiptScope,
1188 pub proofs_complete: bool,
1189 pub export_query: EvidenceExportQuery,
1190 #[serde(default, skip_serializing_if = "Option::is_none")]
1191 pub export_scope_note: Option<String>,
1192}
1193
1194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1195#[serde(rename_all = "camelCase")]
1196pub struct BehavioralFeedDecisionSummary {
1197 pub allow_count: u64,
1198 pub deny_count: u64,
1199 pub cancelled_count: u64,
1200 pub incomplete_count: u64,
1201}
1202
1203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1204#[serde(rename_all = "camelCase")]
1205pub struct BehavioralFeedSettlementSummary {
1206 pub pending_receipts: u64,
1207 pub settled_receipts: u64,
1208 pub failed_receipts: u64,
1209 pub not_applicable_receipts: u64,
1210 pub actionable_receipts: u64,
1211 pub reconciled_receipts: u64,
1212}
1213
1214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1215#[serde(rename_all = "camelCase")]
1216pub struct BehavioralFeedGovernedActionSummary {
1217 pub governed_receipts: u64,
1218 pub approval_receipts: u64,
1219 pub approved_receipts: u64,
1220 pub commerce_receipts: u64,
1221 pub max_amount_receipts: u64,
1222}
1223
1224#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1225#[serde(rename_all = "camelCase")]
1226pub struct BehavioralFeedMeteredBillingSummary {
1227 pub metered_receipts: u64,
1228 pub evidence_attached_receipts: u64,
1229 pub missing_evidence_receipts: u64,
1230 pub over_quoted_units_receipts: u64,
1231 pub over_max_billed_units_receipts: u64,
1232 pub over_quoted_cost_receipts: u64,
1233 pub financial_mismatch_receipts: u64,
1234 pub actionable_receipts: u64,
1235 pub reconciled_receipts: u64,
1236}
1237
1238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1239#[serde(rename_all = "camelCase")]
1240pub struct BehavioralFeedReputationSummary {
1241 pub subject_key: String,
1242 pub effective_score: f64,
1243 pub probationary: bool,
1244 #[serde(default, skip_serializing_if = "Option::is_none")]
1245 pub resolved_tier: Option<String>,
1246 pub imported_signal_count: usize,
1247 pub accepted_imported_signal_count: usize,
1248}
1249
1250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1251#[serde(rename_all = "camelCase")]
1252pub struct BehavioralFeedReceiptRow {
1253 pub receipt_id: String,
1254 pub timestamp: u64,
1255 pub capability_id: String,
1256 #[serde(default, skip_serializing_if = "Option::is_none")]
1257 pub subject_key: Option<String>,
1258 #[serde(default, skip_serializing_if = "Option::is_none")]
1259 pub issuer_key: Option<String>,
1260 pub tool_server: String,
1261 pub tool_name: String,
1262 pub decision: Decision,
1263 pub settlement_status: SettlementStatus,
1264 pub reconciliation_state: SettlementReconciliationState,
1265 pub action_required: bool,
1266 #[serde(default, skip_serializing_if = "Option::is_none")]
1267 pub cost_charged: Option<u64>,
1268 #[serde(default, skip_serializing_if = "Option::is_none")]
1269 pub attempted_cost: Option<u64>,
1270 #[serde(default, skip_serializing_if = "Option::is_none")]
1271 pub currency: Option<String>,
1272 #[serde(default, skip_serializing_if = "Option::is_none")]
1273 pub budget_authority: Option<FinancialBudgetAuthorityReceiptMetadata>,
1274 #[serde(default, skip_serializing_if = "Option::is_none")]
1275 pub governed: Option<GovernedTransactionReceiptMetadata>,
1276 #[serde(default, skip_serializing_if = "Option::is_none")]
1277 pub governed_transaction_diagnostics: Option<GovernedTransactionDiagnostics>,
1278 #[serde(default, skip_serializing_if = "Option::is_none")]
1279 pub metered_reconciliation: Option<BehavioralFeedMeteredBillingRow>,
1280}
1281
1282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1283#[serde(rename_all = "camelCase")]
1284pub struct BehavioralFeedMeteredBillingRow {
1285 pub reconciliation_state: MeteredBillingReconciliationState,
1286 pub action_required: bool,
1287 pub evidence_missing: bool,
1288 pub exceeds_quoted_units: bool,
1289 pub exceeds_max_billed_units: bool,
1290 pub exceeds_quoted_cost: bool,
1291 pub financial_mismatch: bool,
1292 #[serde(default, skip_serializing_if = "Option::is_none")]
1293 pub evidence: Option<MeteredBillingEvidenceRecord>,
1294}
1295
1296#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1297#[serde(rename_all = "camelCase")]
1298pub struct BehavioralFeedReceiptSelection {
1299 pub matching_receipts: u64,
1300 pub receipts: Vec<BehavioralFeedReceiptRow>,
1301}
1302
1303#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1304#[serde(rename_all = "camelCase")]
1305pub struct BehavioralFeedReport {
1306 pub schema: String,
1307 pub generated_at: u64,
1308 pub filters: BehavioralFeedQuery,
1309 pub privacy: BehavioralFeedPrivacyBoundary,
1310 pub decisions: BehavioralFeedDecisionSummary,
1311 pub settlements: BehavioralFeedSettlementSummary,
1312 pub governed_actions: BehavioralFeedGovernedActionSummary,
1313 pub metered_billing: BehavioralFeedMeteredBillingSummary,
1314 #[serde(default, skip_serializing_if = "Option::is_none")]
1315 pub reputation: Option<BehavioralFeedReputationSummary>,
1316 pub shared_evidence: SharedEvidenceReferenceSummary,
1317 pub receipts: Vec<BehavioralFeedReceiptRow>,
1318}
1319
1320pub type SignedBehavioralFeed = SignedExportEnvelope<BehavioralFeedReport>;
1321
1322impl ComplianceReport {
1329 #[must_use]
1335 pub fn compliance_score(
1336 &self,
1337 inputs: &crate::compliance_score::ComplianceScoreInputs,
1338 config: &crate::compliance_score::ComplianceScoreConfig,
1339 agent_id: &str,
1340 now: u64,
1341 ) -> crate::compliance_score::ComplianceScore {
1342 crate::compliance_score::compliance_score(self, inputs, config, agent_id, now)
1343 }
1344}
1345
1346#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1354#[serde(rename_all = "camelCase")]
1355pub struct EmaBaselineState {
1356 pub sample_count: u64,
1358 pub ema_mean: f64,
1360 pub ema_variance: f64,
1362 pub last_update: u64,
1364}
1365
1366impl EmaBaselineState {
1367 pub fn update(&mut self, sample: f64, alpha: f64, now: u64) {
1374 let alpha = alpha.clamp(f64::MIN_POSITIVE, 1.0);
1375 if self.sample_count == 0 {
1376 self.ema_mean = sample;
1377 self.ema_variance = 0.0;
1378 } else {
1379 let prev_mean = self.ema_mean;
1380 self.ema_mean = prev_mean + alpha * (sample - prev_mean);
1381 let diff = sample - prev_mean;
1383 self.ema_variance = (1.0 - alpha) * (self.ema_variance + alpha * diff * diff);
1384 }
1385 self.sample_count = self.sample_count.saturating_add(1);
1386 self.last_update = now;
1387 }
1388
1389 #[must_use]
1391 pub fn stddev(&self) -> f64 {
1392 self.ema_variance.max(0.0).sqrt()
1393 }
1394
1395 #[must_use]
1398 pub fn z_score(&self, sample: f64) -> Option<f64> {
1399 if self.sample_count < 2 {
1400 return None;
1401 }
1402 let stddev = self.stddev();
1403 if stddev <= f64::EPSILON {
1404 return None;
1405 }
1406 Some((sample - self.ema_mean) / stddev)
1407 }
1408}
1409
1410#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1414#[serde(rename_all = "camelCase")]
1415pub struct BehavioralAnomalyScore {
1416 pub agent_id: String,
1418 pub baseline: EmaBaselineState,
1420 pub current_sample: f64,
1422 pub z_score: Option<f64>,
1424 pub sigma_threshold: f64,
1426 pub anomaly: bool,
1428 pub generated_at: u64,
1430}
1431
1432#[must_use]
1437pub fn behavioral_anomaly_score(
1438 agent_id: &str,
1439 baseline: &EmaBaselineState,
1440 current_sample: f64,
1441 sigma_threshold: f64,
1442 now: u64,
1443) -> BehavioralAnomalyScore {
1444 let z_score = baseline.z_score(current_sample);
1445 let anomaly = z_score.is_some_and(|z| z.abs() > sigma_threshold);
1446 BehavioralAnomalyScore {
1447 agent_id: agent_id.to_string(),
1448 baseline: baseline.clone(),
1449 current_sample,
1450 z_score,
1451 sigma_threshold,
1452 anomaly,
1453 generated_at: now,
1454 }
1455}
1456
1457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1458#[serde(rename_all = "camelCase")]
1459pub struct OperatorReport {
1460 pub generated_at: u64,
1461 pub filters: OperatorReportQuery,
1462 pub activity: ReceiptAnalyticsResponse,
1463 pub cost_attribution: CostAttributionReport,
1464 pub budget_utilization: BudgetUtilizationReport,
1465 pub compliance: ComplianceReport,
1466 pub settlement_reconciliation: SettlementReconciliationReport,
1467 pub metered_billing_reconciliation: MeteredBillingReconciliationReport,
1468 pub authorization_context: AuthorizationContextReport,
1469 pub shared_evidence: SharedEvidenceReferenceReport,
1470}
1471
1472#[cfg(test)]
1473#[allow(clippy::unwrap_used)]
1474mod tests {
1475 use super::*;
1476 use chio_core::capability::{GovernedCallChainContext, GovernedProvenanceEvidenceClass};
1477 use chio_core::receipt::{
1478 FinancialBudgetAuthorityReceiptMetadata, FinancialBudgetAuthorizeReceiptMetadata,
1479 FinancialBudgetHoldAuthorityMetadata, FinancialBudgetTerminalReceiptMetadata,
1480 };
1481
1482 #[test]
1483 fn operator_report_behavioral_feed_query_clamps_limit_and_translates_filters() {
1484 let query = BehavioralFeedQuery {
1485 capability_id: Some("cap-1".to_string()),
1486 agent_subject: Some("subject-1".to_string()),
1487 tool_server: Some("shell".to_string()),
1488 tool_name: Some("bash".to_string()),
1489 since: Some(10),
1490 until: Some(20),
1491 receipt_limit: Some(5_000),
1492 };
1493
1494 assert_eq!(
1495 query.receipt_limit_or_default(),
1496 MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT
1497 );
1498 assert_eq!(
1499 query.normalized().receipt_limit,
1500 Some(MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT)
1501 );
1502
1503 let operator_query = query.to_operator_report_query();
1504 assert_eq!(operator_query.capability_id.as_deref(), Some("cap-1"));
1505 assert_eq!(operator_query.agent_subject.as_deref(), Some("subject-1"));
1506 assert_eq!(operator_query.tool_server.as_deref(), Some("shell"));
1507 assert_eq!(operator_query.tool_name.as_deref(), Some("bash"));
1508 assert_eq!(operator_query.since, Some(10));
1509 assert_eq!(operator_query.until, Some(20));
1510 assert_eq!(operator_query.metered_limit_or_default(), 50);
1511
1512 let receipt_query = query.to_receipt_query();
1513 assert_eq!(receipt_query.limit, MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT);
1514 assert_eq!(receipt_query.agent_subject.as_deref(), Some("subject-1"));
1515 }
1516
1517 #[test]
1518 fn operator_report_query_direct_export_support_requires_no_tool_filters() {
1519 let unrestricted = OperatorReportQuery::default();
1520 assert!(unrestricted.direct_evidence_export_supported());
1521
1522 let with_tool_filter = OperatorReportQuery {
1523 tool_server: Some("shell".to_string()),
1524 ..OperatorReportQuery::default()
1525 };
1526 assert!(!with_tool_filter.direct_evidence_export_supported());
1527 }
1528
1529 #[test]
1530 fn operator_report_query_clamps_metered_limit() {
1531 let query = OperatorReportQuery {
1532 metered_limit: Some(5_000),
1533 ..OperatorReportQuery::default()
1534 };
1535
1536 assert_eq!(query.metered_limit_or_default(), MAX_METERED_BILLING_LIMIT);
1537 }
1538
1539 #[test]
1540 fn operator_report_query_clamps_authorization_limit() {
1541 let query = OperatorReportQuery {
1542 authorization_limit: Some(5_000),
1543 ..OperatorReportQuery::default()
1544 };
1545
1546 assert_eq!(
1547 query.authorization_limit_or_default(),
1548 MAX_AUTHORIZATION_CONTEXT_LIMIT
1549 );
1550 }
1551
1552 #[test]
1553 fn operator_report_query_clamps_economic_limit() {
1554 let query = OperatorReportQuery {
1555 economic_limit: Some(5_000),
1556 ..OperatorReportQuery::default()
1557 };
1558
1559 assert_eq!(
1560 query.economic_limit_or_default(),
1561 MAX_ECONOMIC_RECEIPT_LIMIT
1562 );
1563 }
1564
1565 #[test]
1566 fn governed_transaction_diagnostics_serialization_omits_empty_fields() {
1567 let diagnostics = GovernedTransactionDiagnostics::default();
1568
1569 assert!(diagnostics.is_empty());
1570 assert_eq!(
1571 serde_json::to_value(diagnostics).unwrap(),
1572 serde_json::json!({})
1573 );
1574 }
1575
1576 #[test]
1577 fn governed_transaction_diagnostics_preserves_asserted_call_chain_and_lineage_references() {
1578 let diagnostics = GovernedTransactionDiagnostics {
1579 asserted_call_chain: Some(GovernedCallChainProvenance::asserted(
1580 GovernedCallChainContext {
1581 chain_id: "chain-1".to_string(),
1582 parent_request_id: "req-parent-1".to_string(),
1583 parent_receipt_id: Some("rcpt-parent-1".to_string()),
1584 origin_subject: "origin".to_string(),
1585 delegator_subject: "delegator".to_string(),
1586 },
1587 )),
1588 lineage_references: EvidenceLineageReferences {
1589 session_anchor_id: Some("anchor-1".to_string()),
1590 request_lineage_id: Some("req-lineage-1".to_string()),
1591 receipt_lineage_statement_id: Some("stmt-1".to_string()),
1592 },
1593 };
1594
1595 let value = serde_json::to_value(&diagnostics).unwrap();
1596
1597 assert_eq!(
1598 value["assertedCallChain"]["evidenceClass"],
1599 serde_json::json!("asserted")
1600 );
1601 assert_eq!(value["lineageReferences"]["sessionAnchorId"], "anchor-1");
1602 assert_eq!(
1603 diagnostics
1604 .asserted_call_chain
1605 .as_ref()
1606 .map(|call_chain| call_chain.evidence_class),
1607 Some(GovernedProvenanceEvidenceClass::Asserted)
1608 );
1609 }
1610
1611 #[test]
1612 fn settlement_reconciliation_row_serialization_preserves_budget_authority_metadata() {
1613 let budget_authority = FinancialBudgetAuthorityReceiptMetadata {
1614 guarantee_level: "ha_quorum_commit".to_string(),
1615 authority_profile: "ha".to_string(),
1616 metering_profile: "metered".to_string(),
1617 hold_id: "hold-1".to_string(),
1618 budget_term: Some("term-7".to_string()),
1619 authority: Some(FinancialBudgetHoldAuthorityMetadata {
1620 authority_id: "http://leader-a".to_string(),
1621 lease_id: "lease-7".to_string(),
1622 lease_epoch: 7,
1623 }),
1624 authorize: FinancialBudgetAuthorizeReceiptMetadata {
1625 event_id: Some("hold-1:authorize".to_string()),
1626 budget_commit_index: Some(41),
1627 exposure_units: 120,
1628 committed_cost_units_after: 120,
1629 },
1630 terminal: Some(FinancialBudgetTerminalReceiptMetadata {
1631 disposition: "reconciled".to_string(),
1632 event_id: Some("hold-1:reconcile".to_string()),
1633 budget_commit_index: Some(42),
1634 exposure_units: 120,
1635 realized_spend_units: 75,
1636 committed_cost_units_after: 75,
1637 }),
1638 };
1639 let row = SettlementReconciliationRow {
1640 receipt_id: "rcpt-1".to_string(),
1641 timestamp: 42,
1642 capability_id: "cap-1".to_string(),
1643 subject_key: Some("subject-1".to_string()),
1644 tool_server: "payments".to_string(),
1645 tool_name: "charge".to_string(),
1646 payment_reference: Some("payref-1".to_string()),
1647 settlement_status: SettlementStatus::Pending,
1648 cost_charged: Some(75),
1649 currency: Some("usd".to_string()),
1650 budget_authority: Some(budget_authority.clone()),
1651 reconciliation_state: SettlementReconciliationState::Open,
1652 action_required: true,
1653 note: None,
1654 updated_at: None,
1655 };
1656
1657 let value = serde_json::to_value(&row).unwrap();
1658
1659 assert_eq!(
1660 value["budgetAuthority"]["guarantee_level"],
1661 serde_json::json!("ha_quorum_commit")
1662 );
1663 assert_eq!(
1664 value["budgetAuthority"]["hold_id"],
1665 serde_json::json!("hold-1")
1666 );
1667 assert_eq!(
1668 value["budgetAuthority"]["authority"]["authority_id"],
1669 serde_json::json!("http://leader-a")
1670 );
1671 }
1672
1673 #[test]
1674 fn metered_and_behavioral_rows_serialize_budget_authority_metadata() {
1675 let budget_authority = FinancialBudgetAuthorityReceiptMetadata {
1676 guarantee_level: "ha_quorum_commit".to_string(),
1677 authority_profile: "ha".to_string(),
1678 metering_profile: "metered".to_string(),
1679 hold_id: "hold-2".to_string(),
1680 budget_term: Some("term-8".to_string()),
1681 authority: Some(FinancialBudgetHoldAuthorityMetadata {
1682 authority_id: "http://leader-b".to_string(),
1683 lease_id: "lease-8".to_string(),
1684 lease_epoch: 8,
1685 }),
1686 authorize: FinancialBudgetAuthorizeReceiptMetadata {
1687 event_id: Some("hold-2:authorize".to_string()),
1688 budget_commit_index: Some(51),
1689 exposure_units: 150,
1690 committed_cost_units_after: 150,
1691 },
1692 terminal: None,
1693 };
1694
1695 let metered = MeteredBillingReconciliationRow {
1696 receipt_id: "rcpt-2".to_string(),
1697 timestamp: 99,
1698 capability_id: "cap-2".to_string(),
1699 subject_key: None,
1700 tool_server: "meter".to_string(),
1701 tool_name: "bill".to_string(),
1702 settlement_mode: MeteredSettlementMode::AllowThenSettle,
1703 provider: "provider-1".to_string(),
1704 quote_id: "quote-1".to_string(),
1705 billing_unit: "tokens".to_string(),
1706 quoted_units: 10,
1707 quoted_cost: MonetaryAmount {
1708 units: 50,
1709 currency: "USD".to_string(),
1710 },
1711 max_billed_units: Some(10),
1712 financial_cost_charged: Some(50),
1713 financial_currency: Some("USD".to_string()),
1714 budget_authority: Some(budget_authority.clone()),
1715 evidence: None,
1716 reconciliation_state: MeteredBillingReconciliationState::Open,
1717 action_required: true,
1718 evidence_missing: true,
1719 exceeds_quoted_units: false,
1720 exceeds_max_billed_units: false,
1721 exceeds_quoted_cost: false,
1722 financial_mismatch: false,
1723 note: None,
1724 updated_at: None,
1725 };
1726 let behavioral = BehavioralFeedReceiptRow {
1727 receipt_id: "rcpt-3".to_string(),
1728 timestamp: 100,
1729 capability_id: "cap-3".to_string(),
1730 subject_key: None,
1731 issuer_key: None,
1732 tool_server: "meter".to_string(),
1733 tool_name: "bill".to_string(),
1734 decision: Decision::Allow,
1735 settlement_status: SettlementStatus::Settled,
1736 reconciliation_state: SettlementReconciliationState::Reconciled,
1737 action_required: false,
1738 cost_charged: Some(50),
1739 attempted_cost: Some(60),
1740 currency: Some("USD".to_string()),
1741 budget_authority: Some(budget_authority),
1742 governed: None,
1743 governed_transaction_diagnostics: None,
1744 metered_reconciliation: None,
1745 };
1746
1747 let metered_value = serde_json::to_value(&metered).unwrap();
1748 let behavioral_value = serde_json::to_value(&behavioral).unwrap();
1749
1750 assert_eq!(
1751 metered_value["budgetAuthority"]["guarantee_level"],
1752 serde_json::json!("ha_quorum_commit")
1753 );
1754 assert_eq!(
1755 behavioral_value["budgetAuthority"]["hold_id"],
1756 serde_json::json!("hold-2")
1757 );
1758 }
1759}