Skip to main content

tea_session/
store.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use tea_policy::{ActorId, PolicyGrant};
5use tea_protocol::{RecordEnvelope, SessionId, SessionSequence};
6
7use crate::{
8    ApprovalArtifactEntry, GrantJournalEntry, MaterializedSessionState, SessionStoreError,
9};
10
11/// Runtime-neutral boxed future returned by session storage ports.
12pub type SessionStoreFuture<'a, T> =
13    Pin<Box<dyn Future<Output = Result<T, SessionStoreError>> + Send + 'a>>;
14
15/// Atomic append request guarded by the caller's expected session tail.
16#[derive(Debug, Clone)]
17pub struct AppendTransaction {
18    session_id: SessionId,
19    expected_sequence: Option<SessionSequence>,
20    records: Vec<RecordEnvelope>,
21    expected_journal_revision: Option<u64>,
22    approval_artifacts: Vec<ApprovalArtifactEntry>,
23    grant_entries: Vec<GrantJournalEntry>,
24}
25
26impl AppendTransaction {
27    /// Creates a canonical-record append transaction.
28    #[must_use]
29    pub fn new(
30        session_id: SessionId,
31        expected_sequence: Option<SessionSequence>,
32        records: Vec<RecordEnvelope>,
33    ) -> Self {
34        Self {
35            session_id,
36            expected_sequence,
37            records,
38            expected_journal_revision: None,
39            approval_artifacts: Vec::new(),
40            grant_entries: Vec::new(),
41        }
42    }
43
44    /// Guards typed side-journal writes with the expected current revision.
45    #[must_use]
46    pub const fn with_expected_journal_revision(mut self, revision: u64) -> Self {
47        self.expected_journal_revision = Some(revision);
48        self
49    }
50
51    /// Attaches typed approval artifacts committed with canonical transitions.
52    #[must_use]
53    pub fn with_approval_artifacts(
54        mut self,
55        entries: impl IntoIterator<Item = ApprovalArtifactEntry>,
56    ) -> Self {
57        self.approval_artifacts = entries.into_iter().collect();
58        self
59    }
60
61    /// Attaches append-only grant journal facts committed with this transaction.
62    #[must_use]
63    pub fn with_grant_entries(
64        mut self,
65        entries: impl IntoIterator<Item = GrantJournalEntry>,
66    ) -> Self {
67        self.grant_entries = entries.into_iter().collect();
68        self
69    }
70
71    /// Returns the target session.
72    #[must_use]
73    pub const fn session_id(&self) -> SessionId {
74        self.session_id
75    }
76
77    /// Returns expected existing tail, or `None` for creation.
78    #[must_use]
79    pub const fn expected_sequence(&self) -> Option<SessionSequence> {
80        self.expected_sequence
81    }
82
83    /// Returns ordered canonical records.
84    #[must_use]
85    pub fn records(&self) -> &[RecordEnvelope] {
86        &self.records
87    }
88
89    /// Returns expected policy journal revision, when side facts are appended.
90    #[must_use]
91    pub const fn expected_journal_revision(&self) -> Option<u64> {
92        self.expected_journal_revision
93    }
94
95    /// Returns typed approval side-journal entries.
96    #[must_use]
97    pub fn approval_artifacts(&self) -> &[ApprovalArtifactEntry] {
98        &self.approval_artifacts
99    }
100
101    /// Returns typed grant side-journal entries.
102    #[must_use]
103    pub fn grant_entries(&self) -> &[GrantJournalEntry] {
104        &self.grant_entries
105    }
106}
107
108/// Successful append details and rebuilt current projection.
109#[derive(Debug, Clone, PartialEq)]
110pub struct AppendOutcome {
111    previous_sequence: Option<SessionSequence>,
112    current_sequence: SessionSequence,
113    state: MaterializedSessionState,
114    journal_revision: u64,
115}
116
117impl AppendOutcome {
118    pub(crate) const fn new(
119        previous_sequence: Option<SessionSequence>,
120        current_sequence: SessionSequence,
121        state: MaterializedSessionState,
122        journal_revision: u64,
123    ) -> Self {
124        Self {
125            previous_sequence,
126            current_sequence,
127            state,
128            journal_revision,
129        }
130    }
131
132    /// Returns the durable tail before this append, or `None` on creation.
133    #[must_use]
134    pub const fn previous_sequence(&self) -> Option<SessionSequence> {
135        self.previous_sequence
136    }
137
138    /// Returns the durable tail after this append.
139    #[must_use]
140    pub const fn current_sequence(&self) -> SessionSequence {
141        self.current_sequence
142    }
143
144    /// Returns policy side-journal revision after this transaction.
145    #[must_use]
146    pub const fn journal_revision(&self) -> u64 {
147        self.journal_revision
148    }
149
150    /// Returns the materialized state committed with this append.
151    #[must_use]
152    pub const fn state(&self) -> &MaterializedSessionState {
153        &self.state
154    }
155}
156
157/// Complete immutable read view of one stored session.
158#[derive(Debug, Clone, PartialEq)]
159pub struct SessionSnapshot {
160    records: Vec<RecordEnvelope>,
161    state: MaterializedSessionState,
162    approval_artifacts: Vec<ApprovalArtifactEntry>,
163    grant_journal: Vec<GrantJournalEntry>,
164    active_grants: Vec<PolicyGrant>,
165    journal_revision: u64,
166}
167
168impl SessionSnapshot {
169    pub(crate) const fn new(
170        records: Vec<RecordEnvelope>,
171        state: MaterializedSessionState,
172        approval_artifacts: Vec<ApprovalArtifactEntry>,
173        grant_journal: Vec<GrantJournalEntry>,
174        active_grants: Vec<PolicyGrant>,
175        journal_revision: u64,
176    ) -> Self {
177        Self {
178            records,
179            state,
180            approval_artifacts,
181            grant_journal,
182            active_grants,
183            journal_revision,
184        }
185    }
186
187    /// Returns canonical source records in authoritative sequence order.
188    #[must_use]
189    pub fn records(&self) -> &[RecordEnvelope] {
190        &self.records
191    }
192
193    /// Returns current rebuildable projection.
194    #[must_use]
195    pub const fn state(&self) -> &MaterializedSessionState {
196        &self.state
197    }
198
199    /// Returns append-only rich approval artifacts.
200    #[must_use]
201    pub fn approval_artifacts(&self) -> &[ApprovalArtifactEntry] {
202        &self.approval_artifacts
203    }
204
205    /// Returns append-only grant history.
206    #[must_use]
207    pub fn grant_journal(&self) -> &[GrantJournalEntry] {
208        &self.grant_journal
209    }
210
211    /// Returns current typed side-journal revision.
212    #[must_use]
213    pub const fn journal_revision(&self) -> u64 {
214        self.journal_revision
215    }
216
217    /// Returns currently non-revoked grant candidates in stable grant-ID order.
218    #[must_use]
219    pub fn active_grants(&self) -> &[PolicyGrant] {
220        &self.active_grants
221    }
222}
223
224/// Replaceable append-only session repository contract.
225pub trait SessionStore: std::fmt::Debug + Send + Sync {
226    /// Loads one immutable session snapshot.
227    fn load(&self, session_id: SessionId) -> SessionStoreFuture<'_, SessionSnapshot>;
228
229    /// Atomically validates and appends one transaction.
230    fn append(&self, transaction: AppendTransaction) -> SessionStoreFuture<'_, AppendOutcome>;
231
232    /// Returns non-revoked grant candidates issued to one actor across sessions.
233    fn active_grants_for_actor(
234        &self,
235        actor_id: ActorId,
236    ) -> SessionStoreFuture<'_, Vec<PolicyGrant>>;
237}