Skip to main content

chio_kernel/operator_report/
mod.rs

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}