Skip to main content

runifold_workflow/store/
memory.rs

1use super::{
2    Arc, BTreeMap, Budget, Checkpoint, CheckpointError, CheckpointErrorKind, CheckpointId,
3    ClaimedWorkflow, Deserialize, Duration, LeaseDuration, Mutex, MutexGuard, Reverse, Serialize,
4    SystemWorkflowClock, Usage, Value, WorkerId, WorkflowBudgetAuditCursor,
5    WorkflowBudgetAuditEvent, WorkflowBudgetAuditKind, WorkflowBudgetAuditLimit,
6    WorkflowBudgetAuditProjectionId, WorkflowBudgetAuditProjectionLease,
7    WorkflowBudgetForfeitReason, WorkflowBudgetReservationOutcome, WorkflowCancelOutcome,
8    WorkflowCheckpointHistoryLimit, WorkflowCheckpointPhase, WorkflowCheckpointRevision,
9    WorkflowClock, WorkflowDisposition, WorkflowForkCommand, WorkflowForkOutcome,
10    WorkflowInterruptRequest, WorkflowLease, WorkflowLineage, WorkflowSignal, WorkflowSignalId,
11    WorkflowSignalOutcome, WorkflowSignalRetention, WorkflowSignalSnapshot, WorkflowSignalState,
12    WorkflowStore, WorkflowStoreError, WorkflowStoreErrorKind, WorkflowStoreFuture, WorkflowTask,
13    WorkflowTaskSnapshot, WorkflowTaskStatus, WorkflowTenantBudgetPolicy,
14    WorkflowTenantBudgetSnapshot, WorkflowTenantId, WorkflowTenantListLimit, WorkflowTenantPolicy,
15    WorkflowWait, WorkflowWake, decode_revision, fork_checkpoint,
16};
17
18mod budget;
19mod checkpoint;
20mod signal;
21mod task;
22
23use signal::take_buffered_signal;
24use task::{is_non_terminal, require_current_lease, require_tenant, workflow_not_found};
25
26/// Deterministic in-memory implementation of the distributed store contract.
27#[derive(Clone)]
28pub struct InMemoryWorkflowStore {
29    tasks: Arc<Mutex<BTreeMap<CheckpointId, StoredTask>>>,
30    checkpoints: Arc<Mutex<StoredCheckpoints>>,
31    signals: Arc<Mutex<BTreeMap<WorkflowSignalId, StoredSignal>>>,
32    admission: Arc<Mutex<AdmissionState>>,
33    clock: Arc<dyn WorkflowClock>,
34}
35
36impl Default for InMemoryWorkflowStore {
37    fn default() -> Self {
38        Self::with_clock(Arc::new(SystemWorkflowClock))
39    }
40}
41
42impl InMemoryWorkflowStore {
43    /// Creates a store backed by the system clock.
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Creates a store with an explicit authoritative clock.
49    pub fn with_clock(clock: Arc<dyn WorkflowClock>) -> Self {
50        Self {
51            tasks: Arc::new(Mutex::new(BTreeMap::new())),
52            checkpoints: Arc::new(Mutex::new(StoredCheckpoints::default())),
53            signals: Arc::new(Mutex::new(BTreeMap::new())),
54            admission: Arc::new(Mutex::new(AdmissionState::default())),
55            clock,
56        }
57    }
58
59    fn tasks(&self) -> MutexGuard<'_, BTreeMap<CheckpointId, StoredTask>> {
60        self.tasks
61            .lock()
62            .unwrap_or_else(std::sync::PoisonError::into_inner)
63    }
64}
65
66impl std::fmt::Debug for InMemoryWorkflowStore {
67    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        formatter
69            .debug_struct("InMemoryWorkflowStore")
70            .finish_non_exhaustive()
71    }
72}
73
74#[derive(Clone, Debug, Default, Deserialize, Serialize)]
75struct AdmissionState {
76    tenants: BTreeMap<WorkflowTenantId, StoredTenant>,
77    budgets: BTreeMap<WorkflowTenantId, StoredTenantBudget>,
78    budget_audit_projections:
79        BTreeMap<(WorkflowTenantId, WorkflowBudgetAuditProjectionId), StoredBudgetAuditProjection>,
80    next_claim_sequence: u64,
81}
82
83#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
84struct StoredTenant {
85    policy: WorkflowTenantPolicy,
86    last_claim_sequence: u64,
87}
88
89#[derive(Clone, Debug, Deserialize, Serialize)]
90struct StoredTenantBudget {
91    policy: WorkflowTenantBudgetPolicy,
92    window_started_at_ms: u64,
93    committed: Usage,
94    reserved: Usage,
95    reservations: BTreeMap<CheckpointId, StoredBudgetReservation>,
96    next_audit_sequence: u64,
97    audit_events: Vec<StoredBudgetAuditEvent>,
98}
99
100#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
101struct StoredBudgetReservation {
102    baseline: Usage,
103    amount: Usage,
104    reserved_at_ms: u64,
105    expires_at_ms: u64,
106}
107
108#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
109struct StoredBudgetAuditEvent {
110    cursor: WorkflowBudgetAuditCursor,
111    checkpoint_id: Option<CheckpointId>,
112    occurred_at_ms: u64,
113    kind: WorkflowBudgetAuditKind,
114    usage: Usage,
115    reservation_age_ms: Option<u64>,
116    limit: Budget,
117    committed: Usage,
118    reserved: Usage,
119}
120
121#[derive(Clone, Debug, Default, Deserialize, Serialize)]
122struct StoredBudgetAuditProjection {
123    cursor: WorkflowBudgetAuditCursor,
124    owner: Option<WorkerId>,
125    fencing_token: u64,
126    expires_at_ms: Option<u64>,
127}
128
129#[derive(Clone, Debug, Deserialize, Serialize)]
130struct StoredTask {
131    task: WorkflowTask,
132    state: StoredState,
133    attempts: u64,
134    fencing_token: u64,
135    wake: Option<WorkflowWake>,
136    lineage: Option<WorkflowLineage>,
137    created_at_ms: u64,
138    updated_at_ms: u64,
139}
140
141#[derive(Clone, Debug, Default, Deserialize, Serialize)]
142struct StoredCheckpoints {
143    latest: BTreeMap<CheckpointId, Checkpoint>,
144    history: BTreeMap<(CheckpointId, u64), Checkpoint>,
145}
146
147#[derive(Clone, Debug, Deserialize, Serialize)]
148enum StoredState {
149    Queued {
150        available_at_ms: u64,
151    },
152    Leased(WorkflowLease),
153    WaitingTimer {
154        wake_at_ms: u64,
155    },
156    WaitingSignal {
157        name: crate::WorkflowSignalName,
158    },
159    WaitingSignalOrTimeout {
160        name: crate::WorkflowSignalName,
161        wake_at_ms: u64,
162    },
163    WaitingInterrupt {
164        request: WorkflowInterruptRequest,
165    },
166    Completed,
167    Failed(String),
168    Cancelled,
169}
170
171#[derive(Clone, Debug, Deserialize, Serialize)]
172struct StoredSignal {
173    tenant_id: WorkflowTenantId,
174    signal: WorkflowSignal,
175    consumed: bool,
176    dead_lettered: bool,
177    #[serde(default)]
178    compaction_protected: bool,
179    accepted_at_ms: u64,
180}
181
182const PERSISTENT_SNAPSHOT_VERSION: u32 = 1;
183
184#[derive(Deserialize, Serialize)]
185struct PersistentWorkflowSnapshot {
186    version: u32,
187    tasks: Vec<(CheckpointId, StoredTask)>,
188    checkpoints: Vec<(CheckpointId, Checkpoint)>,
189    checkpoint_history: Vec<(CheckpointId, u64, Checkpoint)>,
190    signals: Vec<(WorkflowSignalId, StoredSignal)>,
191    tenants: Vec<(WorkflowTenantId, StoredTenant)>,
192    budgets: Vec<(WorkflowTenantId, StoredTenantBudget)>,
193    budget_audit_projections: Vec<(
194        WorkflowTenantId,
195        WorkflowBudgetAuditProjectionId,
196        StoredBudgetAuditProjection,
197    )>,
198    next_claim_sequence: u64,
199}
200
201impl InMemoryWorkflowStore {
202    /// Encodes the complete deterministic state machine for a durable adapter.
203    ///
204    /// This adapter boundary is intentionally byte-oriented so storage crates do
205    /// not depend on Runifold's private in-memory representation.
206    #[doc(hidden)]
207    pub fn export_persistent_snapshot(&self) -> Result<Vec<u8>, WorkflowStoreError> {
208        let tasks = self.tasks();
209        let checkpoints = self
210            .checkpoints
211            .lock()
212            .unwrap_or_else(std::sync::PoisonError::into_inner);
213        let signals = self
214            .signals
215            .lock()
216            .unwrap_or_else(std::sync::PoisonError::into_inner);
217        let admission = self
218            .admission
219            .lock()
220            .unwrap_or_else(std::sync::PoisonError::into_inner);
221        let snapshot = PersistentWorkflowSnapshot {
222            version: PERSISTENT_SNAPSHOT_VERSION,
223            tasks: tasks.iter().map(|(id, task)| (*id, task.clone())).collect(),
224            checkpoints: checkpoints
225                .latest
226                .iter()
227                .map(|(id, checkpoint)| (*id, checkpoint.clone()))
228                .collect(),
229            checkpoint_history: checkpoints
230                .history
231                .iter()
232                .map(|((id, revision), checkpoint)| (*id, *revision, checkpoint.clone()))
233                .collect(),
234            signals: signals
235                .iter()
236                .map(|(id, signal)| (*id, signal.clone()))
237                .collect(),
238            tenants: admission
239                .tenants
240                .iter()
241                .map(|(id, tenant)| (id.clone(), *tenant))
242                .collect(),
243            budgets: admission
244                .budgets
245                .iter()
246                .map(|(id, budget)| (id.clone(), budget.clone()))
247                .collect(),
248            budget_audit_projections: admission
249                .budget_audit_projections
250                .iter()
251                .map(|((tenant_id, projection_id), projection)| {
252                    (tenant_id.clone(), projection_id.clone(), projection.clone())
253                })
254                .collect(),
255            next_claim_sequence: admission.next_claim_sequence,
256        };
257        serde_json::to_vec(&snapshot).map_err(|error| {
258            WorkflowStoreError::new(
259                WorkflowStoreErrorKind::Storage,
260                format!("workflow snapshot encoding failed: {error}"),
261            )
262        })
263    }
264
265    /// Restores a complete deterministic state machine for a durable adapter.
266    #[doc(hidden)]
267    pub fn from_persistent_snapshot(
268        encoded: &[u8],
269        clock: Arc<dyn WorkflowClock>,
270    ) -> Result<Self, WorkflowStoreError> {
271        let snapshot: PersistentWorkflowSnapshot =
272            serde_json::from_slice(encoded).map_err(|error| {
273                WorkflowStoreError::new(
274                    WorkflowStoreErrorKind::Storage,
275                    format!("workflow snapshot decoding failed: {error}"),
276                )
277            })?;
278        if snapshot.version != PERSISTENT_SNAPSHOT_VERSION {
279            return Err(WorkflowStoreError::new(
280                WorkflowStoreErrorKind::Storage,
281                format!("unsupported workflow snapshot version {}", snapshot.version),
282            ));
283        }
284        let checkpoints = StoredCheckpoints {
285            latest: snapshot.checkpoints.into_iter().collect(),
286            history: snapshot
287                .checkpoint_history
288                .into_iter()
289                .map(|(id, revision, checkpoint)| ((id, revision), checkpoint))
290                .collect(),
291        };
292        let admission = AdmissionState {
293            tenants: snapshot.tenants.into_iter().collect(),
294            budgets: snapshot.budgets.into_iter().collect(),
295            budget_audit_projections: snapshot
296                .budget_audit_projections
297                .into_iter()
298                .map(|(tenant_id, projection_id, projection)| {
299                    ((tenant_id, projection_id), projection)
300                })
301                .collect(),
302            next_claim_sequence: snapshot.next_claim_sequence,
303        };
304        Ok(Self {
305            tasks: Arc::new(Mutex::new(snapshot.tasks.into_iter().collect())),
306            checkpoints: Arc::new(Mutex::new(checkpoints)),
307            signals: Arc::new(Mutex::new(snapshot.signals.into_iter().collect())),
308            admission: Arc::new(Mutex::new(admission)),
309            clock,
310        })
311    }
312}
313
314impl InMemoryWorkflowStore {
315    fn suspend(
316        &self,
317        stored: &mut StoredTask,
318        checkpoint_id: CheckpointId,
319        wait: WorkflowWait,
320        now: u64,
321    ) -> StoredState {
322        match wait {
323            WorkflowWait::Timer { delay_ms } => {
324                stored.wake = None;
325                StoredState::WaitingTimer {
326                    wake_at_ms: now.saturating_add(delay_ms),
327                }
328            }
329            WorkflowWait::Signal { name } => {
330                self.suspend_signal(stored, checkpoint_id, name, None, now)
331            }
332            WorkflowWait::SignalOrTimeout { name, timeout_ms } => {
333                self.suspend_signal(stored, checkpoint_id, name, Some(timeout_ms), now)
334            }
335            WorkflowWait::Interrupt { request } => {
336                let name = request.signal_name();
337                let mut signals = self
338                    .signals
339                    .lock()
340                    .unwrap_or_else(std::sync::PoisonError::into_inner);
341                if let Some(wake) = take_buffered_signal(&mut signals, checkpoint_id, &name) {
342                    stored.wake = Some(wake);
343                    StoredState::Queued {
344                        available_at_ms: now,
345                    }
346                } else {
347                    stored.wake = None;
348                    StoredState::WaitingInterrupt { request }
349                }
350            }
351        }
352    }
353
354    fn suspend_signal(
355        &self,
356        stored: &mut StoredTask,
357        checkpoint_id: CheckpointId,
358        name: crate::WorkflowSignalName,
359        timeout_ms: Option<u64>,
360        now: u64,
361    ) -> StoredState {
362        let mut signals = self
363            .signals
364            .lock()
365            .unwrap_or_else(std::sync::PoisonError::into_inner);
366        if let Some(wake) = take_buffered_signal(&mut signals, checkpoint_id, &name) {
367            stored.wake = Some(wake);
368            return StoredState::Queued {
369                available_at_ms: now,
370            };
371        }
372        stored.wake = None;
373        match timeout_ms {
374            Some(timeout_ms) => StoredState::WaitingSignalOrTimeout {
375                name,
376                wake_at_ms: now.saturating_add(timeout_ms),
377            },
378            None => StoredState::WaitingSignal { name },
379        }
380    }
381}
382
383impl WorkflowStore for InMemoryWorkflowStore {
384    fn current_time_ms(&self) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
385        Box::pin(async move { Ok(self.clock.now_ms()) })
386    }
387
388    fn set_tenant_policy(
389        &self,
390        tenant_id: WorkflowTenantId,
391        policy: WorkflowTenantPolicy,
392    ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
393        self.set_tenant_policy_impl(tenant_id, policy)
394    }
395
396    fn set_tenant_budget_policy(
397        &self,
398        tenant_id: WorkflowTenantId,
399        policy: WorkflowTenantBudgetPolicy,
400    ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
401        self.set_tenant_budget_policy_impl(tenant_id, policy)
402    }
403
404    fn list_tenant_budgets(
405        &self,
406        after: Option<WorkflowTenantId>,
407        limit: WorkflowTenantListLimit,
408    ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowTenantId>, WorkflowStoreError>> {
409        self.list_tenant_budgets_impl(after, limit)
410    }
411
412    fn inspect_tenant_budget(
413        &self,
414        tenant_id: WorkflowTenantId,
415    ) -> WorkflowStoreFuture<'_, Result<WorkflowTenantBudgetSnapshot, WorkflowStoreError>> {
416        self.inspect_tenant_budget_impl(tenant_id)
417    }
418
419    fn list_tenant_budget_audit(
420        &self,
421        tenant_id: WorkflowTenantId,
422        after: Option<WorkflowBudgetAuditCursor>,
423        limit: WorkflowBudgetAuditLimit,
424    ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowBudgetAuditEvent>, WorkflowStoreError>> {
425        self.list_tenant_budget_audit_impl(tenant_id, after, limit)
426    }
427
428    fn compact_tenant_budget_audit(
429        &self,
430        tenant_id: WorkflowTenantId,
431        through: WorkflowBudgetAuditCursor,
432    ) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
433        self.compact_tenant_budget_audit_impl(tenant_id, through)
434    }
435
436    fn load_or_create_tenant_budget_audit_projection(
437        &self,
438        tenant_id: WorkflowTenantId,
439        projection_id: WorkflowBudgetAuditProjectionId,
440    ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditCursor, WorkflowStoreError>> {
441        self.load_or_create_tenant_budget_audit_projection_impl(tenant_id, projection_id)
442    }
443
444    fn advance_tenant_budget_audit_projection(
445        &self,
446        tenant_id: WorkflowTenantId,
447        projection_id: WorkflowBudgetAuditProjectionId,
448        expected: WorkflowBudgetAuditCursor,
449        next: WorkflowBudgetAuditCursor,
450    ) -> WorkflowStoreFuture<'_, Result<bool, WorkflowStoreError>> {
451        self.advance_tenant_budget_audit_projection_impl(tenant_id, projection_id, expected, next)
452    }
453
454    fn claim_tenant_budget_audit_projection(
455        &self,
456        tenant_id: WorkflowTenantId,
457        projection_id: WorkflowBudgetAuditProjectionId,
458        owner: WorkerId,
459        lease: LeaseDuration,
460    ) -> WorkflowStoreFuture<
461        '_,
462        Result<Option<WorkflowBudgetAuditProjectionLease>, WorkflowStoreError>,
463    > {
464        self.claim_tenant_budget_audit_projection_impl(tenant_id, projection_id, owner, lease)
465    }
466
467    fn heartbeat_tenant_budget_audit_projection(
468        &self,
469        lease: WorkflowBudgetAuditProjectionLease,
470        extension: LeaseDuration,
471    ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditProjectionLease, WorkflowStoreError>>
472    {
473        self.heartbeat_tenant_budget_audit_projection_impl(lease, extension)
474    }
475
476    fn advance_tenant_budget_audit_projection_lease(
477        &self,
478        lease: WorkflowBudgetAuditProjectionLease,
479        next: WorkflowBudgetAuditCursor,
480    ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditProjectionLease, WorkflowStoreError>>
481    {
482        self.advance_tenant_budget_audit_projection_lease_impl(lease, next)
483    }
484
485    fn release_tenant_budget_audit_projection(
486        &self,
487        lease: WorkflowBudgetAuditProjectionLease,
488    ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
489        self.release_tenant_budget_audit_projection_impl(lease)
490    }
491
492    fn reserve_budget(
493        &self,
494        lease: WorkflowLease,
495        workflow_limit: Budget,
496        baseline: Usage,
497    ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetReservationOutcome, WorkflowStoreError>> {
498        self.reserve_budget_impl(lease, workflow_limit, baseline)
499    }
500
501    fn settle_budget(
502        &self,
503        lease: WorkflowLease,
504        cumulative: Usage,
505    ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
506        self.settle_budget_impl(lease, cumulative)
507    }
508
509    fn enqueue(
510        &self,
511        task: WorkflowTask,
512    ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
513        self.enqueue_impl(task)
514    }
515
516    fn claim(
517        &self,
518        worker: WorkerId,
519        lease: LeaseDuration,
520    ) -> WorkflowStoreFuture<'_, Result<Option<ClaimedWorkflow>, WorkflowStoreError>> {
521        self.claim_impl(worker, lease)
522    }
523
524    fn heartbeat(
525        &self,
526        lease: WorkflowLease,
527        extension: LeaseDuration,
528    ) -> WorkflowStoreFuture<'_, Result<WorkflowLease, WorkflowStoreError>> {
529        self.heartbeat_impl(lease, extension)
530    }
531
532    fn finish(
533        &self,
534        lease: WorkflowLease,
535        disposition: WorkflowDisposition,
536    ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
537        self.finish_impl(lease, disposition)
538    }
539
540    fn publish_signal(
541        &self,
542        tenant_id: WorkflowTenantId,
543        signal: WorkflowSignal,
544    ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalOutcome, WorkflowStoreError>> {
545        self.publish_signal_impl(tenant_id, signal, false)
546    }
547
548    fn publish_control_signal(
549        &self,
550        tenant_id: WorkflowTenantId,
551        signal: WorkflowSignal,
552    ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalOutcome, WorkflowStoreError>> {
553        self.publish_signal_impl(tenant_id, signal, true)
554    }
555
556    fn cancel(
557        &self,
558        tenant_id: WorkflowTenantId,
559        checkpoint_id: CheckpointId,
560    ) -> WorkflowStoreFuture<'_, Result<WorkflowCancelOutcome, WorkflowStoreError>> {
561        self.cancel_impl(tenant_id, checkpoint_id)
562    }
563
564    fn inspect_signal(
565        &self,
566        tenant_id: WorkflowTenantId,
567        signal_id: WorkflowSignalId,
568    ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalSnapshot, WorkflowStoreError>> {
569        self.inspect_signal_impl(tenant_id, signal_id)
570    }
571
572    fn load_signal_payload(
573        &self,
574        tenant_id: WorkflowTenantId,
575        signal_id: WorkflowSignalId,
576    ) -> WorkflowStoreFuture<'_, Result<Value, WorkflowStoreError>> {
577        Box::pin(async move {
578            let signals = self
579                .signals
580                .lock()
581                .unwrap_or_else(std::sync::PoisonError::into_inner);
582            let stored = signals.get(&signal_id).ok_or_else(|| {
583                WorkflowStoreError::new(
584                    WorkflowStoreErrorKind::NotFound,
585                    "workflow signal does not exist",
586                )
587            })?;
588            if stored.tenant_id != tenant_id {
589                return Err(WorkflowStoreError::new(
590                    WorkflowStoreErrorKind::TenantMismatch,
591                    "workflow tenant does not own signal",
592                ));
593            }
594            Ok(stored.signal.payload.clone())
595        })
596    }
597
598    fn compact_signals(
599        &self,
600        tenant_id: WorkflowTenantId,
601        retention: WorkflowSignalRetention,
602    ) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
603        self.compact_signals_impl(tenant_id, retention)
604    }
605
606    fn inspect(
607        &self,
608        tenant_id: WorkflowTenantId,
609        checkpoint_id: CheckpointId,
610    ) -> WorkflowStoreFuture<'_, Result<WorkflowTaskSnapshot, WorkflowStoreError>> {
611        self.inspect_impl(tenant_id, checkpoint_id)
612    }
613
614    fn load_task_input(
615        &self,
616        tenant_id: WorkflowTenantId,
617        checkpoint_id: CheckpointId,
618    ) -> WorkflowStoreFuture<'_, Result<Value, WorkflowStoreError>> {
619        Box::pin(async move {
620            let tasks = self.tasks();
621            let stored = tasks.get(&checkpoint_id).ok_or_else(|| {
622                WorkflowStoreError::new(WorkflowStoreErrorKind::NotFound, "workflow not found")
623            })?;
624            if stored.task.tenant_id != tenant_id {
625                return Err(WorkflowStoreError::new(
626                    WorkflowStoreErrorKind::TenantMismatch,
627                    "workflow tenant does not own task",
628                ));
629            }
630            Ok(stored.task.input.clone())
631        })
632    }
633
634    fn list_checkpoint_history(
635        &self,
636        tenant_id: WorkflowTenantId,
637        checkpoint_id: CheckpointId,
638        after_revision: Option<u64>,
639        limit: WorkflowCheckpointHistoryLimit,
640    ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowCheckpointRevision>, WorkflowStoreError>> {
641        self.list_checkpoint_history_impl(tenant_id, checkpoint_id, after_revision, limit)
642    }
643
644    fn load_checkpoint_revision(
645        &self,
646        tenant_id: WorkflowTenantId,
647        checkpoint_id: CheckpointId,
648        revision: u64,
649    ) -> WorkflowStoreFuture<'_, Result<WorkflowCheckpointRevision, WorkflowStoreError>> {
650        self.load_checkpoint_revision_impl(tenant_id, checkpoint_id, revision)
651    }
652
653    fn fork_workflow(
654        &self,
655        tenant_id: WorkflowTenantId,
656        command: WorkflowForkCommand,
657    ) -> WorkflowStoreFuture<'_, Result<WorkflowForkOutcome, WorkflowStoreError>> {
658        self.fork_workflow_impl(tenant_id, command)
659    }
660
661    fn load_checkpoint(
662        &self,
663        lease: WorkflowLease,
664    ) -> WorkflowStoreFuture<'_, Result<Checkpoint, CheckpointError>> {
665        self.load_checkpoint_impl(lease)
666    }
667
668    fn compare_and_swap_checkpoint(
669        &self,
670        lease: WorkflowLease,
671        checkpoint: Checkpoint,
672        expected_revision: Option<u64>,
673    ) -> WorkflowStoreFuture<'_, Result<(), CheckpointError>> {
674        self.compare_and_swap_checkpoint_impl(lease, checkpoint, expected_revision)
675    }
676}