# 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