pacta-driver 0.3.1

Mechanical runtime loop for Pacta execution.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
//! Mechanical runtime loop for Pacta execution.

#![forbid(unsafe_code)]
#![warn(missing_docs)]

use std::time::{SystemTime, UNIX_EPOCH};

use pacta_contract::kernel::{Directive, Kernel, Notice, StepResult};
use pacta_contract::{Outcome, Registry, Timestamp};
use pacta_executor::{Execution, Executor};

/// Read the current wall-clock time as a [`Timestamp`] to inject into
/// time-dependent registry operations. Reading the clock is a runtime concern, so
/// it lives here and never in the core contract.
fn current_time() -> Timestamp {
    let millis = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|elapsed| u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX))
        .unwrap_or(0);
    Timestamp::from_millis(millis)
}

/// One mechanical driver step result.
///
/// `#[non_exhaustive]`: a runtime-loop status may gain states (for example a future
/// heartbeat or lapse step), so a downstream match must carry a wildcard arm.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[must_use]
pub enum Step {
    /// No pact was available from the configured dockets.
    Idle,
    /// A claimed pact was fulfilled.
    Fulfilled,
    /// A claimed pact was breached.
    Breached,
}

/// Error returned by a driver step.
///
/// `#[non_exhaustive]`: an error enumeration grows as new failure modes appear, so a
/// downstream match must carry a wildcard arm.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
#[must_use]
pub enum DriverError<RegistryError, ExecutorError> {
    /// Registry operation failed.
    Registry(RegistryError),
    /// Executor infrastructure failed; the claim was left unsettled to lapse and be
    /// reclaimed (no settlement was recorded).
    Executor(ExecutorError),
}

impl<RegistryError, ExecutorError> std::fmt::Display for DriverError<RegistryError, ExecutorError>
where
    RegistryError: std::error::Error,
    ExecutorError: std::error::Error,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Registry(error) => write!(f, "registry operation failed: {error}"),
            Self::Executor(error) => write!(f, "executor infrastructure failed: {error}"),
        }
    }
}

impl<RegistryError, ExecutorError> std::error::Error for DriverError<RegistryError, ExecutorError>
where
    RegistryError: std::error::Error + 'static,
    ExecutorError: std::error::Error + 'static,
{
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Registry(error) => Some(error),
            Self::Executor(error) => Some(error),
        }
    }
}

/// Mechanical loop that performs the directives the sans-I/O kernel issues.
///
/// This is a **reference** runtime skeleton. It drives one step synchronously —
/// claim, execute, settle — and never heartbeats or reclaims within a step: it does
/// not extend a lease while its executor runs (so a long-running pact's lease can
/// *expire* mid-step), and it settles by matching the retainer rather than
/// re-claiming. It is therefore safe for pacts shorter than the lease (the lease
/// never expires mid-step)
/// and for single-worker use (no concurrent claimer can *reclaim* an expired lease
/// mid-step). A workload that is both long-running *and* multi-worker should compose
/// its own loop over the [`Registry`] contract (which includes `heartbeat`); the
/// lifecycle kernel deliberately models no in-flight heartbeat.
pub struct Driver<R, E> {
    registry: R,
    executor: E,
    dockets: Vec<String>,
}

impl<R, E> Driver<R, E> {
    /// Build a driver from a registry, an executor, and docket names.
    pub fn new(registry: R, executor: E, dockets: impl IntoIterator<Item = String>) -> Self {
        Self {
            registry,
            executor,
            dockets: dockets.into_iter().collect(),
        }
    }

    /// Borrow the registry used by this driver.
    #[must_use]
    pub fn registry(&self) -> &R {
        &self.registry
    }

    /// Borrow the executor used by this driver.
    #[must_use]
    pub fn executor(&self) -> &E {
        &self.executor
    }
}

