ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
# API Ergonomics

This document captures what we want the `ready-active-safe` API to feel like.
The goal is Rails level readability in Rust without drifting into a framework.

This is not a contract document.
For the authoritative API contract, see `docs/DESIGN.md`.
For the product identity, see `docs/VISION.md`.

## North Star

Within 15 minutes, a new user should be able to:

1. add the dependency
2. define `Mode`, `Event`, and `Command`
3. write one readable `match (mode, event)`
4. assert on the returned `Decision`

If that takes longer, we fix docs or we fix the API.

## Constraints We Do Not Break

We want great DX, but we keep the core boring and explicit:

- `Machine::on_event` stays pure
- decisions stay data
- effects stay in the runtime
- no proc macro DSL as the primary interface
- time stays out of the base trait

If an idea makes the crate feel like a generic FSM framework, it is probably out of scope.

## What Great Readability Looks Like

Great user experience is not about being clever.
It is about making the happy path obvious and keeping the mental model small.

In practice, that means:

- one import path for common usage
- names that match what people already call things
- a transition definition that you can scan in a few seconds
- a testing story where you assert on outcomes as data

## Ten Reference Projects With Great DX

These are not all direct competitors.
They are reference points for readability, documentation quality, and user flow.

### behave (Rust, testing DSL)

Repo: https://github.com/stateruntime/behave

behave is a good reminder that tests are part of DX.
The best interfaces make the readable thing the easy thing.

What we copy:
keep our own tests short and scenario shaped, and keep failure messages clear.

### statig (Rust, state machines)

Repo: https://github.com/mdeloof/statig

statig reads well because the happy path is simple and close to plain Rust.
Even when it uses macros, the concepts are stable and familiar.

What we copy:
keep state handlers readable and keep transitions explicit.

What we skip:
proc macros as the default in the core crate.

### smlang (Rust, transition tables)

Repo: https://github.com/korken89/smlang

smlang is scannable because the definition is a table, not a pile of control flow.
Guards and actions sit next to the transition they belong to.

What we copy:
make the important transitions easy to scan in one place.

What we skip:
a macro DSL as the primary interface.

### AASM (Ruby, state machines)

Repo: https://github.com/aasm/aasm

Ruby state machine libraries often win on language.
Method names read like English, and docs focus on the happy path first.

What we copy:
start docs with a small working example, then deepen.

### Statesman (Ruby, workflows)

Repo: https://github.com/gocardless/statesman

Statesman is opinionated about audit and history.
That focus makes the library feel like it was built for production, not toy examples.

What we copy:
make audit and replay a first class story, even if implementation ships later.

### Stateless (C#, state machines)

Repo: https://github.com/dotnet-state-machine/stateless

Stateless is widely liked because it reads as configuration.
Transitions are easy to inspect, and it is easy to test.

What we copy:
keep transition intent explicit, and keep the public surface small.

### transitions (Python, state machines)

Repo: https://github.com/pytransitions/transitions

transitions is popular because it is easy to adopt.
It has a short path from install to a working machine.

What we copy:
make the first success fast, and keep the docs copy and paste friendly.

### XState (JS and TS, statecharts)

Docs: https://stately.ai/docs/machines

XState nails inspectability.
Machines are data, so it is easy to log, visualize, and debug.

What we copy:
lean into decisions as inspectable data.

What we skip:
hierarchy and concurrency in the core kernel.

### looplab fsm (Go, state machines)

Repo: https://github.com/looplab/fsm

Go libraries often feel readable because they do not fight the language.
They keep the vocabulary small and keep configuration obvious.

What we copy:
keep our own vocabulary small and consistent across docs and code.

### Tinder StateMachine (Kotlin, state machines)

Repo: https://github.com/Tinder/StateMachine

This library is loved because its definitions read like a simple story.
It also makes side effects explicit via separate actions.

What we copy:
keep the decision boundary clean, and keep actions separate from transitions.

## What This Means For ready-active-safe

These are concrete DX rules that keep us aligned with the vision:

- keep `ready_active_safe::prelude::*` as the one import for typical usage
- keep `Machine::on_event` readable without helper macros
- keep the most common runtime checks easy to write, like `Decision::target_mode()`
- keep match arms sentence shaped with small helpers
- keep runtimes simple with move based helpers instead of clones

Concrete helpers we provide:

- `stay()` and `transition(...)` as wrappers around `Decision::stay()` and `Decision::transition(...)`
- `Decision::emit(...)` and `Decision::emit_all(...)` as readable command helpers
- `apply_decision(...)` to apply a decision without cloning
- `Policy::check(...)` to return a reason string for denied transitions
- `apply_decision_checked(...)` to combine policy checks and application

## What We Avoid

These patterns tend to make Rust code harder to read:

- proc macro DSLs as the default user path
- APIs that hide control flow
- runtimes that dictate sync vs async choices
- deep type level tricks that make error messages worse

## Backlog Ideas That Improve Readability

This list is intentionally practical and small.
Each item should make real code easier to read, not just shorter.

- Add more golden path examples that feel like production code
- Use `runtime::Runner` in docs/examples to show the reference loop (mode storage, policy checks, dispatch)
- Keep docs and examples free of hidden doctest lines