ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
# Guardrails (DOs and DON'Ts)

This document is the “identity firewall” for `ready-active-safe`.
It exists to prevent roadmap drift into “generic state machine framework”.

For the competitive evidence behind these guardrails, see `docs/AUDIT.md`.

---

## DO

- **Keep the purity boundary.** `Machine::on_event` stays deterministic and Sans-I/O.
- **Keep decisions as data.** `Decision` describes intent; runtimes execute effects.
- **Keep runtimes external.** Provide a boring reference loop, but do not dictate sync/async, threads, or I/O.
- **Keep policy small and auditable.** Policies stay simple and composable at the runtime boundary.
- **Keep feature flags honest.** If a feature ships, it must do real work (no stub modules in defaults).
- **Keep the core dependency-free.** Optional integrations may be feature-gated, but the kernel stays lean.
- **Prefer documentation over magic.** Plain Rust beats DSLs for correctness, debugging, and tooling.

---

## DON'T

- **Don’t add hierarchical / nested states.** That’s statecharts territory, not lifecycle modeling.
- **Don’t add a callback system.** If hooks ever exist, cap it at two (`before_transition`, `after_transition`).
- **Don’t add an event queue or scheduler.** Event delivery is a runtime concern; lifecycles are externally driven.
- **Don’t ship an async runtime in this crate.** Async is an adapter concern; keep the machine synchronous.
- **Don’t require proc macros for the happy path.** Macros are optional at most, never the primary interface.
- **Don’t ship silent failures.** If something is invalid, return a clear error with context.
- **Don’t break core APIs post-1.0.** Get the small vocabulary right pre-1.0, then freeze it.
- **Don’t bake persistence into the core journal.** Persistence is an adapter; the core stays in-memory and inspectable.

---

## Rejected Ideas (and Where They Belong Instead)

| Idea | Why Rejected | Where Instead |
|------|--------------|---------------|
| Hierarchical states / statecharts | Overkill for lifecycle phases; increases surface area and cognitive load | Use `statig` or statecharts crates (e.g., `statechart`) |
| Callback matrix (N lifecycle hooks) | Ordering becomes the API; hard to test and reason about | Model it as `Command`s; keep hooks minimal |
| Built-in event queue / scheduler | Turns into a workflow engine; coupling and hidden behavior | Your runtime (channels, queues, supervisors) |
| Built-in async runtime | Forces runtime choice; increases dependencies | Example(s) that wrap a synchronous `Runner` in tokio |
| Persistent journal (DB/file) | Storage decisions don’t belong in the kernel | Adapter crate(s) or user code that persists `TransitionRecord`s |
| Proc-macro DSL as the default | Tooling and errors are worse; code becomes harder to debug | Optional helper crate (future), not core |
| Typestate-only APIs | Cannot handle dynamic external events without an enum wrapper | Use typestate crates when transitions are compile-time fixed |

---

## How We Reconsider a “No”

We only revisit a rejected idea when:

1. It solves a real, repeated problem in **at least three** production integrations, and
2. It can be added without breaking the purity boundary or turning the crate into a framework.