Skip to main content

chio_kernel/
cost_attribution.rs

1use chio_core::receipt::{
2    economics::FinancialBudgetAuthorityReceiptMetadata, economics::SettlementStatus,
3};
4use serde::{Deserialize, Serialize};
5
6use crate::receipt_query::ReceiptReadContext;
7
8/// Maximum number of detailed attribution rows returned in a single report.
9pub const MAX_COST_ATTRIBUTION_LIMIT: usize = 200;
10
11/// Query parameters for delegation-chain cost attribution reporting.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct CostAttributionQuery {
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub capability_id: Option<String>,
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub agent_subject: Option<String>,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub tool_server: Option<String>,
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub tool_name: Option<String>,
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub since: Option<u64>,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub until: Option<u64>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub limit: Option<usize>,
29    /// Auth-derived read authority. This is never accepted from request bodies.
30    #[serde(skip)]
31    pub read_context: Option<ReceiptReadContext>,
32}
33
34impl Default for CostAttributionQuery {
35    fn default() -> Self {
36        Self {
37            capability_id: None,
38            agent_subject: None,
39            tool_server: None,
40            tool_name: None,
41            since: None,
42            until: None,
43            limit: Some(100),
44            read_context: None,
45        }
46    }
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
50#[serde(rename_all = "camelCase")]
51pub struct CostAttributionSummary {
52    pub matching_receipts: u64,
53    pub returned_receipts: u64,
54    pub total_cost_charged: u64,
55    pub total_attempted_cost: u64,
56    pub max_delegation_depth: u64,
57    pub distinct_root_subjects: u64,
58    pub distinct_leaf_subjects: u64,
59    pub lineage_gap_count: u64,
60    pub truncated: bool,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
64#[serde(rename_all = "camelCase")]
65pub struct CostAttributionChainHop {
66    pub capability_id: String,
67    pub subject_key: String,
68    pub issuer_key: String,
69    pub delegation_depth: u64,
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub parent_capability_id: Option<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(rename_all = "camelCase")]
76pub struct RootCostAttributionRow {
77    pub root_subject_key: String,
78    pub receipt_count: u64,
79    pub total_cost_charged: u64,
80    pub total_attempted_cost: u64,
81    pub distinct_leaf_subjects: u64,
82    pub max_delegation_depth: u64,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "camelCase")]
87pub struct LeafCostAttributionRow {
88    pub root_subject_key: String,
89    pub leaf_subject_key: String,
90    pub receipt_count: u64,
91    pub total_cost_charged: u64,
92    pub total_attempted_cost: u64,
93    pub max_delegation_depth: u64,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97#[serde(rename_all = "camelCase")]
98pub struct CostAttributionReceiptRow {
99    pub seq: u64,
100    pub receipt_id: String,
101    pub timestamp: u64,
102    pub capability_id: String,
103    pub tool_server: String,
104    pub tool_name: String,
105    pub decision_kind: String,
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub root_subject_key: Option<String>,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub leaf_subject_key: Option<String>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub grant_index: Option<u32>,
112    pub delegation_depth: u64,
113    pub cost_charged: u64,
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub attempted_cost: Option<u64>,
116    pub currency: String,
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub budget_total: Option<u64>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub budget_remaining: Option<u64>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub settlement_status: Option<SettlementStatus>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub payment_reference: Option<String>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub budget_authority: Option<FinancialBudgetAuthorityReceiptMetadata>,
127    pub lineage_complete: bool,
128    pub chain: Vec<CostAttributionChainHop>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132#[serde(rename_all = "camelCase")]
133pub struct CostAttributionReport {
134    pub summary: CostAttributionSummary,
135    pub by_root: Vec<RootCostAttributionRow>,
136    pub by_leaf: Vec<LeafCostAttributionRow>,
137    pub receipts: Vec<CostAttributionReceiptRow>,
138}