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