Skip to main content

chio_kernel/
cost_attribution.rs

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