peace 0.0.15

zero stress automation
Documentation
# Cmd Execution

When we run a `CmdExecution`, possible outcomes are:

1. In a `CmdBlock`, failure within one or more items.
2. In a `CmdBlock`, failure within block code.
3. Interruption / partial execution.
4. Successful execution of all blocks.

Each `CmdBlock` has its own block outcome type, and its own .


## Framework

### Use Cases: Framework Implementor

#### `*Cmd` Impls

* It would be nice to not have to write interruption handling code in every `CmdBlock`.
* Also, having `CmdBlock`s abstracted allows timings to be collected per block in a common place.

#### What does a `CmdExecution` implementor want to do?

* `DiffCmd`:
    - `StateDiscoveryBlock`: Return item failures to caller.
    - `StateDiscoveryBlock`: Return block failure / interruption to caller.
    - `DiffBlock`: Return item failures to caller.
    - `DiffBlock`: Return block failure / interruption to caller.

* `EnsureCmd`:
    - `StateDiscoveryBlock`: Return item failures to caller.
    - `StateDiscoveryBlock`: Return block failure / interruption to caller.
    - `DiffBlock`: Return item failures to caller.
    - `DiffBlock`: Return block failure / interruption to caller.
    - `EnsureBlock`: Serialize `States` on item failure, return `States` and item failures to caller.
    - `EnsureBlock`: Return block failure / interruption to caller.
    - `CleanBlock`: Serialize `States` on item failure, return `States` and item failures to caller.
    - `CleanBlock`: Return block failure / interruption to caller.
    - `EnsureBlock`: ditto.
    - `CleanBlock`: ditto.

    The desired return type would be:

    - `StatesEnsured` on success.

* How should we reply to the caller?
    - `CmdBlock` block level error.
    - `IndexMap<ItemId, E>` item level error.

    Error handlers per `CmdBlock` will do any serialization work if desired.

### Use Cases: Automation Developer / End User

* Store, retrieve, and display execution report.


## How do we do this in code?

What it could look like conceptually:

### `DiffCmd`

```rust
let cmd_execution = CmdExecution::builder()
    .with_block(StatesDiscoverCmdBlock::<CurrentAndGoal>::new())
    .with_block(DiffCmdBlock::new())
    .build()
    .await;
```

### `EnsureCmd` -- "simple" version

```rust
let cmd_execution = CmdExecution::builder()
    .with_block(StatesCurrentReadCmdBlock::new())
    .with_block(StatesDiscoverCmdBlock::new(Discover::Current))
    // Compares current with stored, and fails if
    // current stored doesn't match current discovered.
    .with_block(ApplyGuardCmdBlock::new())  // `Outcome` and `OutcomeAcc` are `()`
    .with_block(ApplyCmdBlock::new())       // state_goal, diff and apply.
    .build()
    .await;
```


### `EnsureCmd` -- "complex" version

```rust
let cmd_execution = CmdExecution::builder()
    .with_block(StatesCurrentReadCmdBlock::new())
    .with_block(StatesDiscoverCmdBlock::new(Discover::Current))
    .with_block(ApplyGuardCmdBlock::new())
    .with_block(EnsureCmdBlock::new(item_ids_no_blockage))  // state_goal, diff and apply.
    .with_block(CleanCmdBlock::new(item_ids_blocking))    // state_clean, diff and apply.
    .with_block(EnsureCmdBlock::new(item_ids_unblocked))  // state_goal, diff and apply.
    .with_block(CleanCmdBlock::new(item_ids_obsolete))    // state_clean, diff and apply.
    .build()
    .await;

// `item_ids_unblocked` is a superset of the `item_ids_blocking`.
//
// ⚠️ We could have a more granular design version that starts ensuring unblocked items
// while `cleaning` is happening.
```


### Error Handling and Interruptions

When a `CmdBlock` fails, we need to consider what to do with:

* `CmdBlock::OutcomeAcc`.
* item errors.
* `CmdBlock` error.
* interruptions.

Originally an additional closure argument next to each `CmdBlock` argument was considered:

```rust ,ignore
|_input, _outcome_acc, cmd_block_error| async move { cmd_block_error }
```

For example:

```rust ,ignore
|(_states_current, _states_goal), states_ensured_mut, apply_error| async move {
    let serialize_result = StatesSerializer::serialize(states_ensured_mut).await;

    apply_error.or(serialize_result)
},
```

However, most usages would just pass through the error.

Before this `CmdExecution` design, `ApplyCmd` would:

1. In the producer, send the intermediate result to the outcome collator and stop execution ([apply_cmd.rs#L699-L710]https://github.com/azriel91/peace/blob/0.0.11/crate/rt/src/cmds/sub/apply_cmd.rs#L699-L710).
2. In the collator:

    - Intermediate `CmdBlock` errors are returned as is. ([apply_cmd.rs#L521-L525]https://github.com/azriel91/peace/blob/0.0.11/crate/rt/src/cmds/sub/apply_cmd.rs#L521-L525).
    - Intermediate discovery errors are collated into the `CmdExecution` outcome type ([apply_cmd.rs#L526-L558]https://github.com/azriel91/peace/blob/0.0.11/crate/rt/src/cmds/sub/apply_cmd.rs#L526-L558).


From the use cases, what we care about are:

* Returning `CmdBlock` errors to the caller.
* Returning item errors to the caller.
* Collating intermediate `CmdBlock` item values into the `CmdExecution` outcome type if possible.
* Serializing `States` to storage: `EnsureCmdBlock` and `CleanCmdBlock` should still serialize each item's current state, on failure / interruption / success.
* Serializing the `CmdBlock::Outcome` in the execution outcome report.

Deferred:

* Serializing the `CmdBlock::OutcomeAcc` in the execution outcome report.
* Deserializing the `CmdBlock::OutcomeAcc` to be displayed.


## Interruptions

1. Interrupt before execution.
2. Interrupt within a block.
3. Interrupt between blocks.
4. Interrupt after last block.