Skip to main content

tf_types/
store.rs

1#![allow(clippy::doc_overindented_list_items)]
2//! Persistence-backend traits for proof ledger, revocation cache, and
3//! evidence archive. Implementations live in separate crates (tf-store-*).
4//!
5//! These traits let `tf-daemon` (and other components) treat their proof
6//! ledger, revocation cache, and evidence archive as pluggable backends:
7//! a deployment can run SQLite for a home profile, Postgres or MySQL for
8//! enterprise, and Redis as a fast revocation cache fronting any of the
9//! durable ledgers, all without touching daemon code.
10//!
11//! Implementations:
12//! * `tf-store-file`       — first-party file-backed local store, all
13//!                            three traits.
14//! * `tf-store-sqlite`     — single-file embedded SQLite, all three traits.
15//! * `tf-store-postgres`   — sqlx-backed Postgres, all three traits.
16//! * `tf-store-mysql`      — sqlx-backed MySQL, all three traits.
17//! * `tf-revoke-redis`     — Redis-backed `RevocationCache` only (Redis is
18//!                            the wrong shape for an append-only ledger but
19//!                            an excellent fast-path for revocation checks).
20
21use serde_json::Value;
22
23/// Errors returned by every persistence backend.
24///
25/// Backends MUST map their native error types onto these variants so the
26/// daemon can treat them uniformly. `Unavailable` is reserved for transient
27/// connectivity / pool exhaustion; `NotFound` for explicit absence;
28/// `Conflict` for unique-constraint or optimistic-lock failures; `Other`
29/// for everything else (with a human-readable message).
30#[derive(Debug, thiserror::Error)]
31pub enum StoreError {
32    #[error("backend unavailable: {0}")]
33    Unavailable(String),
34    #[error("not found")]
35    NotFound,
36    #[error("conflict")]
37    Conflict,
38    #[error("backend error: {0}")]
39    Other(String),
40}
41
42/// Append-only ledger of TrustForge proof events.
43///
44/// `append` returns the canonical event hash (implementation-defined; the
45/// SQLite/Postgres/MySQL backends use SHA-256 over canonical JSON). Lookup
46/// is by that hash; `tail` returns the most recent `limit` events in
47/// insertion order (oldest first within the slice).
48pub trait ProofLedger: Send + Sync {
49    fn append(&self, event: &Value) -> Result<String, StoreError>;
50    fn lookup(&self, event_hash: &str) -> Result<Option<Value>, StoreError>;
51    fn tail(&self, limit: usize) -> Result<Vec<Value>, StoreError>;
52}
53
54/// Revocation set. Conceptually a `(target_kind, target_id) -> effective_at`
55/// map; `is_revoked` answers "was this target revoked at or before `at`?"
56///
57/// The SQL backends store this as a regular table; Redis stores it as
58/// `tf:revoke:<kind>:<id>` keys whose value is the effective_at timestamp.
59pub trait RevocationCache: Send + Sync {
60    fn insert(
61        &self,
62        target_kind: &str,
63        target_id: &str,
64        effective_at: &str,
65    ) -> Result<(), StoreError>;
66    fn is_revoked(&self, target_kind: &str, target_id: &str, at: &str) -> Result<bool, StoreError>;
67    fn list(&self) -> Result<Vec<(String, String, String)>, StoreError>;
68}
69
70/// Opaque-byte evidence-bundle archive (e.g. compliance bundles per
71/// TF-0012). Bundles are addressed by an external bundle id, not a content
72/// hash, because callers may want to overwrite or version a bundle outside
73/// the archive's responsibility.
74pub trait EvidenceArchive: Send + Sync {
75    fn put(&self, bundle_id: &str, bytes: &[u8]) -> Result<(), StoreError>;
76    fn get(&self, bundle_id: &str) -> Result<Option<Vec<u8>>, StoreError>;
77    fn list(&self) -> Result<Vec<String>, StoreError>;
78}