Skip to main content

chio_store_sqlite/receipt_store/reports/
compliance.rs

1// Compliance report query.
2
3use super::*;
4
5impl SqliteReceiptStore {
6    pub fn query_compliance_report(
7        &self,
8        query: &OperatorReportQuery,
9    ) -> Result<ComplianceReport, ReceiptStoreError> {
10        require_admin_receipt_read_context(query.read_context.as_ref(), "compliance report")?;
11        let capability_id = query.capability_id.as_deref();
12        let tool_server = query.tool_server.as_deref();
13        let tool_name = query.tool_name.as_deref();
14        let since = query.since.map(|value| value as i64);
15        let until = query.until.map(|value| value as i64);
16        let agent_subject = query.agent_subject.as_deref();
17
18        let summary_sql = r#"
19            SELECT
20                COUNT(*) AS matching_receipts,
21                COALESCE(SUM(
22                    CASE
23                        WHEN EXISTS(
24                            SELECT 1
25                            FROM kernel_checkpoints kc
26                            WHERE r.seq BETWEEN kc.batch_start_seq AND kc.batch_end_seq
27                        ) THEN 1
28                        ELSE 0
29                    END
30                ), 0) AS evidence_ready_receipts,
31                COALESCE(SUM(CASE WHEN cl.capability_id IS NOT NULL THEN 1 ELSE 0 END), 0) AS lineage_covered_receipts,
32                COALESCE(SUM(
33                    CASE
34                        WHEN json_extract(r.raw_json, '$.metadata.financial.settlement_status') = 'pending' THEN 1
35                        ELSE 0
36                    END
37                ), 0) AS pending_settlement_receipts,
38                COALESCE(SUM(
39                    CASE
40                        WHEN json_extract(r.raw_json, '$.metadata.financial.settlement_status') = 'failed' THEN 1
41                        ELSE 0
42                    END
43                ), 0) AS failed_settlement_receipts
44            FROM chio_tool_receipts r
45            LEFT JOIN capability_lineage cl ON r.capability_id = cl.capability_id
46            WHERE (?1 IS NULL OR r.capability_id = ?1)
47              AND (?2 IS NULL OR r.tool_server = ?2)
48              AND (?3 IS NULL OR r.tool_name = ?3)
49              AND (?4 IS NULL OR r.timestamp >= ?4)
50              AND (?5 IS NULL OR r.timestamp <= ?5)
51              AND (?6 IS NULL OR COALESCE(r.subject_key, cl.subject_key) = ?6)
52        "#;
53
54        let (
55            matching_receipts,
56            evidence_ready_receipts,
57            lineage_covered_receipts,
58            pending_settlement_receipts,
59            failed_settlement_receipts,
60        ) = self.connection()?.query_row(
61            summary_sql,
62            params![
63                capability_id,
64                tool_server,
65                tool_name,
66                since,
67                until,
68                agent_subject
69            ],
70            |row| {
71                Ok((
72                    row.get::<_, i64>(0)?.max(0) as u64,
73                    row.get::<_, i64>(1)?.max(0) as u64,
74                    row.get::<_, i64>(2)?.max(0) as u64,
75                    row.get::<_, i64>(3)?.max(0) as u64,
76                    row.get::<_, i64>(4)?.max(0) as u64,
77                ))
78            },
79        )?;
80
81        let uncheckpointed_receipts = matching_receipts.saturating_sub(evidence_ready_receipts);
82        let lineage_gap_receipts = matching_receipts.saturating_sub(lineage_covered_receipts);
83        let export_query = query
84            .to_evidence_export_query()
85            .map_err(ReceiptStoreError::ReadBoundary)?;
86
87        Ok(ComplianceReport {
88            matching_receipts,
89            evidence_ready_receipts,
90            uncheckpointed_receipts,
91            checkpoint_coverage_rate: ratio_option(evidence_ready_receipts, matching_receipts),
92            lineage_covered_receipts,
93            lineage_gap_receipts,
94            lineage_coverage_rate: ratio_option(lineage_covered_receipts, matching_receipts),
95            pending_settlement_receipts,
96            failed_settlement_receipts,
97            direct_evidence_export_supported: query.direct_evidence_export_supported(),
98            child_receipt_scope: export_query.child_receipt_scope(),
99            proofs_complete: uncheckpointed_receipts == 0,
100            export_query: export_query.clone(),
101            export_scope_note: compliance_export_scope_note(query, &export_query),
102        })
103    }
104}