Skip to main content

chio_store_sqlite/receipt_store/
reports.rs

1use super::*;
2
3use chio_core::capability::GovernedProvenanceEvidenceClass;
4use chio_kernel::evidence_export::EvidenceLineageReferences;
5use chio_kernel::operator_report::GovernedTransactionDiagnostics;
6
7#[derive(Debug, Clone)]
8struct GovernedTransactionProjection {
9    strong: GovernedTransactionReceiptMetadata,
10    diagnostics: Option<GovernedTransactionDiagnostics>,
11}
12
13#[derive(Debug, Clone, Default)]
14struct PersistedLineageReferences {
15    statement_id: Option<String>,
16    session_anchor_id: Option<String>,
17}
18
19fn extract_governed_transaction_diagnostics(
20    receipt: &ChioReceipt,
21) -> Option<GovernedTransactionDiagnostics> {
22    let diagnostics = receipt
23        .metadata
24        .as_ref()
25        .and_then(|metadata| metadata.get("governed_transaction_diagnostics"))
26        .cloned()
27        .and_then(|value| serde_json::from_value::<GovernedTransactionDiagnostics>(value).ok())
28        .unwrap_or_default();
29
30    (!diagnostics.is_empty()).then_some(diagnostics)
31}
32
33fn sanitize_governed_transaction_projection(
34    receipt: &ChioReceipt,
35    governed: &GovernedTransactionReceiptMetadata,
36) -> GovernedTransactionProjection {
37    let mut strong = governed.clone();
38    let mut diagnostics = extract_governed_transaction_diagnostics(receipt).unwrap_or_default();
39
40    if let Some(call_chain) = strong
41        .call_chain
42        .clone()
43        .filter(|call_chain| call_chain.evidence_class == GovernedProvenanceEvidenceClass::Asserted)
44    {
45        strong.call_chain = None;
46        if diagnostics.asserted_call_chain.is_none() {
47            diagnostics.asserted_call_chain = Some(call_chain);
48        }
49    }
50
51    if diagnostics.lineage_references.session_anchor_id.is_none() {
52        diagnostics.lineage_references.session_anchor_id = strong
53            .call_chain
54            .as_ref()
55            .and_then(|call_chain| call_chain.session_anchor_id.clone())
56            .or_else(|| {
57                diagnostics
58                    .asserted_call_chain
59                    .as_ref()
60                    .and_then(|call_chain| call_chain.session_anchor_id.clone())
61            });
62    }
63    if diagnostics
64        .lineage_references
65        .receipt_lineage_statement_id
66        .is_none()
67    {
68        diagnostics.lineage_references.receipt_lineage_statement_id = strong
69            .call_chain
70            .as_ref()
71            .and_then(|call_chain| call_chain.receipt_lineage_statement_id.clone())
72            .or_else(|| {
73                diagnostics
74                    .asserted_call_chain
75                    .as_ref()
76                    .and_then(|call_chain| call_chain.receipt_lineage_statement_id.clone())
77            });
78    }
79
80    GovernedTransactionProjection {
81        strong,
82        diagnostics: (!diagnostics.is_empty()).then_some(diagnostics),
83    }
84}
85
86fn apply_persisted_lineage_references(
87    projection: &mut GovernedTransactionProjection,
88    persisted: &PersistedLineageReferences,
89) {
90    if persisted.statement_id.is_none() && persisted.session_anchor_id.is_none() {
91        return;
92    }
93
94    if let Some(call_chain) = projection.strong.call_chain.as_mut() {
95        if call_chain.session_anchor_id.is_none() {
96            call_chain.session_anchor_id = persisted.session_anchor_id.clone();
97        }
98        if call_chain.receipt_lineage_statement_id.is_none() {
99            call_chain.receipt_lineage_statement_id = persisted.statement_id.clone();
100        }
101    }
102
103    let diagnostics = projection
104        .diagnostics
105        .get_or_insert_with(GovernedTransactionDiagnostics::default);
106    if let Some(call_chain) = diagnostics.asserted_call_chain.as_mut() {
107        if call_chain.session_anchor_id.is_none() {
108            call_chain.session_anchor_id = persisted.session_anchor_id.clone();
109        }
110        if call_chain.receipt_lineage_statement_id.is_none() {
111            call_chain.receipt_lineage_statement_id = persisted.statement_id.clone();
112        }
113    }
114    if diagnostics.lineage_references.session_anchor_id.is_none() {
115        diagnostics.lineage_references.session_anchor_id = persisted.session_anchor_id.clone();
116    }
117    if diagnostics
118        .lineage_references
119        .receipt_lineage_statement_id
120        .is_none()
121    {
122        diagnostics.lineage_references.receipt_lineage_statement_id =
123            persisted.statement_id.clone();
124    }
125    if diagnostics.is_empty() {
126        projection.diagnostics = None;
127    }
128}
129
130fn call_chain_evidence_class(
131    projection: &GovernedTransactionProjection,
132) -> Option<GovernedProvenanceEvidenceClass> {
133    projection
134        .strong
135        .call_chain
136        .as_ref()
137        .map(|call_chain| call_chain.evidence_class)
138        .or_else(|| {
139            projection
140                .diagnostics
141                .as_ref()
142                .and_then(|diagnostics| diagnostics.asserted_call_chain.as_ref())
143                .map(|call_chain| call_chain.evidence_class)
144        })
145}
146
147fn lineage_references_from_projection(
148    projection: &GovernedTransactionProjection,
149) -> Option<&EvidenceLineageReferences> {
150    projection
151        .diagnostics
152        .as_ref()
153        .map(|diagnostics| &diagnostics.lineage_references)
154        .filter(|references| !references.is_empty())
155}
156
157fn authorization_transaction_context_from_projection(
158    projection: &GovernedTransactionProjection,
159) -> chio_kernel::operator_report::GovernedAuthorizationTransactionContext {
160    let mut transaction_context =
161        authorization_transaction_context_from_governed_metadata(&projection.strong);
162    if transaction_context.call_chain.is_none() {
163        transaction_context.call_chain = projection
164            .diagnostics
165            .as_ref()
166            .and_then(|diagnostics| diagnostics.asserted_call_chain.clone());
167    }
168    transaction_context
169}
170
171impl SqliteReceiptStore {
172    pub fn query_receipt_analytics(
173        &self,
174        query: &ReceiptAnalyticsQuery,
175    ) -> Result<ReceiptAnalyticsResponse, ReceiptStoreError> {
176        let group_limit = query
177            .group_limit
178            .unwrap_or(50)
179            .clamp(1, MAX_ANALYTICS_GROUP_LIMIT);
180        let time_bucket = query.time_bucket.unwrap_or(AnalyticsTimeBucket::Day);
181        let bucket_width = time_bucket.width_secs() as i64;
182
183        let capability_id = query.capability_id.as_deref();
184        let tool_server = query.tool_server.as_deref();
185        let tool_name = query.tool_name.as_deref();
186        let since = query.since.map(|value| value as i64);
187        let until = query.until.map(|value| value as i64);
188        let agent_subject = query.agent_subject.as_deref();
189
190        let summary_sql = r#"
191            SELECT
192                COUNT(*) AS total_receipts,
193                COALESCE(SUM(CASE WHEN r.decision_kind = 'allow' THEN 1 ELSE 0 END), 0) AS allow_count,
194                COALESCE(SUM(CASE WHEN r.decision_kind = 'deny' THEN 1 ELSE 0 END), 0) AS deny_count,
195                COALESCE(SUM(CASE WHEN r.decision_kind = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled_count,
196                COALESCE(SUM(CASE WHEN r.decision_kind = 'incomplete' THEN 1 ELSE 0 END), 0) AS incomplete_count,
197                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.cost_charged'), 0) AS INTEGER)), 0) AS total_cost_charged,
198                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.attempted_cost'), 0) AS INTEGER)), 0) AS total_attempted_cost
199            FROM chio_tool_receipts r
200            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
201            WHERE (?1 IS NULL OR r.capability_id = ?1)
202              AND (?2 IS NULL OR r.tool_server = ?2)
203              AND (?3 IS NULL OR r.tool_name = ?3)
204              AND (?4 IS NULL OR r.timestamp >= ?4)
205              AND (?5 IS NULL OR r.timestamp <= ?5)
206              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
207        "#;
208        let summary = self.connection()?.query_row(
209            summary_sql,
210            params![
211                capability_id,
212                tool_server,
213                tool_name,
214                since,
215                until,
216                agent_subject
217            ],
218            |row| {
219                Ok(ReceiptAnalyticsMetrics::from_raw(
220                    row.get::<_, i64>(0)?.max(0) as u64,
221                    row.get::<_, i64>(1)?.max(0) as u64,
222                    row.get::<_, i64>(2)?.max(0) as u64,
223                    row.get::<_, i64>(3)?.max(0) as u64,
224                    row.get::<_, i64>(4)?.max(0) as u64,
225                    row.get::<_, i64>(5)?.max(0) as u64,
226                    row.get::<_, i64>(6)?.max(0) as u64,
227                ))
228            },
229        )?;
230
231        let by_agent_sql = r#"
232            SELECT
233                COALESCE(r.subject_key, cl.subject_key) AS subject_key,
234                COUNT(*) AS total_receipts,
235                COALESCE(SUM(CASE WHEN r.decision_kind = 'allow' THEN 1 ELSE 0 END), 0) AS allow_count,
236                COALESCE(SUM(CASE WHEN r.decision_kind = 'deny' THEN 1 ELSE 0 END), 0) AS deny_count,
237                COALESCE(SUM(CASE WHEN r.decision_kind = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled_count,
238                COALESCE(SUM(CASE WHEN r.decision_kind = 'incomplete' THEN 1 ELSE 0 END), 0) AS incomplete_count,
239                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.cost_charged'), 0) AS INTEGER)), 0) AS total_cost_charged,
240                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.attempted_cost'), 0) AS INTEGER)), 0) AS total_attempted_cost
241            FROM chio_tool_receipts r
242            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
243            WHERE (?1 IS NULL OR r.capability_id = ?1)
244              AND (?2 IS NULL OR r.tool_server = ?2)
245              AND (?3 IS NULL OR r.tool_name = ?3)
246              AND (?4 IS NULL OR r.timestamp >= ?4)
247              AND (?5 IS NULL OR r.timestamp <= ?5)
248              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
249              AND COALESCE(r.subject_key, cl.subject_key) IS NOT NULL
250            GROUP BY COALESCE(r.subject_key, cl.subject_key)
251            ORDER BY total_receipts DESC, subject_key ASC
252            LIMIT ?7
253        "#;
254        let by_agent = self
255            .connection()?
256            .prepare(by_agent_sql)?
257            .query_map(
258                params![
259                    capability_id,
260                    tool_server,
261                    tool_name,
262                    since,
263                    until,
264                    agent_subject,
265                    group_limit as i64
266                ],
267                |row| {
268                    Ok(AgentAnalyticsRow {
269                        subject_key: row.get(0)?,
270                        metrics: ReceiptAnalyticsMetrics::from_raw(
271                            row.get::<_, i64>(1)?.max(0) as u64,
272                            row.get::<_, i64>(2)?.max(0) as u64,
273                            row.get::<_, i64>(3)?.max(0) as u64,
274                            row.get::<_, i64>(4)?.max(0) as u64,
275                            row.get::<_, i64>(5)?.max(0) as u64,
276                            row.get::<_, i64>(6)?.max(0) as u64,
277                            row.get::<_, i64>(7)?.max(0) as u64,
278                        ),
279                    })
280                },
281            )?
282            .collect::<Result<Vec<_>, _>>()?;
283
284        let by_tool_sql = r#"
285            SELECT
286                r.tool_server,
287                r.tool_name,
288                COUNT(*) AS total_receipts,
289                COALESCE(SUM(CASE WHEN r.decision_kind = 'allow' THEN 1 ELSE 0 END), 0) AS allow_count,
290                COALESCE(SUM(CASE WHEN r.decision_kind = 'deny' THEN 1 ELSE 0 END), 0) AS deny_count,
291                COALESCE(SUM(CASE WHEN r.decision_kind = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled_count,
292                COALESCE(SUM(CASE WHEN r.decision_kind = 'incomplete' THEN 1 ELSE 0 END), 0) AS incomplete_count,
293                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.cost_charged'), 0) AS INTEGER)), 0) AS total_cost_charged,
294                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.attempted_cost'), 0) AS INTEGER)), 0) AS total_attempted_cost
295            FROM chio_tool_receipts r
296            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
297            WHERE (?1 IS NULL OR r.capability_id = ?1)
298              AND (?2 IS NULL OR r.tool_server = ?2)
299              AND (?3 IS NULL OR r.tool_name = ?3)
300              AND (?4 IS NULL OR r.timestamp >= ?4)
301              AND (?5 IS NULL OR r.timestamp <= ?5)
302              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
303            GROUP BY r.tool_server, r.tool_name
304            ORDER BY total_receipts DESC, r.tool_server ASC, r.tool_name ASC
305            LIMIT ?7
306        "#;
307        let by_tool = self
308            .connection()?
309            .prepare(by_tool_sql)?
310            .query_map(
311                params![
312                    capability_id,
313                    tool_server,
314                    tool_name,
315                    since,
316                    until,
317                    agent_subject,
318                    group_limit as i64
319                ],
320                |row| {
321                    Ok(ToolAnalyticsRow {
322                        tool_server: row.get(0)?,
323                        tool_name: row.get(1)?,
324                        metrics: ReceiptAnalyticsMetrics::from_raw(
325                            row.get::<_, i64>(2)?.max(0) as u64,
326                            row.get::<_, i64>(3)?.max(0) as u64,
327                            row.get::<_, i64>(4)?.max(0) as u64,
328                            row.get::<_, i64>(5)?.max(0) as u64,
329                            row.get::<_, i64>(6)?.max(0) as u64,
330                            row.get::<_, i64>(7)?.max(0) as u64,
331                            row.get::<_, i64>(8)?.max(0) as u64,
332                        ),
333                    })
334                },
335            )?
336            .collect::<Result<Vec<_>, _>>()?;
337
338        let by_time_sql = r#"
339            SELECT
340                CAST((r.timestamp / ?7) * ?7 AS INTEGER) AS bucket_start,
341                COUNT(*) AS total_receipts,
342                COALESCE(SUM(CASE WHEN r.decision_kind = 'allow' THEN 1 ELSE 0 END), 0) AS allow_count,
343                COALESCE(SUM(CASE WHEN r.decision_kind = 'deny' THEN 1 ELSE 0 END), 0) AS deny_count,
344                COALESCE(SUM(CASE WHEN r.decision_kind = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled_count,
345                COALESCE(SUM(CASE WHEN r.decision_kind = 'incomplete' THEN 1 ELSE 0 END), 0) AS incomplete_count,
346                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.cost_charged'), 0) AS INTEGER)), 0) AS total_cost_charged,
347                COALESCE(SUM(CAST(COALESCE(json_extract(r.raw_json, '$.metadata.financial.attempted_cost'), 0) AS INTEGER)), 0) AS total_attempted_cost
348            FROM chio_tool_receipts r
349            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
350            WHERE (?1 IS NULL OR r.capability_id = ?1)
351              AND (?2 IS NULL OR r.tool_server = ?2)
352              AND (?3 IS NULL OR r.tool_name = ?3)
353              AND (?4 IS NULL OR r.timestamp >= ?4)
354              AND (?5 IS NULL OR r.timestamp <= ?5)
355              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
356            GROUP BY bucket_start
357            ORDER BY bucket_start ASC
358            LIMIT ?8
359        "#;
360        let by_time = self
361            .connection()?
362            .prepare(by_time_sql)?
363            .query_map(
364                params![
365                    capability_id,
366                    tool_server,
367                    tool_name,
368                    since,
369                    until,
370                    agent_subject,
371                    bucket_width,
372                    group_limit as i64
373                ],
374                |row| {
375                    let bucket_start = row.get::<_, i64>(0)?.max(0) as u64;
376                    Ok(TimeAnalyticsRow {
377                        bucket_start,
378                        bucket_end: bucket_start
379                            .saturating_add(bucket_width.max(1) as u64)
380                            .saturating_sub(1),
381                        metrics: ReceiptAnalyticsMetrics::from_raw(
382                            row.get::<_, i64>(1)?.max(0) as u64,
383                            row.get::<_, i64>(2)?.max(0) as u64,
384                            row.get::<_, i64>(3)?.max(0) as u64,
385                            row.get::<_, i64>(4)?.max(0) as u64,
386                            row.get::<_, i64>(5)?.max(0) as u64,
387                            row.get::<_, i64>(6)?.max(0) as u64,
388                            row.get::<_, i64>(7)?.max(0) as u64,
389                        ),
390                    })
391                },
392            )?
393            .collect::<Result<Vec<_>, _>>()?;
394
395        Ok(ReceiptAnalyticsResponse {
396            summary,
397            by_agent,
398            by_tool,
399            by_time,
400        })
401    }
402
403    pub fn query_cost_attribution_report(
404        &self,
405        query: &CostAttributionQuery,
406    ) -> Result<CostAttributionReport, ReceiptStoreError> {
407        let limit = query
408            .limit
409            .unwrap_or(100)
410            .clamp(1, MAX_COST_ATTRIBUTION_LIMIT);
411        let capability_id = query.capability_id.as_deref();
412        let tool_server = query.tool_server.as_deref();
413        let tool_name = query.tool_name.as_deref();
414        let since = query.since.map(|value| value as i64);
415        let until = query.until.map(|value| value as i64);
416        let agent_subject = query.agent_subject.as_deref();
417
418        let count_sql = r#"
419            SELECT COUNT(*)
420            FROM chio_tool_receipts r
421            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
422            WHERE (?1 IS NULL OR r.capability_id = ?1)
423              AND (?2 IS NULL OR r.tool_server = ?2)
424              AND (?3 IS NULL OR r.tool_name = ?3)
425              AND (?4 IS NULL OR r.timestamp >= ?4)
426              AND (?5 IS NULL OR r.timestamp <= ?5)
427              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
428              AND json_type(r.raw_json, '$.metadata.financial') = 'object'
429        "#;
430
431        let matching_receipts = self
432            .connection()?
433            .query_row(
434                count_sql,
435                params![
436                    capability_id,
437                    tool_server,
438                    tool_name,
439                    since,
440                    until,
441                    agent_subject
442                ],
443                |row| row.get::<_, i64>(0),
444            )
445            .map(|value| value.max(0) as u64)?;
446
447        let data_sql = r#"
448            SELECT r.seq, r.raw_json
449            FROM chio_tool_receipts r
450            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
451            WHERE (?1 IS NULL OR r.capability_id = ?1)
452              AND (?2 IS NULL OR r.tool_server = ?2)
453              AND (?3 IS NULL OR r.tool_name = ?3)
454              AND (?4 IS NULL OR r.timestamp >= ?4)
455              AND (?5 IS NULL OR r.timestamp <= ?5)
456              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
457              AND json_type(r.raw_json, '$.metadata.financial') = 'object'
458            ORDER BY r.seq ASC
459        "#;
460
461        let rows = self
462            .connection()?
463            .prepare(data_sql)?
464            .query_map(
465                params![
466                    capability_id,
467                    tool_server,
468                    tool_name,
469                    since,
470                    until,
471                    agent_subject
472                ],
473                |row| {
474                    Ok((
475                        row.get::<_, i64>(0)?.max(0) as u64,
476                        row.get::<_, String>(1)?,
477                    ))
478                },
479            )?
480            .collect::<Result<Vec<_>, _>>()?;
481
482        let mut receipts = Vec::with_capacity(rows.len().min(limit));
483        let mut by_root = BTreeMap::<String, RootAggregate>::new();
484        let mut by_leaf = BTreeMap::<(String, String), LeafAggregate>::new();
485        let mut distinct_roots = BTreeSet::new();
486        let mut distinct_leaves = BTreeSet::new();
487        let mut total_cost_charged = 0_u64;
488        let mut total_attempted_cost = 0_u64;
489        let mut max_delegation_depth = 0_u64;
490        let mut lineage_gap_count = 0_u64;
491
492        for (seq, raw_json) in rows {
493            let receipt =
494                decode_verified_chio_receipt(&raw_json, "persisted tool receipt", Some(seq))?;
495            let Some(financial) = extract_financial_metadata(&receipt) else {
496                continue;
497            };
498            let attribution = extract_receipt_attribution(&receipt);
499            let chain_snapshots = self
500                .get_combined_delegation_chain(&receipt.capability_id)
501                .unwrap_or_default();
502            let lineage_complete = chain_is_complete(&receipt.capability_id, &chain_snapshots);
503            if !lineage_complete {
504                lineage_gap_count = lineage_gap_count.saturating_add(1);
505            }
506
507            let chain = chain_snapshots
508                .iter()
509                .map(|snapshot| CostAttributionChainHop {
510                    capability_id: snapshot.capability_id.clone(),
511                    subject_key: snapshot.subject_key.clone(),
512                    issuer_key: snapshot.issuer_key.clone(),
513                    delegation_depth: snapshot.delegation_depth,
514                    parent_capability_id: snapshot.parent_capability_id.clone(),
515                })
516                .collect::<Vec<_>>();
517
518            let root_subject_key = chain_snapshots
519                .first()
520                .map(|snapshot| snapshot.subject_key.clone())
521                .or_else(|| Some(financial.root_budget_holder.clone()));
522            let leaf_subject_key = attribution.subject_key.clone().or_else(|| {
523                chain_snapshots
524                    .last()
525                    .map(|snapshot| snapshot.subject_key.clone())
526            });
527            let attempted_cost = financial.attempted_cost.unwrap_or(0);
528            let decision = decision_kind(&receipt.decision).to_string();
529
530            total_cost_charged = total_cost_charged.saturating_add(financial.cost_charged);
531            total_attempted_cost = total_attempted_cost.saturating_add(attempted_cost);
532            max_delegation_depth = max_delegation_depth.max(financial.delegation_depth as u64);
533
534            if let Some(root_key) = root_subject_key.clone() {
535                distinct_roots.insert(root_key.clone());
536                let root_entry = by_root.entry(root_key.clone()).or_default();
537                root_entry.receipt_count = root_entry.receipt_count.saturating_add(1);
538                root_entry.total_cost_charged = root_entry
539                    .total_cost_charged
540                    .saturating_add(financial.cost_charged);
541                root_entry.total_attempted_cost = root_entry
542                    .total_attempted_cost
543                    .saturating_add(attempted_cost);
544                root_entry.max_delegation_depth = root_entry
545                    .max_delegation_depth
546                    .max(financial.delegation_depth as u64);
547
548                if let Some(leaf_key) = leaf_subject_key.clone() {
549                    root_entry.leaf_subjects.insert(leaf_key.clone());
550                    let leaf_entry = by_leaf.entry((root_key, leaf_key)).or_default();
551                    leaf_entry.receipt_count = leaf_entry.receipt_count.saturating_add(1);
552                    leaf_entry.total_cost_charged = leaf_entry
553                        .total_cost_charged
554                        .saturating_add(financial.cost_charged);
555                    leaf_entry.total_attempted_cost = leaf_entry
556                        .total_attempted_cost
557                        .saturating_add(attempted_cost);
558                    leaf_entry.max_delegation_depth = leaf_entry
559                        .max_delegation_depth
560                        .max(financial.delegation_depth as u64);
561                }
562            }
563
564            if let Some(leaf_key) = leaf_subject_key.clone() {
565                distinct_leaves.insert(leaf_key);
566            }
567
568            if receipts.len() < limit {
569                receipts.push(CostAttributionReceiptRow {
570                    seq,
571                    receipt_id: receipt.id.clone(),
572                    timestamp: receipt.timestamp,
573                    capability_id: receipt.capability_id.clone(),
574                    tool_server: receipt.tool_server.clone(),
575                    tool_name: receipt.tool_name.clone(),
576                    decision_kind: decision,
577                    root_subject_key,
578                    leaf_subject_key,
579                    grant_index: Some(financial.grant_index),
580                    delegation_depth: financial.delegation_depth as u64,
581                    cost_charged: financial.cost_charged,
582                    attempted_cost: financial.attempted_cost,
583                    currency: financial.currency.clone(),
584                    budget_total: Some(financial.budget_total),
585                    budget_remaining: Some(financial.budget_remaining),
586                    settlement_status: Some(financial.settlement_status),
587                    payment_reference: financial.payment_reference.clone(),
588                    budget_authority: receipt.financial_budget_authority_metadata(),
589                    lineage_complete,
590                    chain,
591                });
592            }
593        }
594
595        let mut by_root = by_root
596            .into_iter()
597            .map(|(root_subject_key, aggregate)| RootCostAttributionRow {
598                root_subject_key,
599                receipt_count: aggregate.receipt_count,
600                total_cost_charged: aggregate.total_cost_charged,
601                total_attempted_cost: aggregate.total_attempted_cost,
602                distinct_leaf_subjects: aggregate.leaf_subjects.len() as u64,
603                max_delegation_depth: aggregate.max_delegation_depth,
604            })
605            .collect::<Vec<_>>();
606        by_root.sort_by(|left, right| {
607            right
608                .total_cost_charged
609                .cmp(&left.total_cost_charged)
610                .then_with(|| right.receipt_count.cmp(&left.receipt_count))
611                .then_with(|| left.root_subject_key.cmp(&right.root_subject_key))
612        });
613
614        let mut by_leaf = by_leaf
615            .into_iter()
616            .map(
617                |((root_subject_key, leaf_subject_key), aggregate)| LeafCostAttributionRow {
618                    root_subject_key,
619                    leaf_subject_key,
620                    receipt_count: aggregate.receipt_count,
621                    total_cost_charged: aggregate.total_cost_charged,
622                    total_attempted_cost: aggregate.total_attempted_cost,
623                    max_delegation_depth: aggregate.max_delegation_depth,
624                },
625            )
626            .collect::<Vec<_>>();
627        by_leaf.sort_by(|left, right| {
628            right
629                .total_cost_charged
630                .cmp(&left.total_cost_charged)
631                .then_with(|| right.receipt_count.cmp(&left.receipt_count))
632                .then_with(|| left.root_subject_key.cmp(&right.root_subject_key))
633                .then_with(|| left.leaf_subject_key.cmp(&right.leaf_subject_key))
634        });
635
636        Ok(CostAttributionReport {
637            summary: CostAttributionSummary {
638                matching_receipts,
639                returned_receipts: receipts.len() as u64,
640                total_cost_charged,
641                total_attempted_cost,
642                max_delegation_depth,
643                distinct_root_subjects: distinct_roots.len() as u64,
644                distinct_leaf_subjects: distinct_leaves.len() as u64,
645                lineage_gap_count,
646                truncated: matching_receipts > receipts.len() as u64,
647            },
648            by_root,
649            by_leaf,
650            receipts,
651        })
652    }
653
654    pub fn query_shared_evidence_report(
655        &self,
656        query: &SharedEvidenceQuery,
657    ) -> Result<SharedEvidenceReferenceReport, ReceiptStoreError> {
658        let limit = query.limit_or_default();
659        let capability_id = query.capability_id.as_deref();
660        let tool_server = query.tool_server.as_deref();
661        let tool_name = query.tool_name.as_deref();
662        let since = query.since.map(|value| value as i64);
663        let until = query.until.map(|value| value as i64);
664        let agent_subject = query.agent_subject.as_deref();
665        let issuer = query.issuer.as_deref();
666        let partner = query.partner.as_deref();
667
668        let rows = self
669            .connection()?
670            .prepare(
671                r#"
672                SELECT r.receipt_id, r.timestamp, r.capability_id, r.decision_kind
673                FROM chio_tool_receipts r
674                LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
675                WHERE (?1 IS NULL OR r.capability_id = ?1)
676                  AND (?2 IS NULL OR r.tool_server = ?2)
677                  AND (?3 IS NULL OR r.tool_name = ?3)
678                  AND (?4 IS NULL OR r.timestamp >= ?4)
679                  AND (?5 IS NULL OR r.timestamp <= ?5)
680                  AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
681                ORDER BY r.seq ASC
682                "#,
683            )?
684            .query_map(
685                params![
686                    capability_id,
687                    tool_server,
688                    tool_name,
689                    since,
690                    until,
691                    agent_subject
692                ],
693                |row| {
694                    Ok((
695                        row.get::<_, String>(0)?,
696                        row.get::<_, i64>(1)?.max(0) as u64,
697                        row.get::<_, String>(2)?,
698                        row.get::<_, String>(3)?,
699                    ))
700                },
701            )?
702            .collect::<Result<Vec<_>, _>>()?;
703
704        let mut share_cache = BTreeMap::<String, Option<FederatedEvidenceShareSummary>>::new();
705        let mut references = BTreeMap::<(String, String), SharedEvidenceReferenceRow>::new();
706        let mut matched_local_receipts = BTreeSet::<String>::new();
707
708        for (receipt_id, timestamp, local_capability_id, decision) in rows {
709            let chain = self.get_combined_delegation_chain(&local_capability_id)?;
710            if chain.is_empty() {
711                continue;
712            }
713
714            let mut matched_this_receipt = false;
715            for (index, snapshot) in chain.iter().enumerate() {
716                let share = match share_cache.get(&snapshot.capability_id) {
717                    Some(cached) => cached.clone(),
718                    None => {
719                        let loaded = self
720                            .get_federated_share_for_capability(&snapshot.capability_id)?
721                            .map(|(share, _)| share);
722                        share_cache.insert(snapshot.capability_id.clone(), loaded.clone());
723                        loaded
724                    }
725                };
726                let Some(share) = share else {
727                    continue;
728                };
729                if issuer.is_some_and(|expected| share.issuer != expected) {
730                    continue;
731                }
732                if partner.is_some_and(|expected| share.partner != expected) {
733                    continue;
734                }
735
736                let local_anchor_capability_id =
737                    chain.iter().skip(index + 1).find_map(|candidate| {
738                        match share_cache.get(&candidate.capability_id) {
739                            Some(Some(_)) => None,
740                            Some(None) => Some(candidate.capability_id.clone()),
741                            None => {
742                                let loaded = self
743                                    .get_federated_share_for_capability(&candidate.capability_id)
744                                    .ok()
745                                    .and_then(|value| value.map(|(share, _)| share));
746                                share_cache.insert(candidate.capability_id.clone(), loaded.clone());
747                                if loaded.is_some() {
748                                    None
749                                } else {
750                                    Some(candidate.capability_id.clone())
751                                }
752                            }
753                        }
754                    });
755
756                let key = (share.share_id.clone(), snapshot.capability_id.clone());
757                let entry = references
758                    .entry(key)
759                    .or_insert_with(|| SharedEvidenceReferenceRow {
760                        share: share.clone(),
761                        capability_id: snapshot.capability_id.clone(),
762                        subject_key: snapshot.subject_key.clone(),
763                        issuer_key: snapshot.issuer_key.clone(),
764                        delegation_depth: snapshot.delegation_depth,
765                        parent_capability_id: snapshot.parent_capability_id.clone(),
766                        local_anchor_capability_id: local_anchor_capability_id.clone(),
767                        matched_local_receipts: 0,
768                        allow_count: 0,
769                        deny_count: 0,
770                        cancelled_count: 0,
771                        incomplete_count: 0,
772                        first_seen: Some(timestamp),
773                        last_seen: Some(timestamp),
774                    });
775
776                entry.local_anchor_capability_id = entry
777                    .local_anchor_capability_id
778                    .clone()
779                    .or(local_anchor_capability_id);
780                entry.matched_local_receipts = entry.matched_local_receipts.saturating_add(1);
781                entry.first_seen = Some(
782                    entry
783                        .first_seen
784                        .map_or(timestamp, |value| value.min(timestamp)),
785                );
786                entry.last_seen = Some(
787                    entry
788                        .last_seen
789                        .map_or(timestamp, |value| value.max(timestamp)),
790                );
791                match decision.as_str() {
792                    "allow" => entry.allow_count = entry.allow_count.saturating_add(1),
793                    "deny" => entry.deny_count = entry.deny_count.saturating_add(1),
794                    "cancelled" => entry.cancelled_count = entry.cancelled_count.saturating_add(1),
795                    _ => entry.incomplete_count = entry.incomplete_count.saturating_add(1),
796                }
797                matched_this_receipt = true;
798            }
799
800            if matched_this_receipt {
801                matched_local_receipts.insert(receipt_id);
802            }
803        }
804
805        let mut returned_references = references.into_values().collect::<Vec<_>>();
806        returned_references.sort_by(|left, right| {
807            right
808                .matched_local_receipts
809                .cmp(&left.matched_local_receipts)
810                .then_with(|| right.last_seen.cmp(&left.last_seen))
811                .then_with(|| right.share.imported_at.cmp(&left.share.imported_at))
812                .then_with(|| left.share.share_id.cmp(&right.share.share_id))
813                .then_with(|| left.capability_id.cmp(&right.capability_id))
814        });
815
816        let mut distinct_shares = BTreeMap::<String, FederatedEvidenceShareSummary>::new();
817        let mut distinct_remote_subjects = BTreeSet::<String>::new();
818        for reference in &returned_references {
819            distinct_shares
820                .entry(reference.share.share_id.clone())
821                .or_insert_with(|| reference.share.clone());
822            distinct_remote_subjects.insert(reference.subject_key.clone());
823        }
824
825        let matching_references = returned_references.len() as u64;
826        let truncated = returned_references.len() > limit;
827        if truncated {
828            returned_references.truncate(limit);
829        }
830
831        Ok(SharedEvidenceReferenceReport {
832            summary: SharedEvidenceReferenceSummary {
833                matching_shares: distinct_shares.len() as u64,
834                matching_references,
835                matching_local_receipts: matched_local_receipts.len() as u64,
836                remote_tool_receipts: distinct_shares
837                    .values()
838                    .map(|share| share.tool_receipts)
839                    .sum(),
840                remote_lineage_records: distinct_shares
841                    .values()
842                    .map(|share| share.capability_lineage)
843                    .sum(),
844                distinct_remote_subjects: distinct_remote_subjects.len() as u64,
845                proof_required_shares: distinct_shares
846                    .values()
847                    .filter(|share| share.require_proofs)
848                    .count() as u64,
849                truncated,
850            },
851            references: returned_references,
852        })
853    }
854
855    pub fn query_compliance_report(
856        &self,
857        query: &OperatorReportQuery,
858    ) -> Result<ComplianceReport, ReceiptStoreError> {
859        let capability_id = query.capability_id.as_deref();
860        let tool_server = query.tool_server.as_deref();
861        let tool_name = query.tool_name.as_deref();
862        let since = query.since.map(|value| value as i64);
863        let until = query.until.map(|value| value as i64);
864        let agent_subject = query.agent_subject.as_deref();
865
866        let summary_sql = r#"
867            SELECT
868                COUNT(*) AS matching_receipts,
869                COALESCE(SUM(
870                    CASE
871                        WHEN EXISTS(
872                            SELECT 1
873                            FROM kernel_checkpoints kc
874                            WHERE r.seq BETWEEN kc.batch_start_seq AND kc.batch_end_seq
875                        ) THEN 1
876                        ELSE 0
877                    END
878                ), 0) AS evidence_ready_receipts,
879                COALESCE(SUM(CASE WHEN cl.capability_id IS NOT NULL THEN 1 ELSE 0 END), 0) AS lineage_covered_receipts,
880                COALESCE(SUM(
881                    CASE
882                        WHEN json_extract(r.raw_json, '$.metadata.financial.settlement_status') = 'pending' THEN 1
883                        ELSE 0
884                    END
885                ), 0) AS pending_settlement_receipts,
886                COALESCE(SUM(
887                    CASE
888                        WHEN json_extract(r.raw_json, '$.metadata.financial.settlement_status') = 'failed' THEN 1
889                        ELSE 0
890                    END
891                ), 0) AS failed_settlement_receipts
892            FROM chio_tool_receipts r
893            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
894            WHERE (?1 IS NULL OR r.capability_id = ?1)
895              AND (?2 IS NULL OR r.tool_server = ?2)
896              AND (?3 IS NULL OR r.tool_name = ?3)
897              AND (?4 IS NULL OR r.timestamp >= ?4)
898              AND (?5 IS NULL OR r.timestamp <= ?5)
899              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
900        "#;
901
902        let (
903            matching_receipts,
904            evidence_ready_receipts,
905            lineage_covered_receipts,
906            pending_settlement_receipts,
907            failed_settlement_receipts,
908        ) = self.connection()?.query_row(
909            summary_sql,
910            params![
911                capability_id,
912                tool_server,
913                tool_name,
914                since,
915                until,
916                agent_subject
917            ],
918            |row| {
919                Ok((
920                    row.get::<_, i64>(0)?.max(0) as u64,
921                    row.get::<_, i64>(1)?.max(0) as u64,
922                    row.get::<_, i64>(2)?.max(0) as u64,
923                    row.get::<_, i64>(3)?.max(0) as u64,
924                    row.get::<_, i64>(4)?.max(0) as u64,
925                ))
926            },
927        )?;
928
929        let uncheckpointed_receipts = matching_receipts.saturating_sub(evidence_ready_receipts);
930        let lineage_gap_receipts = matching_receipts.saturating_sub(lineage_covered_receipts);
931        let export_query = query.to_evidence_export_query();
932
933        Ok(ComplianceReport {
934            matching_receipts,
935            evidence_ready_receipts,
936            uncheckpointed_receipts,
937            checkpoint_coverage_rate: ratio_option(evidence_ready_receipts, matching_receipts),
938            lineage_covered_receipts,
939            lineage_gap_receipts,
940            lineage_coverage_rate: ratio_option(lineage_covered_receipts, matching_receipts),
941            pending_settlement_receipts,
942            failed_settlement_receipts,
943            direct_evidence_export_supported: query.direct_evidence_export_supported(),
944            child_receipt_scope: export_query.child_receipt_scope(),
945            proofs_complete: uncheckpointed_receipts == 0,
946            export_query: export_query.clone(),
947            export_scope_note: compliance_export_scope_note(query, &export_query),
948        })
949    }
950
951    pub fn upsert_settlement_reconciliation(
952        &self,
953        receipt_id: &str,
954        reconciliation_state: SettlementReconciliationState,
955        note: Option<&str>,
956    ) -> Result<i64, ReceiptStoreError> {
957        let exists = self
958            .connection()?
959            .query_row(
960                "SELECT 1 FROM chio_tool_receipts WHERE receipt_id = ?1",
961                params![receipt_id],
962                |row| row.get::<_, i64>(0),
963            )
964            .optional()?;
965        if exists.is_none() {
966            return Err(ReceiptStoreError::NotFound(format!(
967                "receipt {receipt_id} does not exist"
968            )));
969        }
970
971        let updated_at = unix_timestamp_now_i64();
972        self.connection()?.execute(
973            r#"
974            INSERT INTO settlement_reconciliations (
975                receipt_id,
976                reconciliation_state,
977                note,
978                updated_at
979            ) VALUES (?1, ?2, ?3, ?4)
980            ON CONFLICT(receipt_id) DO UPDATE SET
981                reconciliation_state = excluded.reconciliation_state,
982                note = excluded.note,
983                updated_at = excluded.updated_at
984            "#,
985            params![
986                receipt_id,
987                settlement_reconciliation_state_text(reconciliation_state),
988                note,
989                updated_at
990            ],
991        )?;
992
993        Ok(updated_at)
994    }
995
996    pub fn upsert_metered_billing_reconciliation(
997        &self,
998        receipt_id: &str,
999        evidence: &MeteredBillingEvidenceRecord,
1000        reconciliation_state: MeteredBillingReconciliationState,
1001        note: Option<&str>,
1002    ) -> Result<i64, ReceiptStoreError> {
1003        let (seq, raw_json) = self
1004            .connection()?
1005            .query_row(
1006                "SELECT seq, raw_json FROM chio_tool_receipts WHERE receipt_id = ?1",
1007                params![receipt_id],
1008                |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
1009            )
1010            .optional()?
1011            .ok_or_else(|| {
1012                ReceiptStoreError::NotFound(format!("receipt {receipt_id} does not exist"))
1013            })?;
1014        let receipt = decode_verified_chio_receipt(
1015            &raw_json,
1016            "persisted tool receipt",
1017            Some(seq.max(0) as u64),
1018        )?;
1019        let governed = extract_governed_transaction_metadata(&receipt).ok_or_else(|| {
1020            ReceiptStoreError::Conflict(format!(
1021                "receipt {receipt_id} does not carry governed transaction metadata"
1022            ))
1023        })?;
1024        if governed.metered_billing.is_none() {
1025            return Err(ReceiptStoreError::Conflict(format!(
1026                "receipt {receipt_id} does not carry metered billing context"
1027            )));
1028        }
1029
1030        let existing_receipt = self
1031            .connection()?
1032            .query_row(
1033                r#"
1034                SELECT receipt_id
1035                FROM metered_billing_reconciliations
1036                WHERE adapter_kind = ?1 AND evidence_id = ?2
1037                "#,
1038                params![
1039                    &evidence.usage_evidence.evidence_kind,
1040                    &evidence.usage_evidence.evidence_id
1041                ],
1042                |row| row.get::<_, String>(0),
1043            )
1044            .optional()?;
1045        if let Some(existing_receipt) = existing_receipt {
1046            if existing_receipt != receipt_id {
1047                return Err(ReceiptStoreError::Conflict(format!(
1048                    "metered billing evidence {}/{} is already attached to receipt {}",
1049                    evidence.usage_evidence.evidence_kind,
1050                    evidence.usage_evidence.evidence_id,
1051                    existing_receipt
1052                )));
1053            }
1054        }
1055
1056        let updated_at = unix_timestamp_now_i64();
1057        self.connection()?.execute(
1058            r#"
1059            INSERT INTO metered_billing_reconciliations (
1060                receipt_id,
1061                adapter_kind,
1062                evidence_id,
1063                observed_units,
1064                billed_cost_units,
1065                billed_cost_currency,
1066                evidence_sha256,
1067                recorded_at,
1068                reconciliation_state,
1069                note,
1070                updated_at
1071            ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
1072            ON CONFLICT(receipt_id) DO UPDATE SET
1073                adapter_kind = excluded.adapter_kind,
1074                evidence_id = excluded.evidence_id,
1075                observed_units = excluded.observed_units,
1076                billed_cost_units = excluded.billed_cost_units,
1077                billed_cost_currency = excluded.billed_cost_currency,
1078                evidence_sha256 = excluded.evidence_sha256,
1079                recorded_at = excluded.recorded_at,
1080                reconciliation_state = excluded.reconciliation_state,
1081                note = excluded.note,
1082                updated_at = excluded.updated_at
1083            "#,
1084            params![
1085                receipt_id,
1086                &evidence.usage_evidence.evidence_kind,
1087                &evidence.usage_evidence.evidence_id,
1088                evidence.usage_evidence.observed_units as i64,
1089                evidence.billed_cost.units as i64,
1090                &evidence.billed_cost.currency,
1091                evidence.usage_evidence.evidence_sha256.as_deref(),
1092                evidence.recorded_at as i64,
1093                metered_billing_reconciliation_state_text(reconciliation_state),
1094                note,
1095                updated_at
1096            ],
1097        )?;
1098
1099        Ok(updated_at)
1100    }
1101
1102    pub fn query_metered_billing_reconciliation_report(
1103        &self,
1104        query: &OperatorReportQuery,
1105    ) -> Result<MeteredBillingReconciliationReport, ReceiptStoreError> {
1106        let capability_id = query.capability_id.as_deref();
1107        let tool_server = query.tool_server.as_deref();
1108        let tool_name = query.tool_name.as_deref();
1109        let since = query.since.map(|value| value as i64);
1110        let until = query.until.map(|value| value as i64);
1111        let agent_subject = query.agent_subject.as_deref();
1112        let row_limit = query.metered_limit_or_default();
1113
1114        let summary = self.query_metered_billing_summary(query)?;
1115
1116        let rows_sql = r#"
1117            SELECT
1118                r.seq,
1119                r.raw_json,
1120                COALESCE(r.subject_key, cl.subject_key),
1121                mbr.adapter_kind,
1122                mbr.evidence_id,
1123                mbr.observed_units,
1124                mbr.billed_cost_units,
1125                mbr.billed_cost_currency,
1126                mbr.evidence_sha256,
1127                mbr.recorded_at,
1128                COALESCE(mbr.reconciliation_state, 'open'),
1129                mbr.note,
1130                mbr.updated_at
1131            FROM chio_tool_receipts r
1132            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1133            LEFT JOIN metered_billing_reconciliations mbr ON r.receipt_id = mbr.receipt_id
1134            WHERE json_type(r.raw_json, '$.metadata.governed_transaction.metered_billing') = 'object'
1135              AND (?1 IS NULL OR r.capability_id = ?1)
1136              AND (?2 IS NULL OR r.tool_server = ?2)
1137              AND (?3 IS NULL OR r.tool_name = ?3)
1138              AND (?4 IS NULL OR r.timestamp >= ?4)
1139              AND (?5 IS NULL OR r.timestamp <= ?5)
1140              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1141            ORDER BY r.timestamp DESC, r.seq DESC
1142            LIMIT ?7
1143        "#;
1144
1145        let connection = self.connection()?;
1146        let mut stmt = connection.prepare(rows_sql)?;
1147        let rows = stmt.query_map(
1148            params![
1149                capability_id,
1150                tool_server,
1151                tool_name,
1152                since,
1153                until,
1154                agent_subject,
1155                row_limit as i64
1156            ],
1157            |row| {
1158                Ok((
1159                    row.get::<_, i64>(0)?,
1160                    row.get::<_, String>(1)?,
1161                    row.get::<_, Option<String>>(2)?,
1162                    row.get::<_, Option<String>>(3)?,
1163                    row.get::<_, Option<String>>(4)?,
1164                    row.get::<_, Option<i64>>(5)?,
1165                    row.get::<_, Option<i64>>(6)?,
1166                    row.get::<_, Option<String>>(7)?,
1167                    row.get::<_, Option<String>>(8)?,
1168                    row.get::<_, Option<i64>>(9)?,
1169                    row.get::<_, String>(10)?,
1170                    row.get::<_, Option<String>>(11)?,
1171                    row.get::<_, Option<i64>>(12)?,
1172                ))
1173            },
1174        )?;
1175
1176        let mut receipts = Vec::new();
1177        for row in rows {
1178            let (
1179                seq,
1180                raw_json,
1181                subject_key,
1182                adapter_kind,
1183                evidence_id,
1184                observed_units,
1185                billed_cost_units,
1186                billed_cost_currency,
1187                evidence_sha256,
1188                recorded_at,
1189                reconciliation_state_text,
1190                note,
1191                updated_at,
1192            ) = row?;
1193            let receipt = decode_verified_chio_receipt(
1194                &raw_json,
1195                "persisted tool receipt",
1196                Some(seq.max(0) as u64),
1197            )?;
1198            let governed = extract_governed_transaction_metadata(&receipt).ok_or_else(|| {
1199                ReceiptStoreError::Canonical(format!(
1200                    "receipt {} is missing governed transaction metadata",
1201                    receipt.id
1202                ))
1203            })?;
1204            let metered = governed.metered_billing.ok_or_else(|| {
1205                ReceiptStoreError::Canonical(format!(
1206                    "receipt {} is missing metered billing metadata",
1207                    receipt.id
1208                ))
1209            })?;
1210            let financial = extract_financial_metadata(&receipt);
1211            let evidence = metered_billing_evidence_record_from_columns(
1212                adapter_kind,
1213                evidence_id,
1214                observed_units,
1215                billed_cost_units,
1216                billed_cost_currency,
1217                evidence_sha256,
1218                recorded_at,
1219            );
1220            let reconciliation_state =
1221                parse_metered_billing_reconciliation_state(&reconciliation_state_text)?;
1222            let analysis = analyze_metered_billing_reconciliation(
1223                &metered,
1224                financial.as_ref(),
1225                evidence.as_ref(),
1226                reconciliation_state,
1227            );
1228            let budget_authority = receipt.financial_budget_authority_metadata();
1229
1230            receipts.push(MeteredBillingReconciliationRow {
1231                receipt_id: receipt.id,
1232                timestamp: receipt.timestamp,
1233                capability_id: receipt.capability_id,
1234                subject_key,
1235                tool_server: receipt.tool_server,
1236                tool_name: receipt.tool_name,
1237                settlement_mode: metered.settlement_mode,
1238                provider: metered.quote.provider.clone(),
1239                quote_id: metered.quote.quote_id.clone(),
1240                billing_unit: metered.quote.billing_unit.clone(),
1241                quoted_units: metered.quote.quoted_units,
1242                quoted_cost: metered.quote.quoted_cost.clone(),
1243                max_billed_units: metered.max_billed_units,
1244                financial_cost_charged: financial.as_ref().map(|value| value.cost_charged),
1245                financial_currency: financial.as_ref().map(|value| value.currency.clone()),
1246                budget_authority,
1247                evidence,
1248                reconciliation_state,
1249                action_required: analysis.action_required,
1250                evidence_missing: analysis.evidence_missing,
1251                exceeds_quoted_units: analysis.exceeds_quoted_units,
1252                exceeds_max_billed_units: analysis.exceeds_max_billed_units,
1253                exceeds_quoted_cost: analysis.exceeds_quoted_cost,
1254                financial_mismatch: analysis.financial_mismatch,
1255                note,
1256                updated_at: updated_at.map(|value| value.max(0) as u64),
1257            });
1258        }
1259
1260        Ok(MeteredBillingReconciliationReport {
1261            summary: MeteredBillingReconciliationSummary {
1262                matching_receipts: summary.metered_receipts,
1263                returned_receipts: receipts.len() as u64,
1264                evidence_attached_receipts: summary.evidence_attached_receipts,
1265                missing_evidence_receipts: summary.missing_evidence_receipts,
1266                over_quoted_units_receipts: summary.over_quoted_units_receipts,
1267                over_max_billed_units_receipts: summary.over_max_billed_units_receipts,
1268                over_quoted_cost_receipts: summary.over_quoted_cost_receipts,
1269                financial_mismatch_receipts: summary.financial_mismatch_receipts,
1270                actionable_receipts: summary.actionable_receipts,
1271                reconciled_receipts: summary.reconciled_receipts,
1272                truncated: summary.metered_receipts > receipts.len() as u64,
1273            },
1274            receipts,
1275        })
1276    }
1277
1278    pub fn query_economic_receipt_projection_report(
1279        &self,
1280        query: &OperatorReportQuery,
1281    ) -> Result<EconomicReceiptProjectionReport, ReceiptStoreError> {
1282        let capability_id = query.capability_id.as_deref();
1283        let tool_server = query.tool_server.as_deref();
1284        let tool_name = query.tool_name.as_deref();
1285        let since = query.since.map(|value| value as i64);
1286        let until = query.until.map(|value| value as i64);
1287        let agent_subject = query.agent_subject.as_deref();
1288        let row_limit = query.economic_limit_or_default();
1289
1290        let matching_receipts = self.connection()?.query_row(
1291            r#"
1292            SELECT COUNT(*)
1293            FROM chio_tool_receipts r
1294            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1295            WHERE json_type(r.raw_json, '$.metadata.governed_transaction.economic_authorization') = 'object'
1296              AND (?1 IS NULL OR r.capability_id = ?1)
1297              AND (?2 IS NULL OR r.tool_server = ?2)
1298              AND (?3 IS NULL OR r.tool_name = ?3)
1299              AND (?4 IS NULL OR r.timestamp >= ?4)
1300              AND (?5 IS NULL OR r.timestamp <= ?5)
1301              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1302            "#,
1303            params![
1304                capability_id,
1305                tool_server,
1306                tool_name,
1307                since,
1308                until,
1309                agent_subject
1310            ],
1311            |row| row.get::<_, i64>(0),
1312        )?
1313        .max(0) as u64;
1314
1315        let rows_sql = r#"
1316            SELECT
1317                r.seq,
1318                r.receipt_id,
1319                r.timestamp,
1320                r.capability_id,
1321                COALESCE(r.subject_key, cl.subject_key),
1322                r.tool_server,
1323                r.tool_name,
1324                COALESCE(sr.reconciliation_state, 'open'),
1325                sr.note,
1326                sr.updated_at,
1327                COALESCE(mbr.reconciliation_state, 'open'),
1328                mbr.note,
1329                mbr.updated_at,
1330                mbr.adapter_kind,
1331                mbr.evidence_id,
1332                mbr.observed_units,
1333                mbr.billed_cost_units,
1334                mbr.billed_cost_currency,
1335                mbr.evidence_sha256,
1336                mbr.recorded_at,
1337                r.raw_json
1338            FROM chio_tool_receipts r
1339            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1340            LEFT JOIN settlement_reconciliations sr ON r.receipt_id = sr.receipt_id
1341            LEFT JOIN metered_billing_reconciliations mbr ON r.receipt_id = mbr.receipt_id
1342            WHERE json_type(r.raw_json, '$.metadata.governed_transaction.economic_authorization') = 'object'
1343              AND (?1 IS NULL OR r.capability_id = ?1)
1344              AND (?2 IS NULL OR r.tool_server = ?2)
1345              AND (?3 IS NULL OR r.tool_name = ?3)
1346              AND (?4 IS NULL OR r.timestamp >= ?4)
1347              AND (?5 IS NULL OR r.timestamp <= ?5)
1348              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1349            ORDER BY r.timestamp DESC, r.seq DESC
1350            LIMIT ?7
1351        "#;
1352
1353        let connection = self.connection()?;
1354        let mut stmt = connection.prepare(rows_sql)?;
1355        let rows = stmt.query_map(
1356            params![
1357                capability_id,
1358                tool_server,
1359                tool_name,
1360                since,
1361                until,
1362                agent_subject,
1363                row_limit as i64
1364            ],
1365            |row| {
1366                Ok((
1367                    row.get::<_, i64>(0)?,
1368                    row.get::<_, String>(1)?,
1369                    row.get::<_, i64>(2)?,
1370                    row.get::<_, String>(3)?,
1371                    row.get::<_, Option<String>>(4)?,
1372                    row.get::<_, String>(5)?,
1373                    row.get::<_, String>(6)?,
1374                    row.get::<_, String>(7)?,
1375                    row.get::<_, Option<String>>(8)?,
1376                    row.get::<_, Option<i64>>(9)?,
1377                    row.get::<_, String>(10)?,
1378                    row.get::<_, Option<String>>(11)?,
1379                    row.get::<_, Option<i64>>(12)?,
1380                    row.get::<_, Option<String>>(13)?,
1381                    row.get::<_, Option<String>>(14)?,
1382                    row.get::<_, Option<i64>>(15)?,
1383                    row.get::<_, Option<i64>>(16)?,
1384                    row.get::<_, Option<String>>(17)?,
1385                    row.get::<_, Option<String>>(18)?,
1386                    row.get::<_, Option<i64>>(19)?,
1387                    row.get::<_, String>(20)?,
1388                ))
1389            },
1390        )?;
1391
1392        let mut receipts = Vec::new();
1393        let mut metered_receipts = 0_u64;
1394        let mut pending_settlement_receipts = 0_u64;
1395        let mut failed_settlement_receipts = 0_u64;
1396        let mut settlement_actionable_receipts = 0_u64;
1397        let mut metering_actionable_receipts = 0_u64;
1398        let mut metering_evidence_missing_receipts = 0_u64;
1399        let mut metering_financial_mismatch_receipts = 0_u64;
1400
1401        for row in rows {
1402            let (
1403                seq,
1404                receipt_id,
1405                timestamp,
1406                capability_id,
1407                subject_key,
1408                tool_server,
1409                tool_name,
1410                settlement_reconciliation_state_text,
1411                settlement_note,
1412                settlement_updated_at,
1413                metering_reconciliation_state_text,
1414                metering_note,
1415                metering_updated_at,
1416                adapter_kind,
1417                evidence_id,
1418                observed_units,
1419                billed_cost_units,
1420                billed_cost_currency,
1421                evidence_sha256,
1422                recorded_at,
1423                raw_json,
1424            ) = row?;
1425            let receipt = decode_verified_chio_receipt(
1426                &raw_json,
1427                "persisted tool receipt",
1428                Some(seq.max(0) as u64),
1429            )?;
1430            let governed = extract_governed_transaction_metadata(&receipt).ok_or_else(|| {
1431                ReceiptStoreError::Canonical(format!(
1432                    "receipt {} is missing governed transaction metadata",
1433                    receipt.id
1434                ))
1435            })?;
1436            let economic_authorization = governed
1437                .economic_authorization
1438                .clone()
1439                .or_else(|| extract_economic_authorization_metadata(&receipt))
1440                .ok_or_else(|| {
1441                    ReceiptStoreError::Canonical(format!(
1442                        "receipt {} is missing economic authorization metadata",
1443                        receipt.id
1444                    ))
1445                })?;
1446            let financial = extract_financial_metadata(&receipt);
1447            let settlement_reconciliation_state =
1448                parse_settlement_reconciliation_state(&settlement_reconciliation_state_text)?;
1449            let settlement = EconomicReceiptSettlementProjection {
1450                settlement_status: economic_authorization.settlement.settlement_status.clone(),
1451                reconciliation_state: settlement_reconciliation_state,
1452                action_required: settlement_reconciliation_action_required(
1453                    economic_authorization.settlement.settlement_status.clone(),
1454                    settlement_reconciliation_state,
1455                ),
1456                note: settlement_note,
1457                updated_at: settlement_updated_at.map(|value| value.max(0) as u64),
1458            };
1459            if settlement.settlement_status == SettlementStatus::Pending {
1460                pending_settlement_receipts = pending_settlement_receipts.saturating_add(1);
1461            }
1462            if settlement.settlement_status == SettlementStatus::Failed {
1463                failed_settlement_receipts = failed_settlement_receipts.saturating_add(1);
1464            }
1465            if settlement.action_required {
1466                settlement_actionable_receipts = settlement_actionable_receipts.saturating_add(1);
1467            }
1468
1469            let metering = if economic_authorization.metering.is_some() {
1470                metered_receipts = metered_receipts.saturating_add(1);
1471                let governed_metering = governed.metered_billing.as_ref().ok_or_else(|| {
1472                    ReceiptStoreError::Canonical(format!(
1473                        "receipt {} has economic metering metadata without governed metered billing context",
1474                        receipt.id
1475                    ))
1476                })?;
1477                let evidence = metered_billing_evidence_record_from_columns(
1478                    adapter_kind,
1479                    evidence_id,
1480                    observed_units,
1481                    billed_cost_units,
1482                    billed_cost_currency,
1483                    evidence_sha256,
1484                    recorded_at,
1485                );
1486                let reconciliation_state = parse_metered_billing_reconciliation_state(
1487                    &metering_reconciliation_state_text,
1488                )?;
1489                let analysis = analyze_metered_billing_reconciliation(
1490                    governed_metering,
1491                    financial.as_ref(),
1492                    evidence.as_ref(),
1493                    reconciliation_state,
1494                );
1495                if analysis.action_required {
1496                    metering_actionable_receipts = metering_actionable_receipts.saturating_add(1);
1497                }
1498                if analysis.evidence_missing {
1499                    metering_evidence_missing_receipts =
1500                        metering_evidence_missing_receipts.saturating_add(1);
1501                }
1502                if analysis.financial_mismatch {
1503                    metering_financial_mismatch_receipts =
1504                        metering_financial_mismatch_receipts.saturating_add(1);
1505                }
1506                Some(EconomicReceiptMeteringProjection {
1507                    reconciliation_state,
1508                    action_required: analysis.action_required,
1509                    evidence_missing: analysis.evidence_missing,
1510                    exceeds_quoted_units: analysis.exceeds_quoted_units,
1511                    exceeds_max_billed_units: analysis.exceeds_max_billed_units,
1512                    exceeds_quoted_cost: analysis.exceeds_quoted_cost,
1513                    financial_mismatch: analysis.financial_mismatch,
1514                    evidence,
1515                    note: metering_note,
1516                    updated_at: metering_updated_at.map(|value| value.max(0) as u64),
1517                })
1518            } else {
1519                None
1520            };
1521
1522            receipts.push(EconomicReceiptProjectionRow {
1523                receipt_id,
1524                timestamp: timestamp.max(0) as u64,
1525                capability_id,
1526                subject_key,
1527                tool_server,
1528                tool_name,
1529                economic_authorization,
1530                budget_authority: receipt.financial_budget_authority_metadata(),
1531                settlement,
1532                metering,
1533            });
1534        }
1535
1536        Ok(EconomicReceiptProjectionReport {
1537            summary: EconomicReceiptProjectionSummary {
1538                matching_receipts,
1539                returned_receipts: receipts.len() as u64,
1540                metered_receipts,
1541                pending_settlement_receipts,
1542                failed_settlement_receipts,
1543                settlement_actionable_receipts,
1544                metering_actionable_receipts,
1545                metering_evidence_missing_receipts,
1546                metering_financial_mismatch_receipts,
1547                truncated: matching_receipts > receipts.len() as u64,
1548            },
1549            receipts,
1550        })
1551    }
1552
1553    pub fn query_economic_completion_flow_report(
1554        &self,
1555        query: &ExposureLedgerQuery,
1556    ) -> Result<EconomicCompletionFlowReport, ReceiptStoreError> {
1557        let normalized = query.normalized();
1558        if let Err(message) = normalized.validate() {
1559            return Err(ReceiptStoreError::Conflict(message));
1560        }
1561
1562        let economic_receipts =
1563            self.query_economic_receipt_projection_report(&OperatorReportQuery {
1564                capability_id: normalized.capability_id.clone(),
1565                agent_subject: normalized.agent_subject.clone(),
1566                tool_server: normalized.tool_server.clone(),
1567                tool_name: normalized.tool_name.clone(),
1568                since: normalized.since,
1569                until: normalized.until,
1570                economic_limit: normalized.receipt_limit,
1571                ..OperatorReportQuery::default()
1572            })?;
1573        let underwriting_decisions =
1574            self.query_underwriting_decisions(&UnderwritingDecisionQuery {
1575                decision_id: None,
1576                capability_id: normalized.capability_id.clone(),
1577                agent_subject: normalized.agent_subject.clone(),
1578                tool_server: normalized.tool_server.clone(),
1579                tool_name: normalized.tool_name.clone(),
1580                outcome: None,
1581                lifecycle_state: None,
1582                appeal_status: None,
1583                limit: normalized.decision_limit,
1584            })?;
1585        let credit_facilities = self.query_credit_facilities(&CreditFacilityListQuery {
1586            facility_id: None,
1587            capability_id: normalized.capability_id.clone(),
1588            agent_subject: normalized.agent_subject.clone(),
1589            tool_server: normalized.tool_server.clone(),
1590            tool_name: normalized.tool_name.clone(),
1591            disposition: None,
1592            lifecycle_state: None,
1593            limit: normalized.decision_limit,
1594        })?;
1595        let credit_bonds = self.query_credit_bonds(&CreditBondListQuery {
1596            bond_id: None,
1597            facility_id: None,
1598            capability_id: normalized.capability_id.clone(),
1599            agent_subject: normalized.agent_subject.clone(),
1600            tool_server: normalized.tool_server.clone(),
1601            tool_name: normalized.tool_name.clone(),
1602            disposition: None,
1603            lifecycle_state: None,
1604            limit: normalized.decision_limit,
1605        })?;
1606
1607        let latest_underwriting = underwriting_decisions
1608            .decisions
1609            .iter()
1610            .find(|row| row.lifecycle_state == UnderwritingDecisionLifecycleState::Active)
1611            .or_else(|| underwriting_decisions.decisions.first());
1612        let latest_credit_facility = credit_facilities
1613            .facilities
1614            .iter()
1615            .find(|row| row.lifecycle_state == CreditFacilityLifecycleState::Active)
1616            .or_else(|| credit_facilities.facilities.first());
1617        let latest_credit_bond = credit_bonds
1618            .bonds
1619            .iter()
1620            .find(|row| row.lifecycle_state == CreditBondLifecycleState::Active)
1621            .or_else(|| credit_bonds.bonds.first());
1622
1623        Ok(EconomicCompletionFlowReport {
1624            schema: ECONOMIC_COMPLETION_FLOW_SCHEMA.to_string(),
1625            generated_at: unix_now(),
1626            filters: normalized,
1627            summary: EconomicCompletionFlowSummary {
1628                matching_receipts: economic_receipts.summary.matching_receipts,
1629                returned_receipts: economic_receipts.summary.returned_receipts,
1630                matching_underwriting_decisions: underwriting_decisions.summary.matching_decisions,
1631                returned_underwriting_decisions: underwriting_decisions.summary.returned_decisions,
1632                matching_credit_facilities: credit_facilities.summary.matching_facilities,
1633                returned_credit_facilities: credit_facilities.summary.returned_facilities,
1634                matching_credit_bonds: credit_bonds.summary.matching_bonds,
1635                returned_credit_bonds: credit_bonds.summary.returned_bonds,
1636                pending_settlement_receipts: economic_receipts.summary.pending_settlement_receipts,
1637                failed_settlement_receipts: economic_receipts.summary.failed_settlement_receipts,
1638                metering_actionable_receipts: economic_receipts
1639                    .summary
1640                    .metering_actionable_receipts,
1641                latest_underwriting_decision_id: latest_underwriting
1642                    .map(|row| row.decision.body.decision_id.clone()),
1643                latest_underwriting_outcome: latest_underwriting
1644                    .map(|row| row.decision.body.evaluation.outcome),
1645                latest_credit_facility_id: latest_credit_facility
1646                    .map(|row| row.facility.body.facility_id.clone()),
1647                latest_credit_facility_disposition: latest_credit_facility
1648                    .map(|row| row.facility.body.report.disposition),
1649                latest_credit_bond_id: latest_credit_bond.map(|row| row.bond.body.bond_id.clone()),
1650                latest_credit_bond_disposition: latest_credit_bond
1651                    .map(|row| row.bond.body.report.disposition),
1652            },
1653            economic_receipts,
1654            underwriting_decisions,
1655            credit_facilities,
1656            credit_bonds,
1657        })
1658    }
1659
1660    pub fn query_settlement_reconciliation_report(
1661        &self,
1662        query: &OperatorReportQuery,
1663    ) -> Result<SettlementReconciliationReport, ReceiptStoreError> {
1664        let capability_id = query.capability_id.as_deref();
1665        let tool_server = query.tool_server.as_deref();
1666        let tool_name = query.tool_name.as_deref();
1667        let since = query.since.map(|value| value as i64);
1668        let until = query.until.map(|value| value as i64);
1669        let agent_subject = query.agent_subject.as_deref();
1670        let row_limit = query.settlement_limit_or_default();
1671
1672        let summary_sql = r#"
1673            SELECT
1674                COUNT(*) AS matching_receipts,
1675                COALESCE(SUM(
1676                    CASE
1677                        WHEN json_extract(r.raw_json, '$.metadata.financial.settlement_status') = 'pending' THEN 1
1678                        ELSE 0
1679                    END
1680                ), 0) AS pending_receipts,
1681                COALESCE(SUM(
1682                    CASE
1683                        WHEN json_extract(r.raw_json, '$.metadata.financial.settlement_status') = 'failed' THEN 1
1684                        ELSE 0
1685                    END
1686                ), 0) AS failed_receipts,
1687                COALESCE(SUM(
1688                    CASE
1689                        WHEN COALESCE(sr.reconciliation_state, 'open') NOT IN ('reconciled', 'ignored') THEN 1
1690                        ELSE 0
1691                    END
1692                ), 0) AS actionable_receipts,
1693                COALESCE(SUM(
1694                    CASE
1695                        WHEN COALESCE(sr.reconciliation_state, 'open') = 'reconciled' THEN 1
1696                        ELSE 0
1697                    END
1698                ), 0) AS reconciled_receipts
1699            FROM chio_tool_receipts r
1700            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1701            LEFT JOIN settlement_reconciliations sr ON r.receipt_id = sr.receipt_id
1702            WHERE json_extract(r.raw_json, '$.metadata.financial.settlement_status') IN ('pending', 'failed')
1703              AND (?1 IS NULL OR r.capability_id = ?1)
1704              AND (?2 IS NULL OR r.tool_server = ?2)
1705              AND (?3 IS NULL OR r.tool_name = ?3)
1706              AND (?4 IS NULL OR r.timestamp >= ?4)
1707              AND (?5 IS NULL OR r.timestamp <= ?5)
1708              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1709        "#;
1710
1711        let (
1712            matching_receipts,
1713            pending_receipts,
1714            failed_receipts,
1715            actionable_receipts,
1716            reconciled_receipts,
1717        ) = self.connection()?.query_row(
1718            summary_sql,
1719            params![
1720                capability_id,
1721                tool_server,
1722                tool_name,
1723                since,
1724                until,
1725                agent_subject
1726            ],
1727            |row| {
1728                Ok((
1729                    row.get::<_, i64>(0)?.max(0) as u64,
1730                    row.get::<_, i64>(1)?.max(0) as u64,
1731                    row.get::<_, i64>(2)?.max(0) as u64,
1732                    row.get::<_, i64>(3)?.max(0) as u64,
1733                    row.get::<_, i64>(4)?.max(0) as u64,
1734                ))
1735            },
1736        )?;
1737
1738        let rows_sql = r#"
1739            SELECT
1740                r.seq,
1741                r.receipt_id,
1742                r.timestamp,
1743                r.capability_id,
1744                COALESCE(r.subject_key, cl.subject_key),
1745                r.tool_server,
1746                r.tool_name,
1747                json_extract(r.raw_json, '$.metadata.financial.payment_reference'),
1748                json_extract(r.raw_json, '$.metadata.financial.settlement_status'),
1749                CAST(json_extract(r.raw_json, '$.metadata.financial.cost_charged') AS INTEGER),
1750                json_extract(r.raw_json, '$.metadata.financial.currency'),
1751                COALESCE(sr.reconciliation_state, 'open'),
1752                sr.note,
1753                sr.updated_at,
1754                r.raw_json
1755            FROM chio_tool_receipts r
1756            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1757            LEFT JOIN settlement_reconciliations sr ON r.receipt_id = sr.receipt_id
1758            WHERE json_extract(r.raw_json, '$.metadata.financial.settlement_status') IN ('pending', 'failed')
1759              AND (?1 IS NULL OR r.capability_id = ?1)
1760              AND (?2 IS NULL OR r.tool_server = ?2)
1761              AND (?3 IS NULL OR r.tool_name = ?3)
1762              AND (?4 IS NULL OR r.timestamp >= ?4)
1763              AND (?5 IS NULL OR r.timestamp <= ?5)
1764              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1765            ORDER BY r.timestamp DESC, r.seq DESC
1766            LIMIT ?7
1767        "#;
1768
1769        let connection = self.connection()?;
1770        let mut stmt = connection.prepare(rows_sql)?;
1771        let rows = stmt.query_map(
1772            params![
1773                capability_id,
1774                tool_server,
1775                tool_name,
1776                since,
1777                until,
1778                agent_subject,
1779                row_limit as i64
1780            ],
1781            |row| {
1782                Ok((
1783                    row.get::<_, i64>(0)?,
1784                    row.get::<_, String>(1)?,
1785                    row.get::<_, i64>(2)?,
1786                    row.get::<_, String>(3)?,
1787                    row.get::<_, Option<String>>(4)?,
1788                    row.get::<_, String>(5)?,
1789                    row.get::<_, String>(6)?,
1790                    row.get::<_, Option<String>>(7)?,
1791                    row.get::<_, String>(8)?,
1792                    row.get::<_, Option<i64>>(9)?,
1793                    row.get::<_, Option<String>>(10)?,
1794                    row.get::<_, String>(11)?,
1795                    row.get::<_, Option<String>>(12)?,
1796                    row.get::<_, Option<i64>>(13)?,
1797                    row.get::<_, String>(14)?,
1798                ))
1799            },
1800        )?;
1801
1802        let mut receipts = Vec::new();
1803        for row in rows {
1804            let (
1805                seq,
1806                receipt_id,
1807                timestamp,
1808                capability_id,
1809                subject_key,
1810                tool_server,
1811                tool_name,
1812                payment_reference,
1813                settlement_status_text,
1814                cost_charged,
1815                currency,
1816                reconciliation_state_text,
1817                note,
1818                updated_at,
1819                raw_json,
1820            ) = row?;
1821            let receipt = decode_verified_chio_receipt(
1822                &raw_json,
1823                "persisted tool receipt",
1824                Some(seq.max(0) as u64),
1825            )?;
1826            let settlement_status = parse_settlement_status(&settlement_status_text)?;
1827            let reconciliation_state =
1828                parse_settlement_reconciliation_state(&reconciliation_state_text)?;
1829            let action_required = settlement_reconciliation_action_required(
1830                settlement_status.clone(),
1831                reconciliation_state,
1832            );
1833            receipts.push(SettlementReconciliationRow {
1834                receipt_id,
1835                timestamp: timestamp.max(0) as u64,
1836                capability_id,
1837                subject_key,
1838                tool_server,
1839                tool_name,
1840                payment_reference,
1841                settlement_status,
1842                cost_charged: cost_charged.map(|value| value.max(0) as u64),
1843                currency,
1844                budget_authority: receipt.financial_budget_authority_metadata(),
1845                reconciliation_state,
1846                action_required,
1847                note,
1848                updated_at: updated_at.map(|value| value.max(0) as u64),
1849            });
1850        }
1851
1852        Ok(SettlementReconciliationReport {
1853            summary: SettlementReconciliationSummary {
1854                matching_receipts,
1855                returned_receipts: receipts.len() as u64,
1856                pending_receipts,
1857                failed_receipts,
1858                actionable_receipts,
1859                reconciled_receipts,
1860                truncated: matching_receipts > receipts.len() as u64,
1861            },
1862            receipts,
1863        })
1864    }
1865
1866    pub fn query_authorization_context_report(
1867        &self,
1868        query: &OperatorReportQuery,
1869    ) -> Result<AuthorizationContextReport, ReceiptStoreError> {
1870        let capability_id = query.capability_id.as_deref();
1871        let tool_server = query.tool_server.as_deref();
1872        let tool_name = query.tool_name.as_deref();
1873        let since = query.since.map(|value| value as i64);
1874        let until = query.until.map(|value| value as i64);
1875        let agent_subject = query.agent_subject.as_deref();
1876        let row_limit = query.authorization_limit_or_default();
1877
1878        let summary_sql = r#"
1879            SELECT
1880                COUNT(*),
1881                COALESCE(SUM(
1882                    CASE
1883                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.approval') = 'object' THEN 1
1884                        ELSE 0
1885                    END
1886                ), 0),
1887                COALESCE(SUM(
1888                    CASE
1889                        WHEN json_extract(r.raw_json, '$.metadata.governed_transaction.approval.approved') = 1 THEN 1
1890                        ELSE 0
1891                    END
1892                ), 0),
1893                COALESCE(SUM(
1894                    CASE
1895                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.commerce') = 'object' THEN 1
1896                        ELSE 0
1897                    END
1898                ), 0),
1899                COALESCE(SUM(
1900                    CASE
1901                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.metered_billing') = 'object' THEN 1
1902                        ELSE 0
1903                    END
1904                ), 0),
1905                COALESCE(SUM(
1906                    CASE
1907                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.runtime_assurance') = 'object' THEN 1
1908                        ELSE 0
1909                    END
1910                ), 0),
1911                COALESCE(SUM(
1912                    CASE
1913                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.call_chain') = 'object' THEN 1
1914                        ELSE 0
1915                    END
1916                ), 0),
1917                COALESCE(SUM(
1918                    CASE
1919                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.max_amount') = 'object' THEN 1
1920                        ELSE 0
1921                    END
1922                ), 0)
1923            FROM chio_tool_receipts r
1924            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1925            WHERE json_type(r.raw_json, '$.metadata.governed_transaction') = 'object'
1926              AND (?1 IS NULL OR r.capability_id = ?1)
1927              AND (?2 IS NULL OR r.tool_server = ?2)
1928              AND (?3 IS NULL OR r.tool_name = ?3)
1929              AND (?4 IS NULL OR r.timestamp >= ?4)
1930              AND (?5 IS NULL OR r.timestamp <= ?5)
1931              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1932        "#;
1933
1934        let (
1935            matching_receipts,
1936            approval_receipts,
1937            approved_receipts,
1938            commerce_receipts,
1939            metered_billing_receipts,
1940            runtime_assurance_receipts,
1941            _call_chain_receipts,
1942            max_amount_receipts,
1943        ) = self.connection()?.query_row(
1944            summary_sql,
1945            params![
1946                capability_id,
1947                tool_server,
1948                tool_name,
1949                since,
1950                until,
1951                agent_subject
1952            ],
1953            |row| {
1954                Ok((
1955                    row.get::<_, i64>(0)?.max(0) as u64,
1956                    row.get::<_, i64>(1)?.max(0) as u64,
1957                    row.get::<_, i64>(2)?.max(0) as u64,
1958                    row.get::<_, i64>(3)?.max(0) as u64,
1959                    row.get::<_, i64>(4)?.max(0) as u64,
1960                    row.get::<_, i64>(5)?.max(0) as u64,
1961                    row.get::<_, i64>(6)?.max(0) as u64,
1962                    row.get::<_, i64>(7)?.max(0) as u64,
1963                ))
1964            },
1965        )?;
1966
1967        let rows_sql = r#"
1968            SELECT
1969                r.seq,
1970                r.raw_json,
1971                r.subject_key,
1972                r.issuer_key,
1973                cl.subject_key,
1974                cl.issuer_key,
1975                r.grant_index,
1976                cl.grants_json,
1977                rls.statement_id,
1978                rls.session_anchor_id
1979            FROM chio_tool_receipts r
1980            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
1981            LEFT JOIN receipt_lineage_statements rls ON r.receipt_id = rls.receipt_id
1982            WHERE json_type(r.raw_json, '$.metadata.governed_transaction') = 'object'
1983              AND (?1 IS NULL OR r.capability_id = ?1)
1984              AND (?2 IS NULL OR r.tool_server = ?2)
1985              AND (?3 IS NULL OR r.tool_name = ?3)
1986              AND (?4 IS NULL OR r.timestamp >= ?4)
1987              AND (?5 IS NULL OR r.timestamp <= ?5)
1988              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
1989            ORDER BY r.timestamp DESC, r.seq DESC
1990        "#;
1991
1992        let connection = self.connection()?;
1993        let mut stmt = connection.prepare(rows_sql)?;
1994        let rows = stmt.query_map(
1995            params![
1996                capability_id,
1997                tool_server,
1998                tool_name,
1999                since,
2000                until,
2001                agent_subject
2002            ],
2003            |row| {
2004                Ok((
2005                    row.get::<_, i64>(0)?,
2006                    row.get::<_, String>(1)?,
2007                    row.get::<_, Option<String>>(2)?,
2008                    row.get::<_, Option<String>>(3)?,
2009                    row.get::<_, Option<String>>(4)?,
2010                    row.get::<_, Option<String>>(5)?,
2011                    row.get::<_, Option<i64>>(6)?,
2012                    row.get::<_, Option<String>>(7)?,
2013                    row.get::<_, Option<String>>(8)?,
2014                    row.get::<_, Option<String>>(9)?,
2015                ))
2016            },
2017        )?;
2018
2019        let mut sender_bound_receipts = 0_u64;
2020        let mut dpop_bound_receipts = 0_u64;
2021        let mut runtime_assurance_bound_receipts = 0_u64;
2022        let mut delegated_sender_bound_receipts = 0_u64;
2023        let mut call_chain_receipts = 0_u64;
2024        let mut asserted_call_chain_receipts = 0_u64;
2025        let mut observed_call_chain_receipts = 0_u64;
2026        let mut verified_call_chain_receipts = 0_u64;
2027        let mut session_anchor_receipts = 0_u64;
2028        let mut request_lineage_receipts = 0_u64;
2029        let mut receipt_lineage_statement_receipts = 0_u64;
2030        let mut receipts = Vec::new();
2031        for row in rows {
2032            let (
2033                seq,
2034                raw_json,
2035                receipt_subject_key,
2036                receipt_issuer_key,
2037                lineage_subject_key,
2038                lineage_issuer_key,
2039                persisted_grant_index,
2040                grants_json,
2041                persisted_statement_id,
2042                persisted_session_anchor_id,
2043            ) = row?;
2044            let receipt = decode_verified_chio_receipt(
2045                &raw_json,
2046                "persisted tool receipt",
2047                Some(seq.max(0) as u64),
2048            )?;
2049            let governed = extract_governed_transaction_metadata(&receipt).ok_or_else(|| {
2050                ReceiptStoreError::Canonical(format!(
2051                    "receipt {} is missing governed transaction metadata",
2052                    receipt.id
2053                ))
2054            })?;
2055            let mut projection = sanitize_governed_transaction_projection(&receipt, &governed);
2056            apply_persisted_lineage_references(
2057                &mut projection,
2058                &PersistedLineageReferences {
2059                    statement_id: persisted_statement_id,
2060                    session_anchor_id: persisted_session_anchor_id,
2061                },
2062            );
2063            let attribution = extract_receipt_attribution(&receipt);
2064            let transaction_context =
2065                authorization_transaction_context_from_projection(&projection);
2066            let sender_constraint = derive_authorization_sender_constraint(
2067                &receipt.id,
2068                AuthorizationSenderConstraintArgs {
2069                    tool_server: &receipt.tool_server,
2070                    tool_name: &receipt.tool_name,
2071                    receipt_subject_key: receipt_subject_key.as_deref(),
2072                    receipt_issuer_key: receipt_issuer_key.as_deref(),
2073                    lineage_subject_key: lineage_subject_key.as_deref(),
2074                    lineage_issuer_key: lineage_issuer_key.as_deref(),
2075                    grant_index: attribution
2076                        .grant_index
2077                        .or_else(|| persisted_grant_index.map(|value| value.max(0) as u32)),
2078                    grants_json: grants_json.as_deref(),
2079                },
2080                &transaction_context,
2081            )?;
2082
2083            let authorization_row = AuthorizationContextRow {
2084                receipt_id: receipt.id,
2085                timestamp: receipt.timestamp,
2086                capability_id: receipt.capability_id,
2087                subject_key: Some(sender_constraint.subject_key.clone()),
2088                tool_server: receipt.tool_server,
2089                tool_name: receipt.tool_name,
2090                decision: receipt.decision,
2091                authorization_details: authorization_details_from_governed_metadata(
2092                    &projection.strong,
2093                ),
2094                transaction_context,
2095                governed_transaction_diagnostics: projection.diagnostics.clone(),
2096                sender_constraint,
2097            };
2098            validate_chio_oauth_authorization_row(&authorization_row)?;
2099            if let Some(evidence_class) = call_chain_evidence_class(&projection) {
2100                call_chain_receipts += 1;
2101                match evidence_class {
2102                    GovernedProvenanceEvidenceClass::Asserted => {
2103                        asserted_call_chain_receipts += 1;
2104                    }
2105                    GovernedProvenanceEvidenceClass::Observed => {
2106                        observed_call_chain_receipts += 1;
2107                    }
2108                    GovernedProvenanceEvidenceClass::Verified => {
2109                        verified_call_chain_receipts += 1;
2110                    }
2111                }
2112            }
2113            if let Some(lineage_references) = lineage_references_from_projection(&projection) {
2114                if lineage_references.session_anchor_id.is_some() {
2115                    session_anchor_receipts += 1;
2116                }
2117                if lineage_references.request_lineage_id.is_some() {
2118                    request_lineage_receipts += 1;
2119                }
2120                if lineage_references.receipt_lineage_statement_id.is_some() {
2121                    receipt_lineage_statement_receipts += 1;
2122                }
2123            }
2124            sender_bound_receipts += 1;
2125            if authorization_row.sender_constraint.proof_required {
2126                dpop_bound_receipts += 1;
2127            }
2128            if authorization_row.sender_constraint.runtime_assurance_bound {
2129                runtime_assurance_bound_receipts += 1;
2130            }
2131            if authorization_row
2132                .sender_constraint
2133                .delegated_call_chain_bound
2134            {
2135                delegated_sender_bound_receipts += 1;
2136            }
2137            if receipts.len() < row_limit {
2138                receipts.push(authorization_row);
2139            }
2140        }
2141
2142        Ok(AuthorizationContextReport {
2143            schema: CHIO_OAUTH_AUTHORIZATION_CONTEXT_REPORT_SCHEMA.to_string(),
2144            profile: ChioOAuthAuthorizationProfile::default(),
2145            summary: AuthorizationContextSummary {
2146                matching_receipts,
2147                returned_receipts: receipts.len() as u64,
2148                approval_receipts,
2149                approved_receipts,
2150                commerce_receipts,
2151                metered_billing_receipts,
2152                runtime_assurance_receipts,
2153                call_chain_receipts,
2154                asserted_call_chain_receipts,
2155                observed_call_chain_receipts,
2156                verified_call_chain_receipts,
2157                max_amount_receipts,
2158                sender_bound_receipts,
2159                dpop_bound_receipts,
2160                runtime_assurance_bound_receipts,
2161                delegated_sender_bound_receipts,
2162                session_anchor_receipts,
2163                request_lineage_receipts,
2164                receipt_lineage_statement_receipts,
2165                truncated: matching_receipts > receipts.len() as u64,
2166            },
2167            receipts,
2168        })
2169    }
2170
2171    pub fn authorization_profile_metadata_report(&self) -> ChioOAuthAuthorizationMetadataReport {
2172        ChioOAuthAuthorizationMetadataReport {
2173            schema: CHIO_OAUTH_AUTHORIZATION_METADATA_SCHEMA.to_string(),
2174            generated_at: unix_now(),
2175            profile: ChioOAuthAuthorizationProfile::default(),
2176            report_schema: CHIO_OAUTH_AUTHORIZATION_CONTEXT_REPORT_SCHEMA.to_string(),
2177            discovery: ChioOAuthAuthorizationDiscoveryMetadata {
2178                protected_resource_metadata_paths: vec![
2179                    "/.well-known/oauth-protected-resource".to_string(),
2180                    "/.well-known/oauth-protected-resource/mcp".to_string(),
2181                ],
2182                authorization_server_metadata_path_template:
2183                    "/.well-known/oauth-authorization-server/{issuer-path}".to_string(),
2184                discovery_informational_only: true,
2185            },
2186            support_boundary: ChioOAuthAuthorizationSupportBoundary {
2187                governed_receipts_authoritative: true,
2188                hosted_request_time_authorization_supported: true,
2189                resource_indicator_binding_supported: true,
2190                sender_constrained_projection: true,
2191                runtime_assurance_projection: true,
2192                delegated_call_chain_projection: true,
2193                generic_token_issuance_supported: false,
2194                oidc_identity_assertions_supported: false,
2195                mtls_transport_binding_in_profile: false,
2196                approval_tokens_runtime_authorization_supported: false,
2197                capabilities_runtime_authorization_supported: false,
2198                reviewer_evidence_runtime_authorization_supported: false,
2199            },
2200            example_mapping: ChioOAuthAuthorizationExampleMapping {
2201                authorization_detail_types: vec![
2202                    "type".to_string(),
2203                    "locations".to_string(),
2204                    "actions".to_string(),
2205                    "purpose".to_string(),
2206                    "maxAmount".to_string(),
2207                    "commerce".to_string(),
2208                    "meteredBilling".to_string(),
2209                ],
2210                transaction_context_fields: ChioOAuthAuthorizationProfile::default()
2211                    .transaction_context_fields,
2212                sender_constraint_fields: vec![
2213                    "subjectKey".to_string(),
2214                    "subjectKeySource".to_string(),
2215                    "issuerKey".to_string(),
2216                    "issuerKeySource".to_string(),
2217                    "matchedGrantIndex".to_string(),
2218                    "proofRequired".to_string(),
2219                    "proofType".to_string(),
2220                    "proofSchema".to_string(),
2221                    "runtimeAssuranceBound".to_string(),
2222                    "delegatedCallChainBound".to_string(),
2223                ],
2224            },
2225        }
2226    }
2227
2228    pub fn query_authorization_review_pack(
2229        &self,
2230        query: &OperatorReportQuery,
2231    ) -> Result<ChioOAuthAuthorizationReviewPack, ReceiptStoreError> {
2232        let authorization_context = self.query_authorization_context_report(query)?;
2233        let metadata = self.authorization_profile_metadata_report();
2234        let mut records = Vec::with_capacity(authorization_context.receipts.len());
2235
2236        for row in authorization_context.receipts {
2237            let (seq, raw_json) = self.connection()?.query_row(
2238                "SELECT seq, raw_json FROM chio_tool_receipts WHERE receipt_id = ?1",
2239                params![row.receipt_id.as_str()],
2240                |db_row| Ok((db_row.get::<_, i64>(0)?, db_row.get::<_, String>(1)?)),
2241            )?;
2242            let signed_receipt = decode_verified_chio_receipt(
2243                &raw_json,
2244                "persisted tool receipt",
2245                Some(seq.max(0) as u64),
2246            )?;
2247            let governed_transaction = extract_governed_transaction_metadata(&signed_receipt)
2248                .ok_or_else(|| {
2249                    ReceiptStoreError::Canonical(format!(
2250                        "receipt {} is missing governed transaction metadata",
2251                        signed_receipt.id
2252                    ))
2253                })?;
2254            let mut projection =
2255                sanitize_governed_transaction_projection(&signed_receipt, &governed_transaction);
2256            if let Some(diagnostics) = row.governed_transaction_diagnostics.as_ref() {
2257                apply_persisted_lineage_references(
2258                    &mut projection,
2259                    &PersistedLineageReferences {
2260                        statement_id: diagnostics
2261                            .lineage_references
2262                            .receipt_lineage_statement_id
2263                            .clone(),
2264                        session_anchor_id: diagnostics.lineage_references.session_anchor_id.clone(),
2265                    },
2266                );
2267            }
2268            records.push(ChioOAuthAuthorizationReviewPackRecord {
2269                receipt_id: row.receipt_id.clone(),
2270                capability_id: row.capability_id.clone(),
2271                authorization_context: row,
2272                governed_transaction: projection.strong,
2273                governed_transaction_diagnostics: projection.diagnostics,
2274                signed_receipt,
2275            });
2276        }
2277
2278        Ok(ChioOAuthAuthorizationReviewPack {
2279            schema: CHIO_OAUTH_AUTHORIZATION_REVIEW_PACK_SCHEMA.to_string(),
2280            generated_at: unix_now(),
2281            filters: query.clone(),
2282            metadata,
2283            summary: ChioOAuthAuthorizationReviewPackSummary {
2284                matching_receipts: authorization_context.summary.matching_receipts,
2285                returned_receipts: records.len() as u64,
2286                dpop_required_receipts: authorization_context.summary.dpop_bound_receipts,
2287                runtime_assurance_receipts: authorization_context
2288                    .summary
2289                    .runtime_assurance_bound_receipts,
2290                delegated_call_chain_receipts: authorization_context.summary.call_chain_receipts,
2291                asserted_call_chain_receipts: authorization_context
2292                    .summary
2293                    .asserted_call_chain_receipts,
2294                observed_call_chain_receipts: authorization_context
2295                    .summary
2296                    .observed_call_chain_receipts,
2297                verified_call_chain_receipts: authorization_context
2298                    .summary
2299                    .verified_call_chain_receipts,
2300                session_anchor_receipts: authorization_context.summary.session_anchor_receipts,
2301                request_lineage_receipts: authorization_context.summary.request_lineage_receipts,
2302                receipt_lineage_statement_receipts: authorization_context
2303                    .summary
2304                    .receipt_lineage_statement_receipts,
2305                truncated: authorization_context.summary.truncated,
2306            },
2307            records,
2308        })
2309    }
2310
2311    pub fn query_behavioral_feed_receipts(
2312        &self,
2313        query: &BehavioralFeedQuery,
2314    ) -> Result<
2315        (
2316            BehavioralFeedSettlementSummary,
2317            BehavioralFeedGovernedActionSummary,
2318            BehavioralFeedMeteredBillingSummary,
2319            BehavioralFeedReceiptSelection,
2320        ),
2321        ReceiptStoreError,
2322    > {
2323        let operator_query = query.to_operator_report_query();
2324        let capability_id = operator_query.capability_id.as_deref();
2325        let tool_server = operator_query.tool_server.as_deref();
2326        let tool_name = operator_query.tool_name.as_deref();
2327        let since = operator_query.since.map(|value| value as i64);
2328        let until = operator_query.until.map(|value| value as i64);
2329        let agent_subject = operator_query.agent_subject.as_deref();
2330
2331        let summary_sql = r#"
2332            SELECT
2333                COALESCE(SUM(
2334                    CASE
2335                        WHEN COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') = 'pending' THEN 1
2336                        ELSE 0
2337                    END
2338                ), 0),
2339                COALESCE(SUM(
2340                    CASE
2341                        WHEN COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') = 'settled' THEN 1
2342                        ELSE 0
2343                    END
2344                ), 0),
2345                COALESCE(SUM(
2346                    CASE
2347                        WHEN COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') = 'failed' THEN 1
2348                        ELSE 0
2349                    END
2350                ), 0),
2351                COALESCE(SUM(
2352                    CASE
2353                        WHEN COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') = 'not_applicable' THEN 1
2354                        ELSE 0
2355                    END
2356                ), 0),
2357                COALESCE(SUM(
2358                    CASE
2359                        WHEN COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') IN ('pending', 'failed')
2360                         AND COALESCE(sr.reconciliation_state, 'open') NOT IN ('reconciled', 'ignored')
2361                        THEN 1
2362                        ELSE 0
2363                    END
2364                ), 0),
2365                COALESCE(SUM(
2366                    CASE
2367                        WHEN COALESCE(sr.reconciliation_state, 'open') = 'reconciled' THEN 1
2368                        ELSE 0
2369                    END
2370                ), 0),
2371                COALESCE(SUM(
2372                    CASE
2373                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction') IS NOT NULL THEN 1
2374                        ELSE 0
2375                    END
2376                ), 0),
2377                COALESCE(SUM(
2378                    CASE
2379                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.approval') IS NOT NULL THEN 1
2380                        ELSE 0
2381                    END
2382                ), 0),
2383                COALESCE(SUM(
2384                    CASE
2385                        WHEN json_extract(r.raw_json, '$.metadata.governed_transaction.approval.approved') = 1 THEN 1
2386                        ELSE 0
2387                    END
2388                ), 0),
2389                COALESCE(SUM(
2390                    CASE
2391                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.commerce') IS NOT NULL THEN 1
2392                        ELSE 0
2393                    END
2394                ), 0),
2395                COALESCE(SUM(
2396                    CASE
2397                        WHEN json_type(r.raw_json, '$.metadata.governed_transaction.max_amount') IS NOT NULL THEN 1
2398                        ELSE 0
2399                    END
2400                ), 0)
2401            FROM chio_tool_receipts r
2402            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
2403            LEFT JOIN settlement_reconciliations sr ON r.receipt_id = sr.receipt_id
2404            WHERE (?1 IS NULL OR r.capability_id = ?1)
2405              AND (?2 IS NULL OR r.tool_server = ?2)
2406              AND (?3 IS NULL OR r.tool_name = ?3)
2407              AND (?4 IS NULL OR r.timestamp >= ?4)
2408              AND (?5 IS NULL OR r.timestamp <= ?5)
2409              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
2410        "#;
2411
2412        let (settlements, governed_actions) = self.connection()?.query_row(
2413            summary_sql,
2414            params![
2415                capability_id,
2416                tool_server,
2417                tool_name,
2418                since,
2419                until,
2420                agent_subject
2421            ],
2422            |row| {
2423                Ok((
2424                    BehavioralFeedSettlementSummary {
2425                        pending_receipts: row.get::<_, i64>(0)?.max(0) as u64,
2426                        settled_receipts: row.get::<_, i64>(1)?.max(0) as u64,
2427                        failed_receipts: row.get::<_, i64>(2)?.max(0) as u64,
2428                        not_applicable_receipts: row.get::<_, i64>(3)?.max(0) as u64,
2429                        actionable_receipts: row.get::<_, i64>(4)?.max(0) as u64,
2430                        reconciled_receipts: row.get::<_, i64>(5)?.max(0) as u64,
2431                    },
2432                    BehavioralFeedGovernedActionSummary {
2433                        governed_receipts: row.get::<_, i64>(6)?.max(0) as u64,
2434                        approval_receipts: row.get::<_, i64>(7)?.max(0) as u64,
2435                        approved_receipts: row.get::<_, i64>(8)?.max(0) as u64,
2436                        commerce_receipts: row.get::<_, i64>(9)?.max(0) as u64,
2437                        max_amount_receipts: row.get::<_, i64>(10)?.max(0) as u64,
2438                    },
2439                ))
2440            },
2441        )?;
2442        let metered_billing = self.query_metered_billing_summary(&operator_query)?;
2443
2444        let row_limit = query.receipt_limit_or_default();
2445        let count_sql = r#"
2446            SELECT COUNT(*)
2447            FROM chio_tool_receipts r
2448            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
2449            WHERE (?1 IS NULL OR r.capability_id = ?1)
2450              AND (?2 IS NULL OR r.tool_server = ?2)
2451              AND (?3 IS NULL OR r.tool_name = ?3)
2452              AND (?4 IS NULL OR r.timestamp >= ?4)
2453              AND (?5 IS NULL OR r.timestamp <= ?5)
2454              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
2455        "#;
2456        let matching_receipts = self
2457            .connection()?
2458            .query_row(
2459                count_sql,
2460                params![
2461                    capability_id,
2462                    tool_server,
2463                    tool_name,
2464                    since,
2465                    until,
2466                    agent_subject
2467                ],
2468                |row| row.get::<_, i64>(0),
2469            )
2470            .map(|value| value.max(0) as u64)?;
2471
2472        let rows_sql = r#"
2473            SELECT r.seq, r.raw_json
2474            FROM chio_tool_receipts r
2475            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
2476            WHERE (?1 IS NULL OR r.capability_id = ?1)
2477              AND (?2 IS NULL OR r.tool_server = ?2)
2478              AND (?3 IS NULL OR r.tool_name = ?3)
2479              AND (?4 IS NULL OR r.timestamp >= ?4)
2480              AND (?5 IS NULL OR r.timestamp <= ?5)
2481              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
2482            ORDER BY r.timestamp DESC, r.seq DESC
2483            LIMIT ?7
2484        "#;
2485        let connection = self.connection()?;
2486        let mut stmt = connection.prepare(rows_sql)?;
2487        let rows = stmt.query_map(
2488            params![
2489                capability_id,
2490                tool_server,
2491                tool_name,
2492                since,
2493                until,
2494                agent_subject,
2495                row_limit as i64,
2496            ],
2497            |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
2498        )?;
2499        let mut receipts = Vec::with_capacity(row_limit);
2500        for row in rows {
2501            let (seq, raw_json) = row?;
2502            let receipt = decode_verified_chio_receipt(
2503                &raw_json,
2504                "persisted tool receipt",
2505                Some(seq.max(0) as u64),
2506            )?;
2507            receipts.push(self.behavioral_feed_receipt_row_from_receipt(receipt)?);
2508        }
2509
2510        Ok((
2511            settlements,
2512            governed_actions,
2513            metered_billing,
2514            BehavioralFeedReceiptSelection {
2515                matching_receipts,
2516                receipts,
2517            },
2518        ))
2519    }
2520
2521    pub fn query_recent_credit_loss_receipts(
2522        &self,
2523        query: &BehavioralFeedQuery,
2524        limit: usize,
2525    ) -> Result<(u64, Vec<BehavioralFeedReceiptRow>), ReceiptStoreError> {
2526        let operator_query = query.to_operator_report_query();
2527        let capability_id = operator_query.capability_id.as_deref();
2528        let tool_server = operator_query.tool_server.as_deref();
2529        let tool_name = operator_query.tool_name.as_deref();
2530        let since = operator_query.since.map(|value| value as i64);
2531        let until = operator_query.until.map(|value| value as i64);
2532        let agent_subject = operator_query.agent_subject.as_deref();
2533        let row_limit = limit.max(1);
2534
2535        let count_sql = r#"
2536            SELECT COUNT(*)
2537            FROM chio_tool_receipts r
2538            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
2539            LEFT JOIN settlement_reconciliations sr ON r.receipt_id = sr.receipt_id
2540            WHERE (?1 IS NULL OR r.capability_id = ?1)
2541              AND (?2 IS NULL OR r.tool_server = ?2)
2542              AND (?3 IS NULL OR r.tool_name = ?3)
2543              AND (?4 IS NULL OR r.timestamp >= ?4)
2544              AND (?5 IS NULL OR r.timestamp <= ?5)
2545              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
2546              AND (
2547                    COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') = 'failed'
2548                    OR (
2549                        COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') IN ('pending', 'failed')
2550                        AND COALESCE(sr.reconciliation_state, 'open') NOT IN ('reconciled', 'ignored')
2551                    )
2552              )
2553        "#;
2554
2555        let matching_loss_events = self
2556            .connection()?
2557            .query_row(
2558                count_sql,
2559                params![
2560                    capability_id,
2561                    tool_server,
2562                    tool_name,
2563                    since,
2564                    until,
2565                    agent_subject
2566                ],
2567                |row| row.get::<_, i64>(0),
2568            )
2569            .map(|value| value.max(0) as u64)?;
2570
2571        let rows_sql = r#"
2572            SELECT r.seq, r.raw_json
2573            FROM chio_tool_receipts r
2574            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
2575            LEFT JOIN settlement_reconciliations sr ON r.receipt_id = sr.receipt_id
2576            WHERE (?1 IS NULL OR r.capability_id = ?1)
2577              AND (?2 IS NULL OR r.tool_server = ?2)
2578              AND (?3 IS NULL OR r.tool_name = ?3)
2579              AND (?4 IS NULL OR r.timestamp >= ?4)
2580              AND (?5 IS NULL OR r.timestamp <= ?5)
2581              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
2582              AND (
2583                    COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') = 'failed'
2584                    OR (
2585                        COALESCE(json_extract(r.raw_json, '$.metadata.financial.settlement_status'), 'not_applicable') IN ('pending', 'failed')
2586                        AND COALESCE(sr.reconciliation_state, 'open') NOT IN ('reconciled', 'ignored')
2587                    )
2588              )
2589            ORDER BY r.timestamp DESC, r.seq DESC
2590            LIMIT ?7
2591        "#;
2592
2593        let connection = self.connection()?;
2594        let mut stmt = connection.prepare(rows_sql)?;
2595        let rows = stmt.query_map(
2596            params![
2597                capability_id,
2598                tool_server,
2599                tool_name,
2600                since,
2601                until,
2602                agent_subject,
2603                row_limit as i64
2604            ],
2605            |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
2606        )?;
2607
2608        let mut receipts = Vec::new();
2609        for row in rows {
2610            let (seq, raw_json) = row?;
2611            let receipt = decode_verified_chio_receipt(
2612                &raw_json,
2613                "persisted tool receipt",
2614                Some(seq.max(0) as u64),
2615            )?;
2616            receipts.push(self.behavioral_feed_receipt_row_from_receipt(receipt)?);
2617        }
2618
2619        Ok((matching_loss_events, receipts))
2620    }
2621
2622    pub(crate) fn behavioral_feed_receipt_row_from_receipt(
2623        &self,
2624        receipt: ChioReceipt,
2625    ) -> Result<BehavioralFeedReceiptRow, ReceiptStoreError> {
2626        let attribution = extract_receipt_attribution(&receipt);
2627        let lineage = if attribution.subject_key.is_none() || attribution.issuer_key.is_none() {
2628            self.get_combined_lineage(&receipt.capability_id)?
2629        } else {
2630            None
2631        };
2632        let financial = extract_financial_metadata(&receipt);
2633        let governed = extract_governed_transaction_metadata(&receipt);
2634        let governed_projection = governed
2635            .as_ref()
2636            .map(|metadata| sanitize_governed_transaction_projection(&receipt, metadata));
2637        let metered_reconciliation = governed
2638            .as_ref()
2639            .and_then(|metadata| metadata.metered_billing.as_ref())
2640            .map(|metered| {
2641                let evidence = self.load_metered_billing_evidence_record(&receipt.id)?;
2642                let reconciliation_state = self
2643                    .connection()?
2644                    .query_row(
2645                        r#"
2646                        SELECT COALESCE(reconciliation_state, 'open')
2647                        FROM metered_billing_reconciliations
2648                        WHERE receipt_id = ?1
2649                        "#,
2650                        params![&receipt.id],
2651                        |row| row.get::<_, String>(0),
2652                    )
2653                    .optional()?
2654                    .map(|value| parse_metered_billing_reconciliation_state(&value))
2655                    .transpose()?
2656                    .unwrap_or(MeteredBillingReconciliationState::Open);
2657                let analysis = analyze_metered_billing_reconciliation(
2658                    metered,
2659                    financial.as_ref(),
2660                    evidence.as_ref(),
2661                    reconciliation_state,
2662                );
2663                Ok::<BehavioralFeedMeteredBillingRow, ReceiptStoreError>(
2664                    BehavioralFeedMeteredBillingRow {
2665                        reconciliation_state,
2666                        action_required: analysis.action_required,
2667                        evidence_missing: analysis.evidence_missing,
2668                        exceeds_quoted_units: analysis.exceeds_quoted_units,
2669                        exceeds_max_billed_units: analysis.exceeds_max_billed_units,
2670                        exceeds_quoted_cost: analysis.exceeds_quoted_cost,
2671                        financial_mismatch: analysis.financial_mismatch,
2672                        evidence,
2673                    },
2674                )
2675            })
2676            .transpose()?;
2677        let settlement_status = financial
2678            .as_ref()
2679            .map(|metadata| metadata.settlement_status.clone())
2680            .unwrap_or(SettlementStatus::NotApplicable);
2681        let reconciliation_state = self
2682            .connection()?
2683            .query_row(
2684                r#"
2685                SELECT COALESCE(reconciliation_state, 'open')
2686                FROM settlement_reconciliations
2687                WHERE receipt_id = ?1
2688                "#,
2689                params![receipt.id],
2690                |row| row.get::<_, String>(0),
2691            )
2692            .optional()?
2693            .map(|value| parse_settlement_reconciliation_state(&value))
2694            .transpose()?
2695            .unwrap_or(SettlementReconciliationState::Open);
2696        let action_required = settlement_reconciliation_action_required(
2697            settlement_status.clone(),
2698            reconciliation_state,
2699        );
2700        let budget_authority = receipt.financial_budget_authority_metadata();
2701
2702        Ok(BehavioralFeedReceiptRow {
2703            receipt_id: receipt.id,
2704            timestamp: receipt.timestamp,
2705            capability_id: receipt.capability_id,
2706            subject_key: attribution.subject_key.or_else(|| {
2707                lineage
2708                    .as_ref()
2709                    .map(|snapshot| snapshot.subject_key.clone())
2710            }),
2711            issuer_key: attribution
2712                .issuer_key
2713                .or_else(|| lineage.as_ref().map(|snapshot| snapshot.issuer_key.clone())),
2714            tool_server: receipt.tool_server,
2715            tool_name: receipt.tool_name,
2716            decision: receipt.decision,
2717            settlement_status,
2718            reconciliation_state,
2719            action_required,
2720            cost_charged: financial.as_ref().map(|metadata| metadata.cost_charged),
2721            attempted_cost: financial
2722                .as_ref()
2723                .and_then(|metadata| metadata.attempted_cost),
2724            currency: financial.as_ref().map(|metadata| metadata.currency.clone()),
2725            budget_authority,
2726            governed: governed_projection
2727                .as_ref()
2728                .map(|projection| projection.strong.clone()),
2729            governed_transaction_diagnostics: governed_projection
2730                .and_then(|projection| projection.diagnostics),
2731            metered_reconciliation,
2732        })
2733    }
2734
2735    pub(crate) fn query_metered_billing_summary(
2736        &self,
2737        query: &OperatorReportQuery,
2738    ) -> Result<BehavioralFeedMeteredBillingSummary, ReceiptStoreError> {
2739        let capability_id = query.capability_id.as_deref();
2740        let tool_server = query.tool_server.as_deref();
2741        let tool_name = query.tool_name.as_deref();
2742        let since = query.since.map(|value| value as i64);
2743        let until = query.until.map(|value| value as i64);
2744        let agent_subject = query.agent_subject.as_deref();
2745
2746        let summary_sql = r#"
2747            SELECT
2748                COUNT(*) AS matching_receipts,
2749                COALESCE(SUM(CASE WHEN mbr.receipt_id IS NOT NULL THEN 1 ELSE 0 END), 0),
2750                COALESCE(SUM(CASE WHEN mbr.receipt_id IS NULL THEN 1 ELSE 0 END), 0),
2751                COALESCE(SUM(
2752                    CASE
2753                        WHEN mbr.receipt_id IS NOT NULL
2754                         AND mbr.observed_units > CAST(json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.quote.quotedUnits') AS INTEGER)
2755                        THEN 1
2756                        ELSE 0
2757                    END
2758                ), 0),
2759                COALESCE(SUM(
2760                    CASE
2761                        WHEN mbr.receipt_id IS NOT NULL
2762                         AND json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.maxBilledUnits') IS NOT NULL
2763                         AND mbr.observed_units > CAST(json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.maxBilledUnits') AS INTEGER)
2764                        THEN 1
2765                        ELSE 0
2766                    END
2767                ), 0),
2768                COALESCE(SUM(
2769                    CASE
2770                        WHEN mbr.receipt_id IS NOT NULL
2771                         AND (
2772                            mbr.billed_cost_currency != json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.quote.quotedCost.currency')
2773                            OR mbr.billed_cost_units > CAST(json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.quote.quotedCost.units') AS INTEGER)
2774                         )
2775                        THEN 1
2776                        ELSE 0
2777                    END
2778                ), 0),
2779                COALESCE(SUM(
2780                    CASE
2781                        WHEN mbr.receipt_id IS NOT NULL
2782                         AND json_type(r.raw_json, '$.metadata.financial') = 'object'
2783                         AND (
2784                            mbr.billed_cost_currency != json_extract(r.raw_json, '$.metadata.financial.currency')
2785                            OR mbr.billed_cost_units != CAST(json_extract(r.raw_json, '$.metadata.financial.cost_charged') AS INTEGER)
2786                         )
2787                        THEN 1
2788                        ELSE 0
2789                    END
2790                ), 0),
2791                COALESCE(SUM(
2792                    CASE
2793                        WHEN COALESCE(mbr.reconciliation_state, 'open') = 'reconciled' THEN 1
2794                        ELSE 0
2795                    END
2796                ), 0),
2797                COALESCE(SUM(
2798                    CASE
2799                        WHEN COALESCE(mbr.reconciliation_state, 'open') NOT IN ('reconciled', 'ignored')
2800                         AND (
2801                            mbr.receipt_id IS NULL
2802                            OR mbr.observed_units > CAST(json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.quote.quotedUnits') AS INTEGER)
2803                            OR (
2804                                json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.maxBilledUnits') IS NOT NULL
2805                                AND mbr.observed_units > CAST(json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.maxBilledUnits') AS INTEGER)
2806                            )
2807                            OR mbr.billed_cost_currency != json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.quote.quotedCost.currency')
2808                            OR mbr.billed_cost_units > CAST(json_extract(r.raw_json, '$.metadata.governed_transaction.metered_billing.quote.quotedCost.units') AS INTEGER)
2809                            OR (
2810                                json_type(r.raw_json, '$.metadata.financial') = 'object'
2811                                AND (
2812                                    mbr.billed_cost_currency != json_extract(r.raw_json, '$.metadata.financial.currency')
2813                                    OR mbr.billed_cost_units != CAST(json_extract(r.raw_json, '$.metadata.financial.cost_charged') AS INTEGER)
2814                                )
2815                            )
2816                         )
2817                        THEN 1
2818                        ELSE 0
2819                    END
2820                ), 0)
2821            FROM chio_tool_receipts r
2822            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
2823            LEFT JOIN metered_billing_reconciliations mbr ON r.receipt_id = mbr.receipt_id
2824            WHERE json_type(r.raw_json, '$.metadata.governed_transaction.metered_billing') = 'object'
2825              AND (?1 IS NULL OR r.capability_id = ?1)
2826              AND (?2 IS NULL OR r.tool_server = ?2)
2827              AND (?3 IS NULL OR r.tool_name = ?3)
2828              AND (?4 IS NULL OR r.timestamp >= ?4)
2829              AND (?5 IS NULL OR r.timestamp <= ?5)
2830              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
2831        "#;
2832
2833        let (
2834            metered_receipts,
2835            evidence_attached_receipts,
2836            missing_evidence_receipts,
2837            over_quoted_units_receipts,
2838            over_max_billed_units_receipts,
2839            over_quoted_cost_receipts,
2840            financial_mismatch_receipts,
2841            reconciled_receipts,
2842            actionable_receipts,
2843        ) = self.connection()?.query_row(
2844            summary_sql,
2845            params![
2846                capability_id,
2847                tool_server,
2848                tool_name,
2849                since,
2850                until,
2851                agent_subject
2852            ],
2853            |row| {
2854                Ok((
2855                    row.get::<_, i64>(0)?.max(0) as u64,
2856                    row.get::<_, i64>(1)?.max(0) as u64,
2857                    row.get::<_, i64>(2)?.max(0) as u64,
2858                    row.get::<_, i64>(3)?.max(0) as u64,
2859                    row.get::<_, i64>(4)?.max(0) as u64,
2860                    row.get::<_, i64>(5)?.max(0) as u64,
2861                    row.get::<_, i64>(6)?.max(0) as u64,
2862                    row.get::<_, i64>(7)?.max(0) as u64,
2863                    row.get::<_, i64>(8)?.max(0) as u64,
2864                ))
2865            },
2866        )?;
2867
2868        Ok(BehavioralFeedMeteredBillingSummary {
2869            metered_receipts,
2870            evidence_attached_receipts,
2871            missing_evidence_receipts,
2872            over_quoted_units_receipts,
2873            over_max_billed_units_receipts,
2874            over_quoted_cost_receipts,
2875            financial_mismatch_receipts,
2876            actionable_receipts,
2877            reconciled_receipts,
2878        })
2879    }
2880
2881    pub(crate) fn load_metered_billing_evidence_record(
2882        &self,
2883        receipt_id: &str,
2884    ) -> Result<Option<MeteredBillingEvidenceRecord>, ReceiptStoreError> {
2885        self.connection()?
2886            .query_row(
2887                r#"
2888                SELECT
2889                    adapter_kind,
2890                    evidence_id,
2891                    observed_units,
2892                    billed_cost_units,
2893                    billed_cost_currency,
2894                    evidence_sha256,
2895                    recorded_at
2896                FROM metered_billing_reconciliations
2897                WHERE receipt_id = ?1
2898                "#,
2899                params![receipt_id],
2900                |row| {
2901                    Ok((
2902                        row.get::<_, String>(0)?,
2903                        row.get::<_, String>(1)?,
2904                        row.get::<_, i64>(2)?,
2905                        row.get::<_, i64>(3)?,
2906                        row.get::<_, String>(4)?,
2907                        row.get::<_, Option<String>>(5)?,
2908                        row.get::<_, i64>(6)?,
2909                    ))
2910                },
2911            )
2912            .optional()?
2913            .map(
2914                |(
2915                    adapter_kind,
2916                    evidence_id,
2917                    observed_units,
2918                    billed_cost_units,
2919                    billed_cost_currency,
2920                    evidence_sha256,
2921                    recorded_at,
2922                )| {
2923                    Ok(MeteredBillingEvidenceRecord {
2924                        usage_evidence: chio_core::receipt::MeteredUsageEvidenceReceiptMetadata {
2925                            evidence_kind: adapter_kind,
2926                            evidence_id,
2927                            observed_units: observed_units.max(0) as u64,
2928                            evidence_sha256,
2929                        },
2930                        billed_cost: chio_core::capability::MonetaryAmount {
2931                            units: billed_cost_units.max(0) as u64,
2932                            currency: billed_cost_currency,
2933                        },
2934                        recorded_at: recorded_at.max(0) as u64,
2935                    })
2936                },
2937            )
2938            .transpose()
2939    }
2940}