coven_database/store/
device_join_journal.rs1use coven_protocol::store_commit::device_join_exchange::DeviceJoinAbandonment;
5use coven_protocol::store_commit::device_join_journal::{
6 DeviceJoinJournalRecord, DeviceJoinRoleProgress, JoinerJoinProgress, OwnerJoinProgress,
7};
8
9#[derive(Debug, thiserror::Error)]
12pub enum DeviceJoinJournalError {
13 #[error("device join journal transition is not the declared adjacent transition")]
14 NonAdjacentJournalTransition,
15 #[error("device join journal has a different durable value for this role and attempt")]
16 JournalConflict,
17 #[error("device join journal: {0}")]
18 Serialization(#[from] serde_json::Error),
19 #[error("device join journal: {0}")]
20 Database(#[from] crate::DbError),
21}
22
23pub fn validate_initial_progress(
25 progress: &DeviceJoinRoleProgress,
26) -> Result<(), DeviceJoinJournalError> {
27 if matches!(
28 progress,
29 DeviceJoinRoleProgress::Owner(OwnerJoinProgress::Offered(_))
30 | DeviceJoinRoleProgress::Joiner(JoinerJoinProgress::OfferReceived(_))
31 ) {
32 Ok(())
33 } else {
34 Err(DeviceJoinJournalError::NonAdjacentJournalTransition)
35 }
36}
37
38pub fn require_initial(record: &DeviceJoinJournalRecord) -> Result<(), DeviceJoinJournalError> {
39 validate_initial_progress(&record.progress)
40}
41
42pub fn validate_successor(
43 previous: &DeviceJoinJournalRecord,
44 next: &DeviceJoinJournalRecord,
45) -> Result<(), DeviceJoinJournalError> {
46 if previous.attempt_id != next.attempt_id {
47 return Err(DeviceJoinJournalError::JournalConflict);
48 }
49 validate_transition(&previous.progress, &next.progress)
50}
51
52pub fn joiner_abandonment_retires(
62 record: &DeviceJoinJournalRecord,
63 abandonment: &DeviceJoinAbandonment,
64) -> Result<(), DeviceJoinJournalError> {
65 if record.attempt_id != abandonment.abandonment.attempt_id {
66 return Err(DeviceJoinJournalError::JournalConflict);
67 }
68 match &*record.progress {
69 DeviceJoinRoleProgress::Joiner(
70 JoinerJoinProgress::AccessRequested(_) | JoinerJoinProgress::ApprovalReceived(_),
71 ) => Ok(()),
72 _ => Err(DeviceJoinJournalError::JournalConflict),
73 }
74}
75
76fn validate_transition(
77 previous: &DeviceJoinRoleProgress,
78 next: &DeviceJoinRoleProgress,
79) -> Result<(), DeviceJoinJournalError> {
80 let adjacent = match (previous, next) {
81 (DeviceJoinRoleProgress::Owner(previous), DeviceJoinRoleProgress::Owner(next)) => {
82 owner_adjacent(previous, next)
83 }
84 (DeviceJoinRoleProgress::Joiner(previous), DeviceJoinRoleProgress::Joiner(next)) => {
85 joiner_adjacent(previous, next)
86 }
87 _ => false,
88 };
89 if adjacent {
90 Ok(())
91 } else {
92 Err(DeviceJoinJournalError::NonAdjacentJournalTransition)
93 }
94}
95
96fn owner_adjacent(previous: &OwnerJoinProgress, next: &OwnerJoinProgress) -> bool {
106 if let (
107 OwnerJoinProgress::AccessRequested(request),
108 OwnerJoinProgress::ApprovalPrepared(approval),
109 ) = (previous, next)
110 {
111 return approval.request.as_ref() == request
112 && matches!(
113 approval.admission,
114 coven_protocol::store_commit::device_join_exchange::DeviceProviderAdmission::SamePrincipal
115 );
116 }
117 if let (
118 OwnerJoinProgress::ProviderReady(ready),
119 OwnerJoinProgress::Completed(
120 coven_protocol::store_commit::device_join_exchange::DeviceProviderAdmissionCompletion::SamePrincipal {
121 bootstrap,
122 },
123 ),
124 ) = (previous, next)
125 {
126 return ready == bootstrap.as_ref();
127 }
128 matches!(
129 (previous, next),
130 (
131 OwnerJoinProgress::Offered(_),
132 OwnerJoinProgress::AccessRequested(_)
133 ) | (
134 OwnerJoinProgress::AccessRequested(_),
135 OwnerJoinProgress::AccessGrantPrepared { .. }
136 ) | (
137 OwnerJoinProgress::AccessGrantPrepared { .. },
138 OwnerJoinProgress::ApprovalPrepared(_)
139 ) | (
140 OwnerJoinProgress::ApprovalPrepared(_),
141 OwnerJoinProgress::RegistrationRequested(_)
142 ) | (
143 OwnerJoinProgress::RegistrationRequested(_),
144 OwnerJoinProgress::AttemptActivated(_)
145 ) | (
146 OwnerJoinProgress::RegistrationRequested(_),
147 OwnerJoinProgress::SamePrincipalActivationCreateIntent { .. }
148 ) | (
149 OwnerJoinProgress::SamePrincipalActivationCreateIntent { .. },
150 OwnerJoinProgress::SamePrincipalCompleted { .. }
151 ) | (
152 OwnerJoinProgress::Offered(_),
153 OwnerJoinProgress::AbandonmentCreateIntent { .. }
154 ) | (
155 OwnerJoinProgress::AccessRequested(_),
156 OwnerJoinProgress::AbandonmentCreateIntent { .. }
157 ) | (
158 OwnerJoinProgress::AccessGrantPrepared { .. },
159 OwnerJoinProgress::AbandonmentCreateIntent { .. }
160 ) | (
161 OwnerJoinProgress::ApprovalPrepared(_),
162 OwnerJoinProgress::AbandonmentCreateIntent { .. }
163 ) | (
164 OwnerJoinProgress::RegistrationRequested(_),
165 OwnerJoinProgress::AbandonmentCreateIntent { .. }
166 ) | (
167 OwnerJoinProgress::AbandonmentCreateIntent { .. },
168 OwnerJoinProgress::Abandoned(_)
169 ) | (
170 OwnerJoinProgress::AttemptActivated(_),
171 OwnerJoinProgress::ChallengeCreateIntent(_)
172 ) | (
173 OwnerJoinProgress::ChallengeCreateIntent(_),
174 OwnerJoinProgress::ProviderReady(_)
175 ) | (
176 OwnerJoinProgress::ProviderReady(_),
177 OwnerJoinProgress::ResponseObserved(_)
178 ) | (
179 OwnerJoinProgress::ResponseObserved(_),
180 OwnerJoinProgress::Completed(_)
181 ) | (
182 OwnerJoinProgress::Completed(_),
183 OwnerJoinProgress::ActivationCreateIntent { .. }
184 ) | (
185 OwnerJoinProgress::ActivationCreateIntent { .. },
186 OwnerJoinProgress::ActivationPrepared { .. }
187 )
188 )
189}
190
191fn joiner_adjacent(previous: &JoinerJoinProgress, next: &JoinerJoinProgress) -> bool {
192 matches!(
193 (previous, next),
194 (
195 JoinerJoinProgress::OfferReceived(_),
196 JoinerJoinProgress::AccessRequested(_)
197 ) | (
198 JoinerJoinProgress::AccessRequested(_),
199 JoinerJoinProgress::ApprovalReceived(_)
200 ) | (
201 JoinerJoinProgress::ApprovalReceived(_),
202 JoinerJoinProgress::RegistrationPrepared(_)
203 ) | (
204 JoinerJoinProgress::RegistrationPrepared(_),
205 JoinerJoinProgress::Ready(_)
206 ) | (
207 JoinerJoinProgress::Ready(_),
208 JoinerJoinProgress::ActivationObserved { .. }
209 )
210 )
211}