impl<R, E> Driver<R, E>
where
    R: Registry,
    E: Executor,
{
    /// Perform one claim, execute, and settle step by driving the kernel: the
    /// kernel decides each directive; the driver performs it and feeds a notice
    /// back, deciding no lifecycle outcome itself.
    pub fn step(&mut self) -> Result<Step, DriverError<R::Error, E::Error>> {
        let dockets: Vec<&str> = self.dockets.iter().map(String::as_str).collect();
        let now = current_time();
        let mut kernel = Kernel::new();
        let mut pending_executor_error: Option<E::Error> = None;

        loop {
            if let Some(result) = kernel.result() {
                return match result {
                    StepResult::Idle => Ok(Step::Idle),
                    StepResult::Settled(outcome) => Ok(match outcome {
                        Outcome::Fulfilled => Step::Fulfilled,
                        Outcome::Breached => Step::Breached,
                    }),
                    // An unsettled step means execution failed: settle nothing and
                    // surface the executor error. The claim lapses and is reclaimed.
                    StepResult::Unsettled => Err(DriverError::Executor(
                        pending_executor_error
                            .expect("an unsettled step implies a pending executor error"),
                    )),
                    _ => unreachable!("driver handles every current kernel step result"),
                };
            }

            match kernel.poll() {
                Directive::Claim => {
                    let claim = self
                        .registry
                        .claim(&dockets, now)
                        .map_err(DriverError::Registry)?;
                    kernel.on_event(Notice::Claimed(claim));
                }
                Directive::Execute(pact) => match self.executor.execute(Execution::new(pact)) {
                    Ok(outcome) => kernel.on_event(Notice::Executed(outcome)),
                    Err(error) => {
                        pending_executor_error = Some(error);
                        kernel.on_event(Notice::ExecutionFailed);
                    }
                },
                Directive::Settle(retainer, outcome) => {
                    match outcome {
                        Outcome::Fulfilled => self
                            .registry
                            .fulfill(&retainer)
                            .map_err(DriverError::Registry)?,
                        Outcome::Breached => self
                            .registry
                            .breach(&retainer)
                            .map_err(DriverError::Registry)?,
                    }
                    kernel.on_event(Notice::Settled);
                }
                Directive::Idle => return Ok(Step::Idle),
                _ => unreachable!("driver handles every current kernel directive"),
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Mutex;

    use pacta_contract::{Claim, Pact, Retainer, Timestamp, Transition};
    use uuid::Uuid;

    use super::*;

    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct TestError;

    impl std::fmt::Display for TestError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "test error")
        }
    }

    impl std::error::Error for TestError {}

    #[derive(Default)]
    struct RegistryState {
        claim: Option<Claim>,
        fulfilled: usize,
        breached: usize,
    }

    #[derive(Default)]
    struct TestRegistry {
        state: Mutex<RegistryState>,
    }

    impl TestRegistry {
        fn with_claim(claim: Claim) -> Self {
            Self {
                state: Mutex::new(RegistryState {
                    claim: Some(claim),
                    ..RegistryState::default()
                }),
            }
        }
    }

    impl Registry for TestRegistry {
        type Error = TestError;

        fn claim(&self, _dockets: &[&str], _now: Timestamp) -> Result<Option<Claim>, Self::Error> {
            Ok(self
                .state
                .lock()
                .expect("registry state should not be poisoned")
                .claim
                .take())
        }

        fn lease_millis(&self) -> u64 {
            0
        }

        // Required port; unused by these tests (the driver settles via fulfill/breach, which this
        // registry overrides below to observe the driver's routing).
        fn apply(
            &self,
            _retainer: &Retainer,
            _transition: &Transition<'_>,
        ) -> Result<(), Self::Error> {
            Ok(())
        }

        // fulfill and breach are overridden (not to store state, but to count which the driver
        // called), so the tests can assert the driver routes each outcome to the right op.
        fn fulfill(&self, _retainer: &Retainer) -> Result<(), Self::Error> {
            self.state
                .lock()
                .expect("registry state should not be poisoned")
                .fulfilled += 1;
            Ok(())
        }

        fn breach(&self, _retainer: &Retainer) -> Result<(), Self::Error> {
            self.state
                .lock()
                .expect("registry state should not be poisoned")
                .breached += 1;
            Ok(())
        }
    }

    struct TestExecutor {
        outcome: Result<Outcome, TestError>,
        executions: usize,
    }

    impl Executor for TestExecutor {
        type Error = TestError;

        fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
            self.executions += 1;
            self.outcome
        }
    }

    fn claim() -> Claim {
        Claim::new(
            Pact::new(
                Uuid::new_v4(),
                "default".to_string(),
                "example".to_string(),
                Vec::new(),
            ),
            Retainer::new(Uuid::new_v4()),
            Timestamp::from_millis(0),
        )
    }

    #[test]
    fn successful_execution_fulfills_claim() {
        let registry = TestRegistry::with_claim(claim());
        let executor = TestExecutor {
            outcome: Ok(Outcome::Fulfilled),
            executions: 0,
        };
        let mut driver = Driver::new(registry, executor, ["default".to_string()]);

        assert_eq!(driver.step(), Ok(Step::Fulfilled));
        let state = driver
            .registry()
            .state
            .lock()
            .expect("registry state should not be poisoned");
        assert_eq!(state.fulfilled, 1);
        assert_eq!(state.breached, 0);
        drop(state);
        assert_eq!(driver.executor().executions, 1);
    }

    #[test]
    fn breached_execution_breaches_claim() {
        let registry = TestRegistry::with_claim(claim());
        let executor = TestExecutor {
            outcome: Ok(Outcome::Breached),
            executions: 0,
        };
        let mut driver = Driver::new(registry, executor, ["default".to_string()]);

        assert_eq!(driver.step(), Ok(Step::Breached));
        let state = driver
            .registry()
            .state
            .lock()
            .expect("registry state should not be poisoned");
        assert_eq!(state.fulfilled, 0);
        assert_eq!(state.breached, 1);
        drop(state);
        assert_eq!(driver.executor().executions, 1);
    }

    #[test]
    fn executor_error_leaves_claim_unsettled() {
        let registry = TestRegistry::with_claim(claim());
        let executor = TestExecutor {
            outcome: Err(TestError),
            executions: 0,
        };
        let mut driver = Driver::new(registry, executor, ["default".to_string()]);

        // An infrastructure failure surfaces the executor error and settles nothing:
        // neither fulfilled nor breached. The claim is left unsettled to lapse and be
        // reclaimed — that lapse-reclaim is proven at the registry level by
        // `pacta-conformance` (`expired_lease_lapses_and_reclaims_...`).
        assert_eq!(driver.step(), Err(DriverError::Executor(TestError)));
        let state = driver
            .registry()
            .state
            .lock()
            .expect("registry state should not be poisoned");
        assert_eq!(state.fulfilled, 0);
        assert_eq!(state.breached, 0);
        drop(state);
        assert_eq!(driver.executor().executions, 1);
    }

    #[test]
    fn empty_docket_is_idle() {
        let registry = TestRegistry::default();
        let executor = TestExecutor {
            outcome: Ok(Outcome::Fulfilled),
            executions: 0,
        };
        let mut driver = Driver::new(registry, executor, ["default".to_string()]);

        assert_eq!(driver.step(), Ok(Step::Idle));
        let state = driver
            .registry()
            .state
            .lock()
            .expect("registry state should not be poisoned");
        assert_eq!(state.fulfilled, 0);
        assert_eq!(state.breached, 0);
        drop(state);
        assert_eq!(driver.executor().executions, 0);
    }

    #[test]
    fn driver_error_displays_and_exposes_source() {
        use std::error::Error;

        let executor_error: DriverError<TestError, TestError> = DriverError::Executor(TestError);
        assert_eq!(
            executor_error.to_string(),
            "executor infrastructure failed: test error"
        );
        assert_eq!(
            executor_error
                .source()
                .expect("driver error should expose its source")
                .to_string(),
            "test error"
        );

        let registry_error: DriverError<TestError, TestError> = DriverError::Registry(TestError);
        assert_eq!(
            registry_error.to_string(),
            "registry operation failed: test error"
        );
        assert_eq!(
            registry_error
                .source()
                .expect("driver error should expose its source")
                .to_string(),
            "test error"
        );
    }
}