Skip to main content

chio_store_sqlite/budget_store/
snapshot.rs

1use super::*;
2use chio_core::canonical::canonical_json_bytes;
3use chio_core::crypto::{PublicKey, Signature};
4use chio_core::sha256_hex;
5use hmac::{Hmac, Mac};
6use serde::{Deserialize, Serialize};
7use sha2::Sha256;
8use subtle::ConstantTimeEq;
9
10const BUDGET_ANCHOR_GENESIS_DIGEST: &str =
11    "0000000000000000000000000000000000000000000000000000000000000000";
12
13#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "camelCase", deny_unknown_fields)]
15pub struct BudgetSnapshotAnchorCommitment {
16    pub schema: String,
17    pub commit_sequence: u64,
18    pub previous_chain_digest: String,
19    pub chain_digest: String,
20    pub anchor_set_digest: String,
21    pub leader_url: String,
22    pub election_term: u64,
23    pub committed_at: u64,
24    pub signer_public_key: String,
25}
26
27#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
28#[serde(rename_all = "camelCase", deny_unknown_fields)]
29pub struct SignedBudgetSnapshotAnchorCommitment {
30    pub body: BudgetSnapshotAnchorCommitment,
31    pub signature: Signature,
32}
33
34#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "camelCase", deny_unknown_fields)]
36pub struct BudgetSnapshotAnchorProvenance {
37    pub schema: String,
38    pub chain: Vec<SignedBudgetSnapshotAnchorCommitment>,
39    pub cluster_authenticator: String,
40}
41
42#[derive(Serialize)]
43struct AnchorSetDigestBody<'a> {
44    schema: &'static str,
45    anchors: Vec<AnchorDigestRecord<'a>>,
46}
47
48#[derive(Serialize)]
49struct AnchorDigestRecord<'a> {
50    capability_id: &'a str,
51    grant_index: u32,
52    invocation_count: u32,
53    updated_at: i64,
54    seq: u64,
55    total_cost_exposed: u64,
56    total_cost_realized_spend: u64,
57}
58
59#[derive(Serialize)]
60struct AnchorChainDigestBody<'a> {
61    schema: &'static str,
62    commit_sequence: u64,
63    previous_chain_digest: &'a str,
64    anchor_set_digest: &'a str,
65    leader_url: &'a str,
66    election_term: u64,
67    committed_at: u64,
68    signer_public_key: &'a str,
69}
70
71#[derive(Serialize)]
72struct AnchorAuthenticatorBody<'a> {
73    scheme: &'static str,
74    chain: &'a [SignedBudgetSnapshotAnchorCommitment],
75}
76
77pub fn budget_snapshot_anchor_set_digest(
78    anchors: &[BudgetUsageRecord],
79) -> Result<String, BudgetStoreError> {
80    let mut canonical_anchors = anchors.to_vec();
81    canonical_anchors.sort_by(|left, right| {
82        (&left.capability_id, left.grant_index).cmp(&(&right.capability_id, right.grant_index))
83    });
84    let anchors = canonical_anchors
85        .iter()
86        .map(|anchor| AnchorDigestRecord {
87            capability_id: &anchor.capability_id,
88            grant_index: anchor.grant_index,
89            invocation_count: anchor.invocation_count,
90            updated_at: anchor.updated_at,
91            seq: anchor.seq,
92            total_cost_exposed: anchor.total_cost_exposed,
93            total_cost_realized_spend: anchor.total_cost_realized_spend,
94        })
95        .collect();
96    let bytes = canonical_json_bytes(&AnchorSetDigestBody {
97        schema: "chio.budget-snapshot-anchor-set.v1",
98        anchors,
99    })
100    .map_err(|error| BudgetStoreError::Invariant(error.to_string()))?;
101    Ok(sha256_hex(&bytes))
102}
103
104pub fn budget_snapshot_anchor_chain_digest(
105    body: &BudgetSnapshotAnchorCommitment,
106) -> Result<String, BudgetStoreError> {
107    let bytes = canonical_json_bytes(&AnchorChainDigestBody {
108        schema: "chio.budget-snapshot-anchor-chain.v1",
109        commit_sequence: body.commit_sequence,
110        previous_chain_digest: &body.previous_chain_digest,
111        anchor_set_digest: &body.anchor_set_digest,
112        leader_url: &body.leader_url,
113        election_term: body.election_term,
114        committed_at: body.committed_at,
115        signer_public_key: &body.signer_public_key,
116    })
117    .map_err(|error| BudgetStoreError::Invariant(error.to_string()))?;
118    Ok(sha256_hex(&bytes))
119}
120
121pub fn budget_snapshot_anchor_authenticator(
122    service_token: &str,
123    chain: &[SignedBudgetSnapshotAnchorCommitment],
124) -> Result<String, BudgetStoreError> {
125    let bytes = canonical_json_bytes(&AnchorAuthenticatorBody {
126        scheme: "chio.cluster-budget-anchor-auth.v1",
127        chain,
128    })
129    .map_err(|error| BudgetStoreError::Invariant(error.to_string()))?;
130    let mut authenticator = Hmac::<Sha256>::new_from_slice(service_token.as_bytes())
131        .map_err(|error| BudgetStoreError::Invariant(error.to_string()))?;
132    authenticator.update(&bytes);
133    Ok(hex::encode(authenticator.finalize().into_bytes()))
134}
135
136#[derive(Clone, Debug, PartialEq, Eq)]
137pub struct BudgetStoreSnapshot {
138    pub usages: Vec<BudgetUsageRecord>,
139    pub usage_history_anchors: Vec<BudgetUsageRecord>,
140    pub mutation_events: Vec<BudgetMutationRecord>,
141    pub abandoned_seq_ranges: Vec<(u64, u64)>,
142    pub covered_head: u64,
143    pub origin_ack_heads: Vec<(String, u64)>,
144}
145
146struct BudgetImportBatch<'a> {
147    usages: &'a [BudgetUsageRecord],
148    events: &'a [BudgetMutationRecord],
149    anchors: &'a [BudgetUsageRecord],
150    abandoned_seq_ranges: &'a [(u64, u64)],
151    covered_head: Option<u64>,
152    origin_ack_heads: Option<&'a [(String, u64)]>,
153    allow_verified_anchor_install: bool,
154}
155
156impl SqliteBudgetStore {
157    pub fn budget_import_floor(&self, authority_id: &str) -> Result<u64, BudgetStoreError> {
158        let mut connection = self.connection()?;
159        let transaction = self.begin_read(&mut connection)?;
160        let floor: i64 = transaction
161            .query_row(
162                "SELECT floor_seq FROM budget_import_floors WHERE authority_id = ?1",
163                rusqlite::params![authority_id],
164                |row| row.get(0),
165            )
166            .optional()?
167            .unwrap_or(0);
168        transaction.rollback()?;
169        Ok(floor.max(0) as u64)
170    }
171
172    pub fn record_budget_import_floors(
173        &self,
174        events: &[BudgetMutationRecord],
175    ) -> Result<(), BudgetStoreError> {
176        self.require_standalone_mutation("budget import floor")?;
177        use std::collections::BTreeMap;
178        let mut min_by_origin: BTreeMap<&str, u64> = BTreeMap::new();
179        for event in events {
180            let Some(authority) = event.authority.as_ref() else {
181                continue;
182            };
183            let entry = min_by_origin
184                .entry(authority.authority_id.as_str())
185                .or_insert(event.event_seq);
186            *entry = (*entry).min(event.event_seq);
187        }
188        let mut connection = self.connection()?;
189        let transaction = self.begin_write(&mut connection)?;
190        for (origin, min_seq) in min_by_origin {
191            let floor = min_seq.saturating_sub(1);
192            transaction.execute(
193                "INSERT INTO budget_import_floors (authority_id, floor_seq) VALUES (?1, ?2) \
194                 ON CONFLICT(authority_id) DO UPDATE SET floor_seq = MAX(floor_seq, excluded.floor_seq)",
195                rusqlite::params![origin, budget_u64_to_sqlite(floor, "floor_seq")?],
196            )?;
197        }
198        transaction.commit()?;
199        Ok(())
200    }
201
202    pub fn upsert_usage(&self, record: &BudgetUsageRecord) -> Result<(), BudgetStoreError> {
203        self.require_standalone_mutation("budget usage upsert")?;
204        let mut connection = self.connection()?;
205        let transaction = self.begin_write(&mut connection)?;
206        Self::reconcile_imported_usages(&transaction, std::slice::from_ref(record), &[])?;
207        transaction.commit()?;
208        Ok(())
209    }
210
211    pub fn import_snapshot_records(
212        &self,
213        usages: &[BudgetUsageRecord],
214        events: &[BudgetMutationRecord],
215    ) -> Result<(), BudgetStoreError> {
216        self.import_records(
217            &BudgetImportBatch {
218                usages,
219                events,
220                anchors: &[],
221                abandoned_seq_ranges: &[],
222                covered_head: None,
223                origin_ack_heads: None,
224                allow_verified_anchor_install: false,
225            },
226            "budget snapshot import",
227        )
228    }
229
230    pub fn import_snapshot_records_with_anchors(
231        &self,
232        usages: &[BudgetUsageRecord],
233        events: &[BudgetMutationRecord],
234        anchors: &[BudgetUsageRecord],
235        abandoned_seq_ranges: &[(u64, u64)],
236        covered_head: u64,
237    ) -> Result<(), BudgetStoreError> {
238        self.import_records(
239            &BudgetImportBatch {
240                usages,
241                events,
242                anchors,
243                abandoned_seq_ranges,
244                covered_head: Some(covered_head),
245                origin_ack_heads: None,
246                allow_verified_anchor_install: false,
247            },
248            "budget snapshot import",
249        )
250    }
251
252    pub fn import_budget_snapshot(
253        &self,
254        snapshot: &BudgetStoreSnapshot,
255    ) -> Result<(), BudgetStoreError> {
256        const OPERATION: &str = "budget snapshot import";
257        self.require_standalone_mutation(OPERATION)?;
258
259        let validated = Self::validate_budget_snapshot_in_isolation(snapshot)?;
260        let batch = BudgetImportBatch {
261            usages: &validated.usages,
262            events: &validated.mutation_events,
263            anchors: &validated.usage_history_anchors,
264            abandoned_seq_ranges: &validated.abandoned_seq_ranges,
265            covered_head: Some(validated.covered_head),
266            origin_ack_heads: Some(&validated.origin_ack_heads),
267            allow_verified_anchor_install: false,
268        };
269        let mut connection = self.connection()?;
270        let transaction = self.begin_write(&mut connection)?;
271        Self::validate_exact_snapshot_usage_anchors(&transaction, batch.anchors)?;
272        Self::validate_local_snapshot_subset(&transaction, &validated)?;
273        Self::apply_import_batch(&transaction, &batch)?;
274        transaction.commit()?;
275        Ok(())
276    }
277
278    pub fn import_budget_snapshot_with_anchor_provenance(
279        &self,
280        snapshot: &BudgetStoreSnapshot,
281        provenance: &BudgetSnapshotAnchorProvenance,
282        expected_leader_url: &str,
283        expected_election_term: u64,
284        cluster_service_token: &str,
285    ) -> Result<(), BudgetStoreError> {
286        const OPERATION: &str = "verified budget snapshot import";
287        self.require_standalone_mutation(OPERATION)?;
288        let head = verify_budget_snapshot_anchor_provenance(
289            snapshot,
290            provenance,
291            expected_leader_url,
292            expected_election_term,
293            cluster_service_token,
294        )?;
295        let validated = Self::validate_budget_snapshot_in_isolation(snapshot)?;
296        let batch = BudgetImportBatch {
297            usages: &validated.usages,
298            events: &validated.mutation_events,
299            anchors: &validated.usage_history_anchors,
300            abandoned_seq_ranges: &validated.abandoned_seq_ranges,
301            covered_head: Some(validated.covered_head),
302            origin_ack_heads: Some(&validated.origin_ack_heads),
303            allow_verified_anchor_install: true,
304        };
305        let mut connection = self.connection()?;
306        let transaction = self.begin_write(&mut connection)?;
307        verify_local_anchor_provenance_continuity(&transaction, provenance)?;
308        Self::validate_local_snapshot_subset(&transaction, &validated)?;
309        Self::apply_import_batch(&transaction, &batch)?;
310        let local = Self::snapshot_usage_history_anchors(&transaction)?;
311        if local != validated.usage_history_anchors {
312            return Err(BudgetStoreError::Invariant(
313                "verified budget snapshot history anchor set is not exact".to_string(),
314            ));
315        }
316        transaction.execute(
317            r#"
318            INSERT INTO budget_snapshot_anchor_provenance (
319                leader_url, commit_sequence, chain_digest, anchor_set_digest,
320                election_term, signer_public_key, committed_at
321            ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
322            ON CONFLICT(leader_url) DO UPDATE SET
323                commit_sequence = excluded.commit_sequence,
324                chain_digest = excluded.chain_digest,
325                anchor_set_digest = excluded.anchor_set_digest,
326                election_term = excluded.election_term,
327                signer_public_key = excluded.signer_public_key,
328                committed_at = excluded.committed_at
329            "#,
330            params![
331                &head.leader_url,
332                budget_u64_to_sqlite(head.commit_sequence, "anchor_commit_sequence")?,
333                &head.chain_digest,
334                &head.anchor_set_digest,
335                budget_u64_to_sqlite(head.election_term, "anchor_election_term")?,
336                &head.signer_public_key,
337                budget_u64_to_sqlite(head.committed_at, "anchor_committed_at")?,
338            ],
339        )?;
340        transaction.commit()?;
341        Ok(())
342    }
343
344    pub fn import_delta_records(
345        &self,
346        usages: &[BudgetUsageRecord],
347        events: &[BudgetMutationRecord],
348    ) -> Result<(), BudgetStoreError> {
349        self.import_records(
350            &BudgetImportBatch {
351                usages,
352                events,
353                anchors: &[],
354                abandoned_seq_ranges: &[],
355                covered_head: None,
356                origin_ack_heads: None,
357                allow_verified_anchor_install: false,
358            },
359            "budget delta import",
360        )
361    }
362
363    fn import_records(
364        &self,
365        batch: &BudgetImportBatch<'_>,
366        operation: &str,
367    ) -> Result<(), BudgetStoreError> {
368        self.require_standalone_mutation(operation)?;
369        Self::validate_import_batch_order(batch.events)?;
370        let mut connection = self.connection()?;
371        let transaction = self.begin_write(&mut connection)?;
372        Self::apply_import_batch(&transaction, batch)?;
373        transaction.commit()?;
374        Ok(())
375    }
376
377    fn apply_import_batch(
378        transaction: &rusqlite::Transaction<'_>,
379        batch: &BudgetImportBatch<'_>,
380    ) -> Result<(), BudgetStoreError> {
381        Self::install_snapshot_usage_anchors(
382            transaction,
383            batch.anchors,
384            batch.allow_verified_anchor_install,
385        )?;
386        for event in batch.events {
387            Self::import_mutation_record_in_transaction(transaction, event)?;
388        }
389        Self::reconcile_imported_usages(transaction, batch.usages, batch.events)?;
390        Self::insert_abandoned_event_seq_ranges(transaction, batch.abandoned_seq_ranges)?;
391        if let Some(claimed_head) = batch.covered_head {
392            let exact_head = Self::contiguous_snapshot_head_from(transaction, 0)?;
393            if claimed_head != exact_head {
394                return Err(BudgetStoreError::Invariant(format!(
395                    "budget snapshot claimed covered head {claimed_head}, but imported records prove {exact_head}"
396                )));
397            }
398            let exact_origin_heads = Self::origin_ack_heads_at(transaction, exact_head)?;
399            if let Some(claimed_origin_heads) = batch.origin_ack_heads {
400                if claimed_origin_heads != exact_origin_heads {
401                    return Err(BudgetStoreError::Invariant(
402                        "budget snapshot origin acknowledgement heads are not proved by the retained mutation prefix"
403                            .to_string(),
404                    ));
405                }
406            }
407            Self::install_snapshot_coverage(transaction, exact_head, &exact_origin_heads)?;
408        }
409        Ok(())
410    }
411
412    fn validate_budget_snapshot_in_isolation(
413        snapshot: &BudgetStoreSnapshot,
414    ) -> Result<BudgetStoreSnapshot, BudgetStoreError> {
415        let validator = Self::open(":memory:")?;
416        Self::seed_snapshot_validation_anchors(&validator, &snapshot.usage_history_anchors)?;
417        validator.import_records(
418            &BudgetImportBatch {
419                usages: &snapshot.usages,
420                events: &snapshot.mutation_events,
421                anchors: &snapshot.usage_history_anchors,
422                abandoned_seq_ranges: &snapshot.abandoned_seq_ranges,
423                covered_head: Some(snapshot.covered_head),
424                origin_ack_heads: Some(&snapshot.origin_ack_heads),
425                allow_verified_anchor_install: false,
426            },
427            "budget snapshot validation",
428        )?;
429        let validated = validator.export_budget_snapshot()?;
430        Self::validate_staged_snapshot_matches_payload(snapshot, &validated)?;
431        Ok(validated)
432    }
433
434    fn validate_staged_snapshot_matches_payload(
435        payload: &BudgetStoreSnapshot,
436        staged: &BudgetStoreSnapshot,
437    ) -> Result<(), BudgetStoreError> {
438        let mut payload_usages = payload.usages.clone();
439        payload_usages.sort_by(|left, right| {
440            (&left.capability_id, left.grant_index).cmp(&(&right.capability_id, right.grant_index))
441        });
442        if payload_usages != staged.usages {
443            return Err(BudgetStoreError::Invariant(
444                "budget snapshot does not carry its complete final usage projection".to_string(),
445            ));
446        }
447
448        let same_event_history = payload.mutation_events.len() == staged.mutation_events.len()
449            && payload
450                .mutation_events
451                .iter()
452                .zip(&staged.mutation_events)
453                .all(|(payload, staged)| {
454                    payload.event_id == staged.event_id && payload.event_seq == staged.event_seq
455                });
456        let same_abandoned_history =
457            payload.abandoned_seq_ranges.iter().all(|&(start, end)| {
458                snapshot_ranges_cover(&staged.abandoned_seq_ranges, start, end)
459            }) && staged.abandoned_seq_ranges.iter().all(|&(start, end)| {
460                snapshot_ranges_cover(&payload.abandoned_seq_ranges, start, end)
461            });
462        if !same_event_history || !same_abandoned_history {
463            return Err(BudgetStoreError::Invariant(
464                "budget snapshot validation changed the supplied durable history".to_string(),
465            ));
466        }
467        Ok(())
468    }
469
470    fn seed_snapshot_validation_anchors(
471        validator: &Self,
472        anchors: &[BudgetUsageRecord],
473    ) -> Result<(), BudgetStoreError> {
474        let mut connection = validator.connection()?;
475        let transaction = validator.begin_write(&mut connection)?;
476        transaction.execute(
477            "INSERT OR IGNORE INTO budget_usage_anchor_migration_gate(singleton) VALUES (1)",
478            [],
479        )?;
480        for anchor in anchors {
481            Self::upsert_usage_in_transaction(&transaction, anchor)?;
482            transaction.execute(
483                r#"
484                INSERT INTO budget_usage_history_anchors (
485                    capability_id, grant_index, invocation_count, updated_at, seq,
486                    total_cost_exposed, total_cost_realized_spend,
487                    anchored_schema_version
488                ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 6)
489                "#,
490                params![
491                    &anchor.capability_id,
492                    i64::from(anchor.grant_index),
493                    i64::from(anchor.invocation_count),
494                    anchor.updated_at,
495                    budget_u64_to_sqlite(anchor.seq, "anchor_seq")?,
496                    budget_u64_to_sqlite(anchor.total_cost_exposed, "anchor_total_cost_exposed")?,
497                    budget_u64_to_sqlite(
498                        anchor.total_cost_realized_spend,
499                        "anchor_total_cost_realized_spend"
500                    )?,
501                ],
502            )?;
503        }
504        transaction.execute("DELETE FROM budget_usage_anchor_migration_gate", [])?;
505        transaction.commit()?;
506        Ok(())
507    }
508
509    fn validate_exact_snapshot_usage_anchors(
510        transaction: &rusqlite::Transaction<'_>,
511        anchors: &[BudgetUsageRecord],
512    ) -> Result<(), BudgetStoreError> {
513        Self::install_snapshot_usage_anchors(transaction, anchors, false)?;
514        let local = Self::snapshot_usage_history_anchors(transaction)?;
515        let mut incoming = anchors.to_vec();
516        incoming.sort_by(|left, right| {
517            (&left.capability_id, left.grant_index).cmp(&(&right.capability_id, right.grant_index))
518        });
519        if local != incoming {
520            return Err(BudgetStoreError::Invariant(
521                "budget snapshot history anchor set differs from immutable local migration anchors"
522                    .to_string(),
523            ));
524        }
525        Ok(())
526    }
527
528    fn validate_local_snapshot_subset(
529        transaction: &rusqlite::Transaction<'_>,
530        snapshot: &BudgetStoreSnapshot,
531    ) -> Result<(), BudgetStoreError> {
532        let incoming_events = snapshot
533            .mutation_events
534            .iter()
535            .map(|event| (event.event_id.as_str(), event))
536            .collect::<std::collections::BTreeMap<_, _>>();
537        for local in Self::snapshot_mutation_events(transaction)? {
538            if incoming_events.get(local.event_id.as_str()).copied() != Some(&local) {
539                return Err(BudgetStoreError::Invariant(format!(
540                    "budget snapshot does not retain identical local event `{}`",
541                    local.event_id
542                )));
543            }
544        }
545
546        for (start, end) in Self::snapshot_abandoned_seq_ranges(transaction)? {
547            if !snapshot_ranges_cover(&snapshot.abandoned_seq_ranges, start, end) {
548                return Err(BudgetStoreError::Invariant(format!(
549                    "budget snapshot does not retain local abandoned sequence range {start}..={end}"
550                )));
551            }
552        }
553
554        for usage in Self::snapshot_usages(transaction)? {
555            if !snapshot_proves_usage(snapshot, &usage) {
556                return Err(BudgetStoreError::Invariant(format!(
557                    "budget snapshot does not prove local usage `{}` grant {} at sequence {}",
558                    usage.capability_id, usage.grant_index, usage.seq
559                )));
560            }
561        }
562        Ok(())
563    }
564
565    fn install_snapshot_coverage(
566        transaction: &rusqlite::Transaction<'_>,
567        covered_head: u64,
568        origin_ack_heads: &[(String, u64)],
569    ) -> Result<(), BudgetStoreError> {
570        let covered_head = budget_u64_to_sqlite(covered_head, "covered_head")?;
571        transaction.execute(
572            "UPDATE budget_snapshot_coverage SET covered_head = ?1 WHERE singleton = 1",
573            params![covered_head],
574        )?;
575        transaction.execute(
576            "UPDATE budget_ack_head_watermark SET head_seq = ?1 WHERE singleton = 1",
577            params![covered_head],
578        )?;
579        transaction.execute("DELETE FROM budget_origin_ack_heads", [])?;
580        for (authority_id, head_seq) in origin_ack_heads {
581            transaction.execute(
582                "INSERT INTO budget_origin_ack_heads (authority_id, head_seq) VALUES (?1, ?2)",
583                params![
584                    authority_id,
585                    budget_u64_to_sqlite(*head_seq, "origin_ack_head")?
586                ],
587            )?;
588        }
589        raise_budget_replication_seq_floor(transaction, covered_head as u64)?;
590        Ok(())
591    }
592
593    fn origin_ack_heads_at(
594        connection: &Connection,
595        covered_head: u64,
596    ) -> Result<Vec<(String, u64)>, BudgetStoreError> {
597        let mut statement = connection.prepare(
598            r#"
599            SELECT authority_id, MAX(event_seq)
600            FROM budget_mutation_events
601            WHERE authority_id IS NOT NULL AND event_seq <= ?1
602            GROUP BY authority_id
603            ORDER BY authority_id
604            "#,
605        )?;
606        let rows = statement
607            .query_map(
608                params![budget_u64_to_sqlite(covered_head, "covered_head")?],
609                |row| {
610                    Ok((
611                        row.get::<_, String>(0)?,
612                        budget_u64_from_row(row, 1, "origin_ack_head")?,
613                    ))
614                },
615            )?
616            .collect::<Result<Vec<_>, _>>()?;
617        Ok(rows)
618    }
619
620    pub fn export_budget_snapshot(&self) -> Result<BudgetStoreSnapshot, BudgetStoreError> {
621        let mut connection = self.connection()?;
622        let transaction = self.begin_read(&mut connection)?;
623        let usages = Self::snapshot_usages(&transaction)?;
624        let usage_history_anchors = Self::snapshot_usage_history_anchors(&transaction)?;
625        let mutation_events = Self::snapshot_mutation_events(&transaction)?;
626        let abandoned_seq_ranges = Self::snapshot_abandoned_seq_ranges(&transaction)?;
627        let covered_head = Self::contiguous_snapshot_head_in(&transaction)?;
628        let origin_ack_heads = Self::origin_ack_heads_at(&transaction, covered_head)?;
629        transaction.rollback()?;
630        Ok(BudgetStoreSnapshot {
631            usages,
632            usage_history_anchors,
633            mutation_events,
634            abandoned_seq_ranges,
635            covered_head,
636            origin_ack_heads,
637        })
638    }
639
640    fn snapshot_usages(
641        connection: &Connection,
642    ) -> Result<Vec<BudgetUsageRecord>, BudgetStoreError> {
643        let mut statement = connection.prepare(
644            r#"
645            SELECT capability_id, grant_index, invocation_count, updated_at, seq,
646                   total_cost_exposed, total_cost_realized_spend
647            FROM capability_grant_budgets
648            ORDER BY capability_id, grant_index
649            "#,
650        )?;
651        let rows = statement
652            .query_map([], record_from_row)?
653            .collect::<Result<Vec<_>, _>>()?;
654        Ok(rows)
655    }
656
657    fn snapshot_usage_history_anchors(
658        connection: &Connection,
659    ) -> Result<Vec<BudgetUsageRecord>, BudgetStoreError> {
660        let mut statement = connection.prepare(
661            r#"
662            SELECT capability_id, grant_index, invocation_count, updated_at, seq,
663                   total_cost_exposed, total_cost_realized_spend
664            FROM budget_usage_history_anchors
665            ORDER BY capability_id, grant_index
666            "#,
667        )?;
668        let rows = statement
669            .query_map([], record_from_row)?
670            .collect::<Result<Vec<_>, _>>()?;
671        Ok(rows)
672    }
673
674    fn snapshot_mutation_events(
675        transaction: &rusqlite::Transaction<'_>,
676    ) -> Result<Vec<BudgetMutationRecord>, BudgetStoreError> {
677        let event_ids = {
678            let mut statement = transaction
679                .prepare("SELECT event_id FROM budget_mutation_events ORDER BY event_seq")?;
680            let event_ids = statement
681                .query_map([], |row| row.get::<_, String>(0))?
682                .collect::<Result<Vec<_>, _>>()?;
683            event_ids
684        };
685        event_ids
686            .iter()
687            .map(|event_id| {
688                Self::load_projected_mutation_event(transaction, event_id)?.ok_or_else(|| {
689                    BudgetStoreError::Invariant(format!(
690                        "budget mutation event `{event_id}` disappeared during snapshot export"
691                    ))
692                })
693            })
694            .collect()
695    }
696
697    fn snapshot_abandoned_seq_ranges(
698        connection: &Connection,
699    ) -> Result<Vec<(u64, u64)>, BudgetStoreError> {
700        let mut statement = connection.prepare(
701            r#"
702            SELECT MIN(seq), MAX(seq)
703            FROM (
704                SELECT seq, seq - ROW_NUMBER() OVER (ORDER BY seq) AS island
705                FROM budget_abandoned_event_seqs
706            )
707            GROUP BY island
708            ORDER BY MIN(seq)
709            "#,
710        )?;
711        let rows = statement
712            .query_map([], |row| {
713                Ok((
714                    budget_u64_from_row(row, 0, "abandoned_range_start")?,
715                    budget_u64_from_row(row, 1, "abandoned_range_end")?,
716                ))
717            })?
718            .collect::<Result<Vec<_>, _>>()?;
719        Ok(rows)
720    }
721
722    pub fn budget_snapshot_covered_head(&self) -> Result<u64, BudgetStoreError> {
723        let mut connection = self.connection()?;
724        let transaction = self.begin_read(&mut connection)?;
725        let head = Self::contiguous_snapshot_head_in(&transaction)?;
726        transaction.rollback()?;
727        Ok(head)
728    }
729
730    pub(super) fn contiguous_snapshot_head_in(
731        connection: &Connection,
732    ) -> Result<u64, BudgetStoreError> {
733        Self::contiguous_snapshot_head_from(connection, 0)
734    }
735
736    pub(super) fn rebuild_snapshot_proof_caches(
737        connection: &mut Connection,
738    ) -> Result<(), BudgetStoreError> {
739        let transaction = connection.transaction_with_behavior(TransactionBehavior::Immediate)?;
740        let covered_head = Self::contiguous_snapshot_head_from(&transaction, 0)?;
741        let origin_ack_heads = Self::origin_ack_heads_at(&transaction, covered_head)?;
742        Self::install_snapshot_coverage(&transaction, covered_head, &origin_ack_heads)?;
743        transaction.commit()?;
744        Ok(())
745    }
746
747    fn contiguous_snapshot_head_from(
748        connection: &Connection,
749        floor: u64,
750    ) -> Result<u64, BudgetStoreError> {
751        let floor_sqlite = budget_u64_to_sqlite(floor, "snapshot_covered_head")?;
752        let next_slot = floor_sqlite.checked_add(1).ok_or_else(|| {
753            BudgetStoreError::Overflow("budget snapshot head overflowed i64".to_string())
754        })?;
755        let next_slot_filled: bool = connection.query_row(
756            r#"
757            SELECT EXISTS(
758                SELECT 1 FROM budget_mutation_events WHERE event_seq = ?1
759                UNION ALL
760                SELECT 1 FROM budget_abandoned_event_seqs WHERE seq = ?1
761            )
762            "#,
763            params![next_slot],
764            |row| row.get::<_, i64>(0).map(|value| value != 0),
765        )?;
766        if !next_slot_filled {
767            return Ok(floor);
768        }
769        let head: i64 = connection.query_row(
770            r#"
771            WITH filled AS (
772                SELECT event_seq AS seq
773                FROM budget_mutation_events
774                WHERE event_seq IS NOT NULL AND event_seq > ?1
775                UNION
776                SELECT seq
777                FROM budget_abandoned_event_seqs
778                WHERE seq > ?1
779            ),
780            run AS (
781                SELECT seq, seq - ROW_NUMBER() OVER (ORDER BY seq) AS island
782                FROM filled
783            )
784            SELECT COALESCE(MAX(seq), ?1)
785            FROM run
786            WHERE island = ?1
787            "#,
788            params![floor_sqlite],
789            |row| row.get(0),
790        )?;
791        u64::try_from(head).map_err(|_| {
792            BudgetStoreError::Invariant("budget snapshot head has a negative sequence".to_string())
793        })
794    }
795
796    pub fn list_usage_history_anchors(&self) -> Result<Vec<BudgetUsageRecord>, BudgetStoreError> {
797        let mut connection = self.connection()?;
798        let transaction = self.begin_read(&mut connection)?;
799        let anchors = {
800            let mut statement = transaction.prepare(
801                r#"
802                SELECT capability_id, grant_index, invocation_count, updated_at, seq,
803                       total_cost_exposed, total_cost_realized_spend
804                FROM budget_usage_history_anchors
805                ORDER BY capability_id, grant_index
806                "#,
807            )?;
808            let rows = statement
809                .query_map([], record_from_row)?
810                .collect::<Result<Vec<_>, _>>()?;
811            rows
812        };
813        transaction.rollback()?;
814        Ok(anchors)
815    }
816}
817
818fn snapshot_ranges_cover(ranges: &[(u64, u64)], start: u64, end: u64) -> bool {
819    let mut next = start;
820    for &(range_start, range_end) in ranges {
821        if range_end < next {
822            continue;
823        }
824        if range_start > next {
825            return false;
826        }
827        if range_end >= end {
828            return true;
829        }
830        let Some(after_range) = range_end.checked_add(1) else {
831            return true;
832        };
833        next = after_range;
834    }
835    false
836}
837
838fn snapshot_proves_usage(snapshot: &BudgetStoreSnapshot, usage: &BudgetUsageRecord) -> bool {
839    snapshot
840        .usage_history_anchors
841        .iter()
842        .any(|anchor| anchor == usage)
843        || snapshot.mutation_events.iter().any(|event| {
844            event.capability_id == usage.capability_id
845                && event.grant_index == usage.grant_index
846                && event.event_seq == usage.seq
847                && event.usage_seq == Some(event.event_seq)
848                && event.recorded_at == usage.updated_at
849                && event.invocation_count_after == usage.invocation_count
850                && event.total_cost_exposed_after == usage.total_cost_exposed
851                && event.total_cost_realized_spend_after == usage.total_cost_realized_spend
852        })
853}
854
855fn verify_budget_snapshot_anchor_provenance<'a>(
856    snapshot: &BudgetStoreSnapshot,
857    provenance: &'a BudgetSnapshotAnchorProvenance,
858    expected_leader_url: &str,
859    expected_election_term: u64,
860    cluster_service_token: &str,
861) -> Result<&'a BudgetSnapshotAnchorCommitment, BudgetStoreError> {
862    if expected_leader_url.is_empty()
863        || expected_election_term == 0
864        || cluster_service_token.is_empty()
865        || provenance.chain.is_empty()
866        || provenance.schema != "chio.budget-snapshot-anchor-provenance.v1"
867    {
868        return Err(BudgetStoreError::Invariant(
869            "budget snapshot anchor provenance trust context is incomplete".to_string(),
870        ));
871    }
872    let expected_authenticator =
873        budget_snapshot_anchor_authenticator(cluster_service_token, &provenance.chain)?;
874    if !bool::from(
875        provenance
876            .cluster_authenticator
877            .as_bytes()
878            .ct_eq(expected_authenticator.as_bytes()),
879    ) {
880        return Err(BudgetStoreError::Invariant(
881            "budget snapshot anchor provenance cluster authentication failed".to_string(),
882        ));
883    }
884    let expected_anchor_digest =
885        budget_snapshot_anchor_set_digest(&snapshot.usage_history_anchors)?;
886    let mut previous_digest = BUDGET_ANCHOR_GENESIS_DIGEST;
887    for (index, signed) in provenance.chain.iter().enumerate() {
888        let expected_sequence = u64::try_from(index)
889            .ok()
890            .and_then(|value| value.checked_add(1))
891            .ok_or_else(|| {
892                BudgetStoreError::Invariant(
893                    "budget snapshot anchor provenance sequence overflowed".to_string(),
894                )
895            })?;
896        let body = &signed.body;
897        if body.schema != "chio.budget-snapshot-anchor-commitment.v1"
898            || body.commit_sequence != expected_sequence
899            || body.previous_chain_digest != previous_digest
900            || body.election_term == 0
901            || body.leader_url.is_empty()
902            || body.signer_public_key.is_empty()
903            || budget_snapshot_anchor_chain_digest(body)? != body.chain_digest
904        {
905            return Err(BudgetStoreError::Invariant(
906                "budget snapshot anchor provenance chain is invalid".to_string(),
907            ));
908        }
909        let signer = PublicKey::from_hex(&body.signer_public_key)
910            .map_err(|error| BudgetStoreError::Invariant(error.to_string()))?;
911        if !signer
912            .verify_canonical(body, &signed.signature)
913            .map_err(|error| BudgetStoreError::Invariant(error.to_string()))?
914        {
915            return Err(BudgetStoreError::Invariant(
916                "budget snapshot anchor provenance signature is invalid".to_string(),
917            ));
918        }
919        previous_digest = &body.chain_digest;
920    }
921    let head = provenance.chain.last().ok_or_else(|| {
922        BudgetStoreError::Invariant("budget snapshot anchor provenance is empty".to_string())
923    })?;
924    if head.body.leader_url != expected_leader_url
925        || head.body.election_term != expected_election_term
926        || head.body.anchor_set_digest != expected_anchor_digest
927    {
928        return Err(BudgetStoreError::Invariant(
929            "budget snapshot anchor provenance is not bound to the elected leader, term, and exact anchor set"
930                .to_string(),
931        ));
932    }
933    Ok(&head.body)
934}
935
936fn verify_local_anchor_provenance_continuity(
937    transaction: &rusqlite::Transaction<'_>,
938    provenance: &BudgetSnapshotAnchorProvenance,
939) -> Result<(), BudgetStoreError> {
940    let head = provenance.chain.last().ok_or_else(|| {
941        BudgetStoreError::Invariant("budget snapshot anchor provenance is empty".to_string())
942    })?;
943    let local = transaction
944        .query_row(
945            r#"
946            SELECT commit_sequence, chain_digest
947            FROM budget_snapshot_anchor_provenance
948            WHERE leader_url = ?1
949            "#,
950            params![&head.body.leader_url],
951            |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
952        )
953        .optional()?;
954    let Some((local_sequence, local_digest)) = local else {
955        return Ok(());
956    };
957    let local_sequence = local_sequence.max(0) as u64;
958    let index = usize::try_from(local_sequence.saturating_sub(1)).map_err(|_| {
959        BudgetStoreError::Invariant(
960            "persisted budget snapshot anchor provenance sequence is invalid".to_string(),
961        )
962    })?;
963    let committed = provenance.chain.get(index).ok_or_else(|| {
964        BudgetStoreError::Invariant(
965            "budget snapshot anchor provenance rewinds the persisted leader chain".to_string(),
966        )
967    })?;
968    if committed.body.commit_sequence != local_sequence
969        || committed.body.chain_digest != local_digest
970    {
971        return Err(BudgetStoreError::Invariant(
972            "budget snapshot anchor provenance forks the persisted leader chain".to_string(),
973        ));
974    }
975    Ok(())
976}