Skip to main content

liminal_server/auth_membership/
authority.rs

1use super::{AtomicU64, GrantLog, MembershipRefusal, Ordering, Resource, Subject};
2
3/// The ACCESS-005 grant authority's state, as the membership gate reads it
4/// (ACCESS-006 R2).
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum AuthorityState {
7    /// No verified baseline yet.
8    NotReady,
9    /// Baseline, cursor and indexes agree on `log` at `revision`, in resume
10    /// generation `generation`.
11    Ready {
12        /// The grant log the authority is current on.
13        log: GrantLog,
14        /// The last applied receipt revision.
15        revision: u64,
16        /// The settled resume generation.
17        generation: u64,
18    },
19    /// The change feed or the provider is lost.
20    Unavailable {
21        /// The stable cause.
22        cause: String,
23    },
24    /// The change feed has an uncovered interval.
25    Gap {
26        /// The last revision applied before the gap.
27        after: u64,
28        /// How many revisions are missing.
29        missing: u64,
30    },
31    /// The grant log was reset and the new baseline is not yet verified.
32    Reset,
33}
34
35/// The seam ACCESS-005's versioned grant owner implements for membership
36/// (ACCESS-006 R2): its current state, read locally with no network call and
37/// no wait.
38///
39/// The one owner per grant log drives ordered changes and resumes
40/// into the gate through [`MembershipListener`]; there is no second feed.
41pub trait MembershipAuthority: Send + Sync + std::fmt::Debug {
42    /// The authority's current state.
43    fn state(&self) -> AuthorityState;
44}
45
46/// One applied grant change, in the owner's order.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct MembershipChange {
49    /// The grant log the change belongs to.
50    pub log: GrantLog,
51    /// Its receipt revision.
52    pub revision: u64,
53    /// The subject whose grant changed; `None` when every holder on the
54    /// resource is affected.
55    pub subject: Option<Subject>,
56    /// The resource the changed grant is held on: a channel or a workspace.
57    pub resource: Resource,
58}
59
60/// What applying one change did.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
62pub struct AppliedChange {
63    /// The revision was already applied: nothing was touched.
64    pub duplicate: bool,
65    /// Retained bindings the change's resource reaches.
66    pub affected: u64,
67    /// Of those, re-decided at the change's revision.
68    pub redecided: u64,
69    /// Of those, released because the provider could not answer; the next
70    /// act asks again.
71    pub failed: u64,
72}
73
74/// What one resume did.
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
76pub struct ResumeReport {
77    /// Retained bindings re-decided once for the generation.
78    pub rechecked: u64,
79    /// Of those, released because the provider could not answer.
80    pub failed: u64,
81    /// Whether every binding agreed and the gate adopted the generation:
82    /// the owner republishes `Ready` only when this holds.
83    pub settled: bool,
84}
85
86/// The gate's side of the ACCESS-005 owner's ordered consumer.
87///
88/// The owner
89/// calls [`Self::changed`] after applying each change and before it admits
90/// any later act, and [`Self::resumed`] after a resume's contiguous tail and
91/// before it republishes `Ready`.
92pub trait MembershipListener: Send + Sync {
93    /// Re-decides the retained bindings `change` reaches.
94    ///
95    /// # Errors
96    /// [`MembershipRefusal::LogMismatch`] for another log; the index's own
97    /// refusals.
98    fn changed(&self, change: &MembershipChange) -> Result<AppliedChange, MembershipRefusal>;
99
100    /// Re-decides every retained binding once at `revision` and adopts
101    /// `generation` when all agree.
102    ///
103    /// # Errors
104    /// The index's own refusals.
105    fn resumed(&self, generation: u64, revision: u64) -> Result<ResumeReport, MembershipRefusal>;
106}
107
108/// The result of a membership check.
109#[derive(Clone, Debug, PartialEq, Eq)]
110pub enum Decided {
111    /// The channel is not membership-protected: its prior contract applies.
112    NotProtected,
113    /// Allowed by `grant`, decided at `revision`.
114    Allowed {
115        /// The granting receipt.
116        grant: String,
117        /// The revision decided at.
118        revision: u64,
119    },
120}
121
122/// Every count the gate keeps, by owned phase (ACCESS-006 R6). There is no
123/// history, roster or whole-state structure to traverse or clone, so no such
124/// count exists; a page's rows are counted as received.
125#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
126pub struct MembershipCounts {
127    /// Membership decisions asked of the provider.
128    pub decisions_asked: u64,
129    /// Admission decisions asked of the provider.
130    pub admissions_asked: u64,
131    /// Pages asked of the provider.
132    pub pages_asked: u64,
133    /// Page rows received.
134    pub page_rows: u64,
135    /// Checks answered from the index.
136    pub local_lookups: u64,
137    /// Release checks made at a delivery seam.
138    pub release_checks: u64,
139    /// Refused checks.
140    pub refused: u64,
141    /// Bindings a change's reverse index visited.
142    pub affected_visited: u64,
143    /// Bindings a resume re-decided.
144    pub resume_rechecks: u64,
145    /// Bindings released when their last connection closed.
146    pub released: u64,
147    /// Index lock acquisitions.
148    pub locks: u64,
149}
150
151#[derive(Debug, Default)]
152pub(super) struct Counters {
153    pub(super) decisions_asked: AtomicU64,
154    pub(super) admissions_asked: AtomicU64,
155    pub(super) pages_asked: AtomicU64,
156    pub(super) page_rows: AtomicU64,
157    pub(super) local_lookups: AtomicU64,
158    pub(super) release_checks: AtomicU64,
159    pub(super) refused: AtomicU64,
160    pub(super) affected_visited: AtomicU64,
161    pub(super) resume_rechecks: AtomicU64,
162    pub(super) released: AtomicU64,
163    pub(super) locks: AtomicU64,
164}
165
166impl Counters {
167    pub(super) fn snapshot(&self) -> MembershipCounts {
168        let read = |counter: &AtomicU64| counter.load(Ordering::Relaxed);
169        MembershipCounts {
170            decisions_asked: read(&self.decisions_asked),
171            admissions_asked: read(&self.admissions_asked),
172            pages_asked: read(&self.pages_asked),
173            page_rows: read(&self.page_rows),
174            local_lookups: read(&self.local_lookups),
175            release_checks: read(&self.release_checks),
176            refused: read(&self.refused),
177            affected_visited: read(&self.affected_visited),
178            resume_rechecks: read(&self.resume_rechecks),
179            released: read(&self.released),
180            locks: read(&self.locks),
181        }
182    }
183}