ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
# Policy

Determines whether a mode transition is allowed.

Policies let you enforce transition rules externally. The runtime checks the
policy before applying a [`ModeChange`](mode_change.md) from a
[`Decision`](decision.md). When a transition is denied, the current mode is
unchanged and commands are dropped.

## The Trait

```rust
use ready_active_safe::Policy;

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

struct ForwardOnly;

impl Policy<Mode> for ForwardOnly {
    fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
        matches!(
            (from, to),
            (Mode::Ready, Mode::Active) | (Mode::Active, Mode::Safe)
        )
    }
}

let policy = ForwardOnly;
assert!(policy.is_allowed(&Mode::Ready, &Mode::Active));
assert!(!policy.is_allowed(&Mode::Active, &Mode::Ready));
```

## `check` — returns a `Result`

The default `check` implementation calls `is_allowed` and returns a reason
string on denial. Override it for custom error messages.

```rust
use ready_active_safe::Policy;

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

struct ForwardOnly;

impl Policy<Mode> for ForwardOnly {
    fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
        matches!(
            (from, to),
            (Mode::Ready, Mode::Active) | (Mode::Active, Mode::Safe)
        )
    }
}

let policy = ForwardOnly;
assert!(policy.check(&Mode::Ready, &Mode::Active).is_ok());

let err = policy.check(&Mode::Active, &Mode::Ready).unwrap_err();
assert_eq!(err, "transition denied by policy");
```

## `AllowAll`

Permits every transition. Use in tests and prototypes.

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

let policy = AllowAll;
assert!(policy.is_allowed(&"ready", &"active"));
assert!(policy.is_allowed(&"active", &"ready"));
```

## `DenyAll`

Denies every transition. Use to test policy enforcement paths.

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

let policy = DenyAll;
assert!(!policy.is_allowed(&"ready", &"active"));

let err = policy.check(&"ready", &"active").unwrap_err();
assert_eq!(err, "transition denied by policy");
```

## Edge Cases

Policies are only checked for transitions, not stays:

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

let d: Decision<&str, ()> = stay();
// apply_checked succeeds even with DenyAll because there is no transition
let (mode, _) = d.apply_checked("ready", &DenyAll)?;
assert_eq!(mode, "ready");
# Ok::<(), LifecycleError<&str>>(())
```

## See Also

- [`Decision::apply_checked`]decision.md — applies a decision with a policy guard
- [`Machine::step_checked`]machine.md — convenience for decide + apply_checked
- [`LifecycleError`]lifecycle_error.md — the error returned on denial