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}