1use serde::{Deserialize, Serialize};
2
3use chio_core::appraisal::AttestationVerifierFamily;
4use chio_core::capability::{
5 governance::{GovernedCallChainProvenance, MeteredSettlementMode},
6 runtime_attestation::RuntimeAssuranceTier,
7 scope::MonetaryAmount,
8};
9use chio_core::receipt::{
10 body::ChioReceipt, decision::Decision, economics::EconomicAuthorizationReceiptMetadata,
11 economics::FinancialBudgetAuthorityReceiptMetadata, economics::SettlementStatus,
12 governance::GovernedTransactionReceiptMetadata,
13 governance::MeteredUsageEvidenceReceiptMetadata, lineage::SignedExportEnvelope,
14};
15use chio_core::session::ChioIdentityAssertion;
16use chio_core::{
17 ChioGovernedAuthorizationBinding, ChioPortableClaimCatalog, ChioPortableIdentityBinding,
18};
19use chio_credit::{
20 CreditBondDisposition, CreditBondListReport, CreditFacilityDisposition,
21 CreditFacilityListReport, ExposureLedgerQuery,
22};
23use chio_underwriting::{UnderwritingDecisionListReport, UnderwritingDecisionOutcome};
24
25use crate::cost_attribution::CostAttributionQuery;
26use crate::evidence_export::{
27 EvidenceChildReceiptScope, EvidenceExportQuery, EvidenceLineageReferences,
28};
29use crate::receipt_analytics::{AnalyticsTimeBucket, ReceiptAnalyticsResponse};
30use crate::receipt_query::{ReceiptQuery, ReceiptReadBoundary, ReceiptReadContext};
31use crate::receipt_store::FederatedEvidenceShareSummary;
32use crate::CostAttributionReport;
33
34mod authorization_context;
35mod behavioral_analysis;
36mod budget_report;
37mod comptroller_surface;
38mod constants;
39mod operator_report_types;
40mod queries;
41mod settlement_report;
42
43pub use authorization_context::{
44 AuthorizationContextReport, AuthorizationContextRow, AuthorizationContextSenderConstraint,
45 AuthorizationContextSummary, ChioOAuthArtifactBoundary,
46 ChioOAuthAuthorizationDiscoveryMetadata, ChioOAuthAuthorizationExampleMapping,
47 ChioOAuthAuthorizationMetadataReport, ChioOAuthAuthorizationProfile,
48 ChioOAuthAuthorizationReviewPack, ChioOAuthAuthorizationReviewPackRecord,
49 ChioOAuthAuthorizationReviewPackSummary, ChioOAuthAuthorizationSupportBoundary,
50 ChioOAuthRequestTimeContract, ChioOAuthResourceBinding, ChioOAuthSenderConstraintProfile,
51 GovernedAuthorizationCommerceDetail, GovernedAuthorizationDetail,
52 GovernedAuthorizationMeteredBillingDetail, GovernedAuthorizationTransactionContext,
53 GovernedTransactionDiagnostics,
54};
55pub use behavioral_analysis::{
56 behavioral_anomaly_score, BehavioralAnomalyScore, BehavioralFeedDecisionSummary,
57 BehavioralFeedGovernedActionSummary, BehavioralFeedMeteredBillingRow,
58 BehavioralFeedMeteredBillingSummary, BehavioralFeedPrivacyBoundary, BehavioralFeedReceiptRow,
59 BehavioralFeedReceiptSelection, BehavioralFeedReport, BehavioralFeedReputationSummary,
60 BehavioralFeedSettlementSummary, EmaBaselineState, SignedBehavioralFeed,
61};
62pub use budget_report::{
63 BudgetDimensionProfile, BudgetDimensionUsage, BudgetUtilizationReport, BudgetUtilizationRow,
64 BudgetUtilizationSummary, ComplianceReport,
65};
66pub use comptroller_surface::{
67 ComptrollerDecisionSummary, ComptrollerSurfaceReport, ComptrollerSurfaceSourceRefs,
68 COMPTROLLER_SURFACE_REPORT_SCHEMA,
69};
70pub use constants::{
71 BEHAVIORAL_FEED_SCHEMA, CHIO_OAUTH_AUTHORIZATION_COMMERCE_DETAIL_TYPE,
72 CHIO_OAUTH_AUTHORIZATION_CONTEXT_REPORT_SCHEMA, CHIO_OAUTH_AUTHORIZATION_METADATA_SCHEMA,
73 CHIO_OAUTH_AUTHORIZATION_METERED_BILLING_DETAIL_TYPE, CHIO_OAUTH_AUTHORIZATION_PROFILE_ID,
74 CHIO_OAUTH_AUTHORIZATION_PROFILE_SCHEMA, CHIO_OAUTH_AUTHORIZATION_REVIEW_PACK_SCHEMA,
75 CHIO_OAUTH_AUTHORIZATION_TOOL_DETAIL_TYPE, CHIO_OAUTH_REQUEST_TIME_AUTHORIZATION_DETAILS_CLAIM,
76 CHIO_OAUTH_REQUEST_TIME_AUTHORIZATION_DETAILS_PARAMETER,
77 CHIO_OAUTH_REQUEST_TIME_TRANSACTION_CONTEXT_CLAIM,
78 CHIO_OAUTH_REQUEST_TIME_TRANSACTION_CONTEXT_PARAMETER,
79 CHIO_OAUTH_SENDER_BINDING_CAPABILITY_SUBJECT, CHIO_OAUTH_SENDER_CONSTRAINT_SCHEMA,
80 CHIO_OAUTH_SENDER_PROOF_CHIO_ATTESTATION, CHIO_OAUTH_SENDER_PROOF_CHIO_DPOP,
81 CHIO_OAUTH_SENDER_PROOF_CHIO_MTLS, ECONOMIC_COMPLETION_FLOW_SCHEMA,
82 MAX_AUTHORIZATION_CONTEXT_LIMIT, MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT, MAX_ECONOMIC_RECEIPT_LIMIT,
83 MAX_METERED_BILLING_LIMIT, MAX_OPERATOR_BUDGET_LIMIT, MAX_SETTLEMENT_BACKLOG_LIMIT,
84 MAX_SHARED_EVIDENCE_LIMIT,
85};
86pub use operator_report_types::{
87 OperatorReport, SharedEvidenceReferenceReport, SharedEvidenceReferenceRow,
88 SharedEvidenceReferenceSummary,
89};
90pub use queries::{BehavioralFeedQuery, OperatorReportQuery, SharedEvidenceQuery};
91pub use settlement_report::{
92 EconomicCompletionFlowReport, EconomicCompletionFlowSummary, EconomicReceiptMeteringProjection,
93 EconomicReceiptProjectionReport, EconomicReceiptProjectionRow,
94 EconomicReceiptProjectionSummary, EconomicReceiptSettlementProjection,
95 MeteredBillingEvidenceRecord, MeteredBillingReconciliationReport,
96 MeteredBillingReconciliationRow, MeteredBillingReconciliationState,
97 MeteredBillingReconciliationSummary, SettlementReconciliationReport,
98 SettlementReconciliationRow, SettlementReconciliationState, SettlementReconciliationSummary,
99};
100
101#[cfg(test)]
102#[allow(clippy::unwrap_used)]
103mod tests {
104 use super::*;
105 use chio_core::capability::governance::{
106 GovernedCallChainContext, GovernedProvenanceEvidenceClass,
107 };
108 use chio_core::receipt::{
109 economics::FinancialBudgetAuthorityReceiptMetadata,
110 economics::FinancialBudgetAuthorizeReceiptMetadata,
111 economics::FinancialBudgetHoldAuthorityMetadata,
112 economics::FinancialBudgetTerminalReceiptMetadata,
113 };
114
115 #[test]
116 fn operator_report_behavioral_feed_query_clamps_limit_and_translates_filters() {
117 let query = BehavioralFeedQuery {
118 capability_id: Some("cap-1".to_string()),
119 agent_subject: Some("subject-1".to_string()),
120 tool_server: Some("shell".to_string()),
121 tool_name: Some("bash".to_string()),
122 since: Some(10),
123 until: Some(20),
124 receipt_limit: Some(5_000),
125 read_context: Some(ReceiptReadContext::local_operator_admin_all()),
126 };
127
128 assert_eq!(
129 query.receipt_limit_or_default(),
130 MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT
131 );
132 assert_eq!(
133 query.normalized().receipt_limit,
134 Some(MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT)
135 );
136
137 let operator_query = query.to_operator_report_query();
138 assert_eq!(operator_query.capability_id.as_deref(), Some("cap-1"));
139 assert_eq!(operator_query.agent_subject.as_deref(), Some("subject-1"));
140 assert_eq!(operator_query.tool_server.as_deref(), Some("shell"));
141 assert_eq!(operator_query.tool_name.as_deref(), Some("bash"));
142 assert_eq!(operator_query.since, Some(10));
143 assert_eq!(operator_query.until, Some(20));
144 assert_eq!(operator_query.metered_limit_or_default(), 50);
145
146 let receipt_query = query.to_receipt_query();
147 assert_eq!(receipt_query.limit, MAX_BEHAVIORAL_FEED_RECEIPT_LIMIT);
148 assert_eq!(receipt_query.agent_subject.as_deref(), Some("subject-1"));
149 }
150
151 #[test]
152 fn operator_report_query_requires_read_context_for_evidence_export() {
153 let query = OperatorReportQuery::default();
154 let err = query
155 .to_evidence_export_query()
156 .expect_err("evidence export must require explicit read authority");
157
158 assert_eq!(
159 err,
160 "operator report evidence export requires an explicit read context"
161 );
162 }
163
164 #[test]
165 fn operator_report_query_maps_admin_read_context_to_evidence_export() {
166 let query = OperatorReportQuery {
167 capability_id: Some("cap-1".to_string()),
168 since: Some(10),
169 read_context: Some(ReceiptReadContext::admin_service()),
170 ..OperatorReportQuery::default()
171 };
172
173 let export = query
174 .to_evidence_export_query()
175 .expect("admin read context should authorize evidence export");
176
177 assert_eq!(export.capability_id.as_deref(), Some("cap-1"));
178 assert_eq!(export.since, Some(10));
179 assert!(export.tenant.is_none());
180 assert_eq!(export.read_boundary, Some(ReceiptReadBoundary::AdminAll));
181 }
182
183 #[test]
184 fn operator_report_query_maps_tenant_read_context_to_evidence_export() {
185 let query = OperatorReportQuery {
186 read_context: Some(ReceiptReadContext::authenticated_tenant("tenant-a")),
187 ..OperatorReportQuery::default()
188 };
189
190 let export = query
191 .to_evidence_export_query()
192 .expect("tenant read context should authorize evidence export");
193
194 assert_eq!(export.tenant.as_deref(), Some("tenant-a"));
195 assert_eq!(
196 export.read_boundary,
197 Some(ReceiptReadBoundary::TenantScoped {
198 tenant: "tenant-a".to_string(),
199 })
200 );
201 }
202
203 #[test]
204 fn operator_report_query_direct_export_support_requires_no_tool_filters() {
205 let unrestricted = OperatorReportQuery::default();
206 assert!(unrestricted.direct_evidence_export_supported());
207
208 let with_tool_filter = OperatorReportQuery {
209 tool_server: Some("shell".to_string()),
210 ..OperatorReportQuery::default()
211 };
212 assert!(!with_tool_filter.direct_evidence_export_supported());
213 }
214
215 #[test]
216 fn operator_report_query_clamps_metered_limit() {
217 let query = OperatorReportQuery {
218 metered_limit: Some(5_000),
219 ..OperatorReportQuery::default()
220 };
221
222 assert_eq!(query.metered_limit_or_default(), MAX_METERED_BILLING_LIMIT);
223 }
224
225 #[test]
226 fn operator_report_query_clamps_authorization_limit() {
227 let query = OperatorReportQuery {
228 authorization_limit: Some(5_000),
229 ..OperatorReportQuery::default()
230 };
231
232 assert_eq!(
233 query.authorization_limit_or_default(),
234 MAX_AUTHORIZATION_CONTEXT_LIMIT
235 );
236 }
237
238 #[test]
239 fn operator_report_query_clamps_economic_limit() {
240 let query = OperatorReportQuery {
241 economic_limit: Some(5_000),
242 ..OperatorReportQuery::default()
243 };
244
245 assert_eq!(
246 query.economic_limit_or_default(),
247 MAX_ECONOMIC_RECEIPT_LIMIT
248 );
249 }
250
251 #[test]
252 fn governed_transaction_diagnostics_serialization_omits_empty_fields() {
253 let diagnostics = GovernedTransactionDiagnostics::default();
254
255 assert!(diagnostics.is_empty());
256 assert_eq!(
257 serde_json::to_value(diagnostics).unwrap(),
258 serde_json::json!({})
259 );
260 }
261
262 #[test]
263 fn governed_transaction_diagnostics_preserves_asserted_call_chain_and_lineage_references() {
264 let diagnostics = GovernedTransactionDiagnostics {
265 asserted_call_chain: Some(GovernedCallChainProvenance::asserted(
266 GovernedCallChainContext {
267 chain_id: "chain-1".to_string(),
268 parent_request_id: "req-parent-1".to_string(),
269 parent_receipt_id: Some("rcpt-parent-1".to_string()),
270 origin_subject: "origin".to_string(),
271 delegator_subject: "delegator".to_string(),
272 },
273 )),
274 lineage_references: EvidenceLineageReferences {
275 session_anchor_id: Some("anchor-1".to_string()),
276 request_lineage_id: Some("req-lineage-1".to_string()),
277 receipt_lineage_statement_id: Some("stmt-1".to_string()),
278 },
279 };
280
281 let value = serde_json::to_value(&diagnostics).unwrap();
282
283 assert_eq!(
284 value["assertedCallChain"]["evidenceClass"],
285 serde_json::json!("asserted")
286 );
287 assert_eq!(value["lineageReferences"]["sessionAnchorId"], "anchor-1");
288 assert_eq!(
289 diagnostics
290 .asserted_call_chain
291 .as_ref()
292 .map(|call_chain| call_chain.evidence_class),
293 Some(GovernedProvenanceEvidenceClass::Asserted)
294 );
295 }
296
297 #[test]
298 fn settlement_reconciliation_row_serialization_preserves_budget_authority_metadata() {
299 let budget_authority = FinancialBudgetAuthorityReceiptMetadata {
300 guarantee_level: "ha_quorum_commit".to_string(),
301 authority_profile: "ha".to_string(),
302 metering_profile: "metered".to_string(),
303 hold_id: "hold-1".to_string(),
304 budget_term: Some("term-7".to_string()),
305 authority: Some(FinancialBudgetHoldAuthorityMetadata {
306 authority_id: "http://leader-a".to_string(),
307 lease_id: "lease-7".to_string(),
308 lease_epoch: 7,
309 }),
310 authorize: FinancialBudgetAuthorizeReceiptMetadata {
311 event_id: Some("hold-1:authorize".to_string()),
312 budget_commit_index: Some(41),
313 exposure_units: 120,
314 committed_cost_units_after: 120,
315 },
316 terminal: Some(FinancialBudgetTerminalReceiptMetadata {
317 disposition: "reconciled".to_string(),
318 event_id: Some("hold-1:reconcile".to_string()),
319 budget_commit_index: Some(42),
320 exposure_units: 120,
321 realized_spend_units: 75,
322 committed_cost_units_after: 75,
323 }),
324 };
325 let row = SettlementReconciliationRow {
326 receipt_id: "rcpt-1".to_string(),
327 timestamp: 42,
328 capability_id: "cap-1".to_string(),
329 subject_key: Some("subject-1".to_string()),
330 tool_server: "payments".to_string(),
331 tool_name: "charge".to_string(),
332 payment_reference: Some("payref-1".to_string()),
333 settlement_status: SettlementStatus::Pending,
334 cost_charged: Some(75),
335 currency: Some("usd".to_string()),
336 budget_authority: Some(budget_authority.clone()),
337 reconciliation_state: SettlementReconciliationState::Open,
338 action_required: true,
339 note: None,
340 updated_at: None,
341 };
342
343 let value = serde_json::to_value(&row).unwrap();
344
345 assert_eq!(
346 value["budgetAuthority"]["guarantee_level"],
347 serde_json::json!("ha_quorum_commit")
348 );
349 assert_eq!(
350 value["budgetAuthority"]["hold_id"],
351 serde_json::json!("hold-1")
352 );
353 assert_eq!(
354 value["budgetAuthority"]["authority"]["authority_id"],
355 serde_json::json!("http://leader-a")
356 );
357 }
358
359 #[test]
360 fn metered_and_behavioral_rows_serialize_budget_authority_metadata() {
361 let budget_authority = FinancialBudgetAuthorityReceiptMetadata {
362 guarantee_level: "ha_quorum_commit".to_string(),
363 authority_profile: "ha".to_string(),
364 metering_profile: "metered".to_string(),
365 hold_id: "hold-2".to_string(),
366 budget_term: Some("term-8".to_string()),
367 authority: Some(FinancialBudgetHoldAuthorityMetadata {
368 authority_id: "http://leader-b".to_string(),
369 lease_id: "lease-8".to_string(),
370 lease_epoch: 8,
371 }),
372 authorize: FinancialBudgetAuthorizeReceiptMetadata {
373 event_id: Some("hold-2:authorize".to_string()),
374 budget_commit_index: Some(51),
375 exposure_units: 150,
376 committed_cost_units_after: 150,
377 },
378 terminal: None,
379 };
380
381 let metered = MeteredBillingReconciliationRow {
382 receipt_id: "rcpt-2".to_string(),
383 timestamp: 99,
384 capability_id: "cap-2".to_string(),
385 subject_key: None,
386 tool_server: "meter".to_string(),
387 tool_name: "bill".to_string(),
388 settlement_mode: MeteredSettlementMode::AllowThenSettle,
389 provider: "provider-1".to_string(),
390 quote_id: "quote-1".to_string(),
391 billing_unit: "tokens".to_string(),
392 quoted_units: 10,
393 quoted_cost: MonetaryAmount {
394 units: 50,
395 currency: "USD".to_string(),
396 },
397 max_billed_units: Some(10),
398 financial_cost_charged: Some(50),
399 financial_currency: Some("USD".to_string()),
400 budget_authority: Some(budget_authority.clone()),
401 evidence: None,
402 reconciliation_state: MeteredBillingReconciliationState::Open,
403 action_required: true,
404 evidence_missing: true,
405 exceeds_quoted_units: false,
406 exceeds_max_billed_units: false,
407 exceeds_quoted_cost: false,
408 financial_mismatch: false,
409 note: None,
410 updated_at: None,
411 };
412 let behavioral = BehavioralFeedReceiptRow {
413 receipt_id: "rcpt-3".to_string(),
414 timestamp: 100,
415 capability_id: "cap-3".to_string(),
416 subject_key: None,
417 issuer_key: None,
418 tool_server: "meter".to_string(),
419 tool_name: "bill".to_string(),
420 decision: Some(Decision::Allow),
421 authorized: true,
422 settlement_status: SettlementStatus::Settled,
423 reconciliation_state: SettlementReconciliationState::Reconciled,
424 action_required: false,
425 cost_charged: Some(50),
426 attempted_cost: Some(60),
427 currency: Some("USD".to_string()),
428 budget_authority: Some(budget_authority),
429 governed: None,
430 governed_transaction_diagnostics: None,
431 metered_reconciliation: None,
432 };
433
434 let metered_value = serde_json::to_value(&metered).unwrap();
435 let behavioral_value = serde_json::to_value(&behavioral).unwrap();
436
437 assert_eq!(
438 metered_value["budgetAuthority"]["guarantee_level"],
439 serde_json::json!("ha_quorum_commit")
440 );
441 assert_eq!(
442 behavioral_value["budgetAuthority"]["hold_id"],
443 serde_json::json!("hold-2")
444 );
445 }
446}