1use liminal_protocol::client::{
2 DetachReplayRefusalReason, DetachTransportAttemptDecision, DetachTransportFate,
3 DetachTransportFateDecision, ExplicitReconnectAction, LostAuthorityKind,
4 LostOperationAuthorityDecision, LostReconnectAuthorityDecision, ProvedOnlineTransition,
5 ReconnectAttemptDecision, ReconnectAttemptFate, ReconnectAttemptFateDecision,
6 ReconnectAttemptFateRefusalReason, ReconnectAttemptRefusalReason, ReconnectPermitDecision,
7 RecoveredExpectedOperationDecision, RecoveredReconnectPermitDecision, record_attempt_fate,
8 record_explicit_reconnect, record_online_transition, recover_expected_operation,
9 recover_reconnect_permit, redeem_attempt, resolve_lost_operation_authority,
10 resolve_lost_reconnect_authority, transport_attempt_started, transport_fate,
11};
12use liminal_protocol::outcome::ReconnectState;
13use liminal_protocol::wire::{ClientRequest, DetachRequest};
14
15use super::{
16 OperationDurability, ParticipantResumeStore, RemoteOperationTransportFate,
17 RemoteParticipantError, RemoteParticipantHandle, RemoteParticipantOperation,
18 RemoteParticipantSendOutcome, RemoteReconnectPermit, RemoteReconnectPermitOutcome, persist,
19 record_connection_fate, record_operation_transport_fate, take_aggregate,
20};
21
22#[derive(Debug)]
24pub enum RemoteExpectedOperationRecovery {
25 Recovered(RemoteParticipantOperation),
27 NotAvailable {
29 already_issued: bool,
31 },
32}
33
34#[derive(Debug, PartialEq, Eq)]
36pub enum RemoteLostOperationResolution {
37 Recorded {
39 request: ClientRequest,
41 testimony: LostAuthorityKind,
43 },
44 DetachParked {
46 request: ClientRequest,
48 testimony: LostAuthorityKind,
50 },
51 Refused {
53 reason: liminal_protocol::client::LostAuthorityResolutionRefusalReason,
55 },
56}
57
58#[derive(Debug, PartialEq, Eq)]
60pub enum RemoteLostReconnectResolution {
61 Recorded {
63 testimony: LostAuthorityKind,
65 },
66 Refused {
68 reason: liminal_protocol::client::LostAuthorityResolutionRefusalReason,
70 },
71}
72
73#[derive(Debug)]
75pub enum RemoteReconnectPermitRecovery {
76 Recovered(RemoteReconnectPermit),
78 NotAvailable {
80 state: ReconnectState,
82 },
83}
84
85#[derive(Debug)]
87pub enum RemoteReconnectAttemptOutcome {
88 Connected {
90 provenance: super::ParticipantResponseProvenance,
92 },
93 Failed {
95 error: crate::SdkError,
97 },
98 Refused {
100 permit: RemoteReconnectPermit,
102 reason: ReconnectAttemptRefusalReason,
104 },
105 FateRefused {
107 reason: ReconnectAttemptFateRefusalReason,
109 error: Option<crate::SdkError>,
111 provenance: Option<super::ParticipantResponseProvenance>,
113 },
114}
115
116#[derive(Debug)]
118pub enum RemoteDetachReplayOutcome {
119 Send(RemoteParticipantSendOutcome),
121 Refused {
123 reason: DetachReplayRefusalReason,
125 },
126}
127
128#[derive(Debug, PartialEq, Eq)]
130pub enum RemoteReplayApplyOutcome<T> {
131 Applied,
133 Refused {
135 input: T,
137 reason: DetachReplayRefusalReason,
139 },
140}
141
142#[derive(Debug)]
144pub struct RemoteTransportLossOutcome {
145 pub operation_fate: RemoteOperationTransportFate,
147 pub reconnect: RemoteReconnectPermitOutcome,
149}
150
151impl<S: ParticipantResumeStore> RemoteParticipantHandle<S> {
152 pub fn recover_expected_operation(
159 &self,
160 ) -> Result<RemoteExpectedOperationRecovery, RemoteParticipantError> {
161 let mut state = self.state.lock();
162 let aggregate = take_aggregate(&mut state)?;
163 match recover_expected_operation(aggregate) {
164 RecoveredExpectedOperationDecision::Recovered {
165 aggregate,
166 operation,
167 } => {
168 state.aggregate = Some(aggregate);
169 Ok(RemoteExpectedOperationRecovery::Recovered(
170 RemoteParticipantOperation {
171 operation,
172 durability: OperationDurability::WriteAhead,
173 },
174 ))
175 }
176 RecoveredExpectedOperationDecision::NotAvailable {
177 aggregate,
178 already_issued,
179 } => {
180 state.aggregate = Some(aggregate);
181 Ok(RemoteExpectedOperationRecovery::NotAvailable { already_issued })
182 }
183 }
184 }
185
186 pub fn resolve_lost_operation_authority(
192 &self,
193 ) -> Result<RemoteLostOperationResolution, RemoteParticipantError> {
194 let mut state = self.state.lock();
195 let aggregate = take_aggregate(&mut state)?;
196 let outcome = match resolve_lost_operation_authority(aggregate) {
197 LostOperationAuthorityDecision::Recorded {
198 aggregate,
199 request,
200 testimony,
201 } => {
202 state.aggregate = Some(aggregate);
203 RemoteLostOperationResolution::Recorded {
204 request,
205 testimony: testimony.kind(),
206 }
207 }
208 LostOperationAuthorityDecision::DetachParked {
209 aggregate,
210 request,
211 testimony,
212 } => {
213 state.aggregate = Some(aggregate);
214 RemoteLostOperationResolution::DetachParked {
215 request,
216 testimony: testimony.kind(),
217 }
218 }
219 LostOperationAuthorityDecision::Refused { aggregate, reason } => {
220 state.aggregate = Some(aggregate);
221 RemoteLostOperationResolution::Refused { reason }
222 }
223 };
224 checkpoint_state(&mut state)?;
225 Ok(outcome)
226 }
227
228 pub fn take_restored_operation_abandonment(
234 &self,
235 ) -> Result<
236 Option<liminal_protocol::client::RestoredExpectedOperationAbandonment>,
237 RemoteParticipantError,
238 > {
239 let mut state = self.state.lock();
240 let mut aggregate = take_aggregate(&mut state)?;
241 let abandonment = aggregate.take_restored_operation_abandonment();
242 if abandonment.is_some() {
243 persist(&mut state.store, &aggregate)?;
244 }
245 state.aggregate = Some(aggregate);
246 Ok(abandonment)
247 }
248
249 pub fn record_transport_fate(
255 &self,
256 ) -> Result<RemoteReconnectPermitOutcome, RemoteParticipantError> {
257 let mut state = self.state.lock();
258 record_connection_fate(&mut state)
259 }
260
261 pub fn record_online_transition(
267 &self,
268 ) -> Result<RemoteReconnectPermitOutcome, RemoteParticipantError> {
269 self.record_fresh_reconnect(|aggregate| {
270 record_online_transition(aggregate, ProvedOnlineTransition::ProvedOnline)
271 })
272 }
273
274 pub fn record_explicit_reconnect(
280 &self,
281 ) -> Result<RemoteReconnectPermitOutcome, RemoteParticipantError> {
282 self.record_fresh_reconnect(|aggregate| {
283 record_explicit_reconnect(aggregate, ExplicitReconnectAction::ReconnectNow)
284 })
285 }
286
287 fn record_fresh_reconnect(
288 &self,
289 decide: impl FnOnce(
290 liminal_protocol::client::ClientParticipantAggregate,
291 ) -> ReconnectPermitDecision,
292 ) -> Result<RemoteReconnectPermitOutcome, RemoteParticipantError> {
293 let mut state = self.state.lock();
294 let aggregate = take_aggregate(&mut state)?;
295 let outcome = match decide(aggregate) {
296 ReconnectPermitDecision::Permitted {
297 aggregate,
298 permit,
299 result,
300 } => {
301 state.aggregate = Some(aggregate);
302 RemoteReconnectPermitOutcome::Permitted {
303 permit: RemoteReconnectPermit { permit },
304 result,
305 }
306 }
307 ReconnectPermitDecision::Refused(refusal) => {
308 let reason = refusal.reason();
309 let result = refusal.result();
310 let (aggregate, _) = refusal.into_parts();
311 state.aggregate = Some(aggregate);
312 RemoteReconnectPermitOutcome::Refused { reason, result }
313 }
314 };
315 checkpoint_state(&mut state)?;
316 Ok(outcome)
317 }
318
319 pub fn recover_reconnect_permit(
325 &self,
326 ) -> Result<RemoteReconnectPermitRecovery, RemoteParticipantError> {
327 let mut state = self.state.lock();
328 let aggregate = take_aggregate(&mut state)?;
329 match recover_reconnect_permit(aggregate) {
330 RecoveredReconnectPermitDecision::Recovered { aggregate, permit } => {
331 state.aggregate = Some(aggregate);
332 Ok(RemoteReconnectPermitRecovery::Recovered(
333 RemoteReconnectPermit { permit },
334 ))
335 }
336 RecoveredReconnectPermitDecision::NotAvailable {
337 aggregate,
338 state: value,
339 } => {
340 state.aggregate = Some(aggregate);
341 Ok(RemoteReconnectPermitRecovery::NotAvailable { state: value })
342 }
343 }
344 }
345
346 pub fn resolve_lost_reconnect_authority(
352 &self,
353 ) -> Result<RemoteLostReconnectResolution, RemoteParticipantError> {
354 let mut state = self.state.lock();
355 let aggregate = take_aggregate(&mut state)?;
356 let outcome = match resolve_lost_reconnect_authority(aggregate) {
357 LostReconnectAuthorityDecision::Recorded {
358 aggregate,
359 testimony,
360 } => {
361 state.aggregate = Some(aggregate);
362 RemoteLostReconnectResolution::Recorded {
363 testimony: testimony.kind(),
364 }
365 }
366 LostReconnectAuthorityDecision::Refused { aggregate, reason } => {
367 state.aggregate = Some(aggregate);
368 RemoteLostReconnectResolution::Refused { reason }
369 }
370 };
371 checkpoint_state(&mut state)?;
372 Ok(outcome)
373 }
374
375 pub fn reconnect(
381 &self,
382 permit: RemoteReconnectPermit,
383 ) -> Result<RemoteReconnectAttemptOutcome, RemoteParticipantError> {
384 let mut state = self.state.lock();
385 let aggregate = take_aggregate(&mut state)?;
386 let (aggregate, attempt) = match redeem_attempt(aggregate, permit.permit) {
387 ReconnectAttemptDecision::Started { aggregate, attempt } => (aggregate, attempt),
388 ReconnectAttemptDecision::Refused {
389 aggregate,
390 permit,
391 reason,
392 } => {
393 state.aggregate = Some(aggregate);
394 return Ok(RemoteReconnectAttemptOutcome::Refused {
395 permit: RemoteReconnectPermit { permit },
396 reason,
397 });
398 }
399 };
400 persist(&mut state.store, &aggregate)?;
401 state.aggregate = Some(aggregate);
402 drop(state);
403
404 let transport_result = self.transport.reconnect_participant(&self.server_address);
405 let fate = if transport_result.is_ok() {
406 ReconnectAttemptFate::Connected
407 } else {
408 ReconnectAttemptFate::Failed
409 };
410
411 let mut state = self.state.lock();
412 let aggregate = take_aggregate(&mut state)?;
413 match record_attempt_fate(aggregate, attempt, fate) {
414 ReconnectAttemptFateDecision::Recorded(aggregate) => {
415 persist(&mut state.store, &aggregate)?;
416 state.aggregate = Some(aggregate);
417 match transport_result {
418 Ok(provenance) => Ok(RemoteReconnectAttemptOutcome::Connected { provenance }),
419 Err(error) => Ok(RemoteReconnectAttemptOutcome::Failed { error }),
420 }
421 }
422 ReconnectAttemptFateDecision::Refused {
423 aggregate,
424 attempt,
425 reason,
426 ..
427 } => {
428 state.aggregate = Some(aggregate);
429 state.reconnect_attempt = Some(attempt);
430 let (provenance, error) = match transport_result {
431 Ok(value) => (Some(value), None),
432 Err(value) => (None, Some(value)),
433 };
434 Ok(RemoteReconnectAttemptOutcome::FateRefused {
435 reason,
436 error,
437 provenance,
438 })
439 }
440 }
441 }
442
443 pub fn record_established_transport_loss(
449 &self,
450 ) -> Result<RemoteTransportLossOutcome, RemoteParticipantError> {
451 let mut state = self.state.lock();
452 let operation_fate = if let Some(correlation) = state.correlation.take() {
453 let aggregate = take_aggregate(&mut state)?;
454 record_operation_transport_fate(&mut state, aggregate, correlation)
455 } else {
456 RemoteOperationTransportFate::NotOutstanding
457 };
458 let reconnect = record_connection_fate(&mut state)?;
459 Ok(RemoteTransportLossOutcome {
460 operation_fate,
461 reconnect,
462 })
463 }
464
465 pub fn replay_detach(&self) -> Result<RemoteDetachReplayOutcome, RemoteParticipantError> {
471 let mut state = self.state.lock();
472 let aggregate = take_aggregate(&mut state)?;
473 let (aggregate, attempt) = match transport_attempt_started(aggregate) {
474 DetachTransportAttemptDecision::Started { aggregate, attempt } => (aggregate, attempt),
475 DetachTransportAttemptDecision::Refused(refusal) => {
476 let reason = refusal.reason();
477 let (aggregate, ()) = refusal.into_parts();
478 state.aggregate = Some(aggregate);
479 return Ok(RemoteDetachReplayOutcome::Refused { reason });
480 }
481 };
482 persist(&mut state.store, &aggregate)?;
483 let (request, correlation) = attempt.into_request();
484 let request = ClientRequest::Detach(DetachRequest {
485 conversation_id: request.conversation_id,
486 participant_id: request.participant_id,
487 capability_generation: request.capability_generation,
488 detach_attempt_token: request.detach_attempt_token,
489 });
490 match self
491 .transport
492 .send_participant(&self.server_address, &request)
493 {
494 Ok(provenance) => {
495 state.aggregate = Some(aggregate);
496 state.correlation = Some(correlation);
497 Ok(RemoteDetachReplayOutcome::Send(
498 RemoteParticipantSendOutcome::Sent { provenance },
499 ))
500 }
501 Err(error) => {
502 let operation_fate = match transport_fate(
503 aggregate,
504 correlation,
505 DetachTransportFate::ResponseUnavailable,
506 ) {
507 DetachTransportFateDecision::Parked(applied) => {
508 state.aggregate = Some(applied.into_aggregate());
509 RemoteOperationTransportFate::DetachParked
510 }
511 DetachTransportFateDecision::Refused(refusal) => {
512 let (aggregate, (correlation, _)) = refusal.into_parts();
513 state.aggregate = Some(aggregate);
514 state.correlation = Some(correlation);
515 RemoteOperationTransportFate::Refused {
516 reason: liminal_protocol::client::ExpectedOperationFateRefusalReason::DetachUsesReplayFate,
517 }
518 }
519 };
520 let reconnect = record_connection_fate(&mut state)?;
521 Ok(RemoteDetachReplayOutcome::Send(
522 RemoteParticipantSendOutcome::TransportLost {
523 error,
524 operation_fate,
525 reconnect,
526 },
527 ))
528 }
529 }
530 }
531}
532
533fn checkpoint_state<S: ParticipantResumeStore>(
534 state: &mut super::RemoteParticipantState<S>,
535) -> Result<(), RemoteParticipantError> {
536 let aggregate = take_aggregate(state)?;
537 persist(&mut state.store, &aggregate)?;
538 state.aggregate = Some(aggregate);
539 Ok(())
540}