ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
# ready-active-safe

[![Crates.io](https://img.shields.io/crates/v/ready-active-safe.svg)](https://crates.io/crates/ready-active-safe)
[![Documentation](https://docs.rs/ready-active-safe/badge.svg)](https://docs.rs/ready-active-safe)
[![CI](https://github.com/stateruntime/ready-active-safe/actions/workflows/ci.yml/badge.svg)](https://github.com/stateruntime/ready-active-safe/actions/workflows/ci.yml)
[![License](https://img.shields.io/crates/l/ready-active-safe.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/MSRV-1.75-blue.svg)](https://blog.rust-lang.org/)

Lifecycle engine for externally driven systems.

## The Idea

Most real systems do not have an "anything can go anywhere" state graph. They have a lifecycle.
They start up, run, and shut down. When things go wrong, they go safe and recover.

`ready-active-safe` gives you a small kernel for that shape of problem.

You write one pure function, `Machine::on_event`, and you get back a `Decision`.
The decision is plain data that says:

- move to a new mode, or stay
- emit zero or more commands for your runtime to execute

The machine never does I/O. Your runtime stays yours.

This crate is not a general purpose state machine framework. It is a lifecycle engine.

## How It Works

A `Machine` implementation is a pure function from `(mode, event)` to `Decision`.
The `Decision` contains an optional `ModeChange` (transition to a new mode) and a list of
`Command` values for the runtime to execute. The runtime owns the current mode, feeds events,
checks the `Policy`, and dispatches commands. The machine itself never performs side effects.

```text
Event --> Machine::on_event(mode, event) --> Decision
                                                |-- ModeChange (optional)
                                                +-- Commands (Vec<C>)
```

## Why This Crate

Lifecycle logic is often tangled with I/O, threading, and error handling,
making it hard to test and reason about. This crate separates the two concerns:

- **Pure logic boundary:** `Machine::on_event` never performs I/O or side effects. It returns data. You can test every transition with a simple assertion.
- **Decisions as data:** mode changes and commands are plain values your runtime executes — not callbacks.
- **Batteries are optional:** `runtime::Runner`, `journal`, and `time` are feature-gated helpers, not a framework.
- **no_std core:** core types work in `no_std` with `alloc`.
- **Safety posture:** no `unsafe`, no panic/unwrap/expect in library code, strict CI, runnable docs.

## Quick Start

Add this to your `Cargo.toml`:

```toml
[dependencies]
ready-active-safe = "0.1"
```

Then define modes, events, and commands:

```rust
use ready_active_safe::prelude::*;
use ready_active_safe::runtime::Runner;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode {
    Ready,
    Active,
    Safe,
}

#[derive(Debug)]
enum Event {
    Start,
    Stop,
    Fault,
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum Command {
    Initialize,
    BeginProcessing,
    Shutdown,
}

struct System;

impl Machine for System {
    type Mode = Mode;
    type Event = Event;
    type Command = Command;

    fn initial_mode(&self) -> Mode {
        Mode::Ready
    }

    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
        use Command::*;
        use Event::*;
        use Mode::*;

        match (mode, event) {
            (Ready, Start) => transition(Active)
                .emit_all([Initialize, BeginProcessing]),
            (Active, Stop | Fault) => transition(Safe)
                .emit(Shutdown),
            _ => stay(),
        }
    }
}

let system = System;

let decision = system.decide(&Mode::Ready, &Event::Start);
assert_eq!(decision.target_mode(), Some(&Mode::Active));
assert_eq!(decision.commands(), &[Command::Initialize, Command::BeginProcessing]);

let decision = system.decide(&Mode::Active, &Event::Fault);
assert_eq!(decision.target_mode(), Some(&Mode::Safe));

let mut runner = Runner::new(&system);
let commands = runner.feed(&Event::Start);
assert_eq!(runner.mode(), &Mode::Active);
assert_eq!(commands, vec![Command::Initialize, Command::BeginProcessing]);
```

## Feature Flags

| Feature   | Default | Requires | Description                          |
|-----------|---------|----------|--------------------------------------|
| `full`    | Yes     | none     | Enables all features below           |
| `std`     | No*     | none     | Standard library support             |
| `runtime` | No*     | `std`    | Event loop and command dispatch      |
| `time`    | No*     | none     | Clock, instant, and deadline types   |
| `journal` | No*     | `std`    | Transition recording and replay      |

*Enabled by default through the `full` feature.

To use in a `no_std` environment:

```toml
[dependencies]
ready-active-safe = { version = "0.1", default-features = false }
```

To select specific features:

```toml
[dependencies]
ready-active-safe = { version = "0.1", default-features = false, features = ["std", "time"] }
```

## API Reference

| Type | Module | Description |
|------|--------|-------------|
| [`Machine`]docs/api/machine.md | core | Pure function from (mode, event) to `Decision` |
| [`Decision`]docs/api/decision.md | core | Outcome of processing an event: mode change + commands |
| [`ModeChange`]docs/api/mode_change.md | core | A requested transition to a new mode |
| [`Policy`]docs/api/policy.md | core | Determines whether a transition is allowed |
| [`LifecycleError`]docs/api/lifecycle_error.md | core | Transition failure errors |
| [Free functions]docs/api/functions.md | core | `stay()`, `transition()`, `ignore()`, `apply_decision()` |
| [Macros]docs/api/macros.md | core | `assert_transitions_to!`, `assert_stays!`, `assert_emits!` |
| [`Clock`]docs/api/clock.md | `time` | Trait: a source of monotonic time |
| [`Instant`]docs/api/instant.md | `time` | Nanosecond-resolution monotonic instant |
| [`Deadline`]docs/api/deadline.md | `time` | An expiration point expressed as an `Instant` |
| [`ManualClock`]docs/api/manual_clock.md | `time` | Deterministic clock for tests and simulation |
| [`SystemClock`]docs/api/system_clock.md | `time` | Monotonic wall clock (requires `std`) |
| [`Runner`]docs/api/runner.md | `runtime` | Owns the current mode and feeds events into a `Machine` |
| [`TransitionRecord`]docs/api/transition_record.md | `journal` | A record of one processed event |
| [`InMemoryJournal`]docs/api/in_memory_journal.md | `journal` | In-memory journal with recording and replay |
| [`ReplayError`]docs/api/replay_error.md | `journal` | Errors from deterministic replay |

See the full [API reference index](docs/api/README.md) for categorized documentation.

## Use Cases

This pattern fits best when your system is driven by external events, and when recovery matters.
Common examples include:

- OpenXR session lifecycles
- embedded controllers and robotics nodes
- services that need explicit start and stop phases
- instruments that must fail safe and recover

## What It Does / Does Not Do

**Does:**

- Model system lifecycles as typed modes with pure transition logic
- Return decisions as data: commands, mode changes, or both
- Enforce transition policies externally via the `Policy` trait
- Support `no_std` environments (core types require only `alloc`)
- Enable deterministic replay through pure `Machine` implementations

**Does not:**

- Execute side effects. That is the runtime's job.
- Manage threads, async runtimes, or I/O
- Prescribe a fixed set of modes. You define your own.
- Require a specific serialization format or persistence layer

## Examples

Run any example with `cargo run --example <name>`. Suggested reading order:

1. **`basic`** — core API: `Machine`, `Decision`, `stay()`, `transition()`, `emit()`
2. **`openxr_session`** — real-world domain mapping (XR session lifecycle)
3. **`recovery`**`Policy` enforcement and `apply_checked`
4. **`runner`**`Runner` event loop with policy denial handling
5. **`recovery_cycle`** — repeated fault/recovery with external retry logic
6. **`channel_runtime`** — multi-threaded event feeding over channels
7. **`metrics`** — observability wrapper around `Runner`
8. **`replay`** — journal recording and deterministic replay

## Documentation

- [User guide]docs/USER_GUIDE.md — start here
- [API reference]docs/api/README.md — per-type documentation
- [Cookbook (recipes)]docs/COOKBOOK.md — copy-paste patterns
- [API reference on docs.rs]https://docs.rs/ready-active-safe
- [Design rationale]docs/DESIGN.md
- [Architecture overview]docs/ARCHITECTURE.md
- [Roadmap to v1.0]docs/ROADMAP.md

## Contributing

Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a pull request.

## Security

To report a security vulnerability, see [SECURITY.md](SECURITY.md).

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.