# Roadmap
Release plan from v0.1.0 to v1.0.
**Last updated:** 2026-03-14
This is intentionally “issue-ready”: most items are phrased so you can turn them into GitHub issues
with clear acceptance criteria.
For the full competitive/production audit behind these decisions, see `docs/AUDIT.md`.
For non-goals and rejected ideas, see `docs/GUARDRAILS.md`.
---
## Vision Check (Is This The Right Product?)
`ready-active-safe` is a **lifecycle engine** for **externally driven systems**.
This vision is **correct** when:
- your “states” are really *operational phases* (Ready/Active/Safe/Recovery)
- you want a **Sans-I/O** decision boundary and **decisions as data**
- you want an explicit **policy** boundary and a structured **journal** for replay/audit
- you want “just Rust” (match arms, no required DSL), with a tiny vocabulary you can teach
This vision is **wrong** when:
- you need statecharts / hierarchy / richer internal semantics
- you want an internal event queue, scheduler, actor runtime, or “run-to-completion” framework
- you need compile-time typestate enforcement as the primary interface
- you need a strictly no-heap story (no `alloc`); today the kernel requires `alloc`
**Roadmap implication:** we prioritize *interop + developer experience + allocation clarity* over
adding “richer” machine semantics.
---
## Guiding Principles (Always True)
These apply to every version:
1. **Progressive disclosure.** Simple case is simple. Complex case is possible.
Impossible case is impossible. (Apple WWDC22 principle.)
2. **Be Minitest, not RSpec.** Feel like "just Rust." No custom DSL required.
3. **Pit of success.** The default path produces correct code. Mistakes require deliberate effort.
4. **Convention over configuration.** Provide sensible defaults. Don’t force users to configure what can be defaulted.
5. **Rule of Three.** Don’t abstract until there are three concrete uses.
6. **Kernel stays lean.** Core stays dependency-free. Optional integrations are feature-gated deps.
7. **No empty promises.** Default features must not ship stubs. If a feature flag exists, it must do real work.
8. **Docs are executable.** README + key docs compile as doctests.
---
## Roadmap Conventions
- Features marked with `[MUST]` are required for the version.
- Features marked with `[SHOULD]` are strongly desired.
- Features marked with `[MAY]` are nice-to-have if time permits.
“Status” meanings:
- **DONE**: implemented and verified on `main`.
- **RELEASE PENDING**: implemented on `main`, but not yet published to crates.io.
- **PLANNED**: not implemented yet.
---
## Release Definition of Done (DoD)
Every release (even patches) must meet all of these:
### Verification (Local + CI)
- [ ] `cargo test --all-features`
- [ ] `cargo test --no-default-features`
- [ ] Individual feature combination tests pass (`--features runtime`, `--features time`, `--features journal`)
- [ ] `cargo clippy --all-features --all-targets -- -D warnings`
- [ ] `RUSTDOCFLAGS=”-D warnings” cargo doc --no-deps --all-features`
- [ ] `bash scripts/test-markdown-docs.sh`
- [ ] Examples compile (`cargo build --examples --all-features`)
- [ ] `cargo publish --dry-run` succeeds
- [ ] `cargo deny check` passes (when `cargo-deny` is available)
- [ ] CI pipeline passes (not just local commands)
### Documentation
- [ ] README matches actual feature flags and “what’s included by default”.
- [ ] Examples build (feature gating is correct).
- [ ] No stale “coming soon”, “stub”, or “TODO” claims in user-facing docs.
### Semantics (Must Be Explicit + Tested)
- [ ] Policy denial behavior is documented and tested (mode does not change; commands dropped).
- [ ] Command ordering is documented and tested (emission order preserved).
- [ ] Replay/journal semantics are documented and tested (divergence yields actionable error).
### Release Hygiene
- [ ] `VERSION` and `Cargo.toml` versions match.
- [ ] `CHANGELOG.md` has an entry for the release.
- [ ] `docs/RELEASE.md` steps still match reality.
- [ ] MSRV remains `1.75` or is intentionally bumped and documented.
---
## v0.1.0 — Foundation + Batteries (DONE)
**Theme:** Everything a user needs to build, test, and reason about a lifecycle machine with zero friction,
plus honest “batteries included” modules (time, runtime, journal) — no stubs.
**North star:** A new user can go from `cargo add` to a working, tested lifecycle machine in 15 minutes.
### Kernel (DONE)
- [x] `Machine` trait with `initial_mode()`, `on_event()`, `decide()`, `step()`, `step_checked()`
- [x] `Decision` with `stay()`, `transition()`, `emit()`, `emit_all()`, `apply()`, `apply_checked()`
- [x] `ModeChange` struct
- [x] `Policy` trait with `is_allowed()`, `check()`
- [x] `LifecycleError` with `TransitionDenied`
- [x] Free functions: `stay()`, `transition()`, `ignore()`
- [x] `no_std` support (kernel requires `alloc`)
### Testing Helpers (DONE)
- [x] `assert_transitions_to!(decision, expected_mode)` macro
- [x] `assert_stays!(decision)` macro
- [x] `assert_emits!(decision, expected_commands...)` macro
### Time Module (DONE)
- [x] `Clock` trait (`fn now(&self) -> Instant`)
- [x] `Instant` newtype (u64, “nanos”)
- [x] `Deadline` (`Deadline::at`, `is_expired`)
- [x] `ManualClock` for deterministic tests
- [x] `SystemClock` for production (requires `std`)
- [x] `Instant` arithmetic helpers (`checked_add`, `checked_sub`, `checked_duration_since`)
**Constraints:**
- `Clock` stays `no_std` compatible.
- We do not ship a timer wheel/scheduler. The runtime owns timers and emits time events.
### Runtime Module (DONE)
- [x] `runtime::Runner<M>` that owns the current mode
- [x] `Runner::new(machine)` initializes from `Machine::initial_mode`
- [x] `Runner::feed(event)` applies the decision and returns commands
- [x] `Runner::feed_checked(event, policy)` enforces policy with `LifecycleError` on denial
- [x] `Runner::mode()` accessor
- [x] Dispatch helpers:
- `Runner::feed_and_dispatch(event, dispatch)`
- `Runner::feed_checked_and_dispatch(event, policy, dispatch)`
**Constraints:**
- Runner remains synchronous; async runtimes wrap it in a task.
- No internal event queue; callers provide events.
- No thread management; callers own concurrency.
### Journal Module (DONE)
- [x] `TransitionRecord` (step/from/to/event/commands/timestamp)
- [x] `InMemoryJournal` with `append()` and `records()`
- [x] `len()` / `is_empty()`
- [x] `replay(machine, initial_mode)` with actionable replay errors
- [x] `transitions_from(mode)` / `transitions_to(mode)` query helpers
- [x] `last()` accessor
**Constraints:**
- In-memory only. Persistence is an adapter concern.
- Journal is passive: it records but does not affect decisions.
### Developer Experience (DONE)
- [x] `prelude::*` re-exports feature types when enabled (`Runner`, journal/time types)
- [x] `docs/COOKBOOK.md` with copy/paste recipes (doctested)
- [x] `examples/runner.rs` (reference loop + denial handling)
- [x] `full` feature is default and remains dependency-free
- [x] Every feature module is minimal but real (no stubs, no “placeholder” APIs)
### Documentation + Examples (DONE)
- [x] Core narrative docs (`docs/USER_GUIDE.md`, `docs/DESIGN.md`, etc.)
- [x] Multiple realistic examples (basic, recovery, OpenXR session, replay)
### CI/CD Infrastructure (DONE)
- [x] `ci.yml` pipeline: format, clippy, tests (all-features / no-default-features / individual features), examples compile, docs, markdown doc tests
- [x] MSRV job: `cargo check --all-features` on Rust 1.75
- [x] PR checks: VERSION/Cargo.toml sync enforcement, changelog update warning
- [x] `release.yml`: tag-triggered validate (tests + `cargo publish --dry-run`) → publish to crates.io → GitHub Release with extracted changelog notes
- [x] Required secret: `CARGO_REGISTRY_TOKEN`
- [x] `justfile` mirrors CI locally (`just check` runs all verification steps)
- [x] `deny.toml` configured: advisory/license/ban/source policies in place
> **Note:** `behave` is a dev-dependency sourced from git (not crates.io). This does not
> affect published crate users but is a CI fragility point. Track publishing `behave` to
> crates.io as a future improvement.
---
## v0.2.0 — Interop & Observability (PLANNED)
**Theme:** Integrate cleanly with production Rust codebases without turning into a framework.
**Why now:** Production teams will not adopt “pure kernel” crates unless interop is easy:
serialization for audit/replay, structured logs, and an async integration story (as examples).
### Deferred from v0.1.0
These `[MAY]` items were not needed for the initial release and are revisited here:
- [ ] `[MAY]` `Deadline::remaining(now)` (returns `Option<Duration>`) — add if real users ask for it
- [ ] `[MAY]` `Runner::feed_all(events)` batch processing — only if it remains “boring”
- [ ] `[MAY]` `InMemoryJournal::snapshot()` (point-in-time copy) — add if requested by real users
### Fallible Machine (`E` Type Parameter)
The `Machine` trait accepts a generic error type `E` (defaults to `Infallible`). The infrastructure
exists but has no documentation or examples for non-`Infallible` usage.
- [ ] `[MUST]` Add an example using a non-`Infallible` error type (e.g., domain validation error)
- [ ] `[MUST]` Add docs explaining when and why to use a custom `E`
- [ ] `[SHOULD]` Add a test exercising a fallible machine through `Runner` and `Journal`
### Supply Chain & Auditing
- [ ] `[SHOULD]` Add `cargo deny check` to CI pipeline
- [ ] `[SHOULD]` Add `cargo semver-checks` to CI (catches accidental breaking changes)
### Miri
- [ ] `[SHOULD]` Add `cargo +nightly miri test` to CI — low risk (no `unsafe`) but strong trust signal
### Cargo Features (Feature Hygiene)
- [ ] `[MUST]` Add `serde` as an **optional** feature (not part of default `full`)
- [ ] `[MUST]` Keep `full` dependency-free (supply-chain posture is a selling point)
- [ ] `[SHOULD]` Add an `integrations` feature that enables all opt-in deps (`serde`, `defmt`, …)
- [ ] `[MUST]` Document “what you get by default” vs “what pulls dependencies”
### `serde` Feature
- [ ] `[MUST]` `serde` feature flag in `Cargo.toml` with optional dependency on `serde`
- [ ] `[MUST]` `Serialize`/`Deserialize` for:
- `Decision`
- `ModeChange`
- `LifecycleError`
- `journal::TransitionRecord` (when `journal` + `serde`)
- `journal::ReplayError` / `ReplayMismatch` (when `journal` + `serde`) *(optional but ideal)*
- [ ] `[MUST]` Add serde-focused docs:
- how to persist `TransitionRecord` values
- schema stability guidance (what changes are breaking vs additive)
**Acceptance criteria:**
- `cargo test --all-features` passes with `serde` enabled.
- `--no-default-features` still works (serde must not be accidentally pulled in).
- Docs include a copy/paste snippet persisting records to JSON (in user code).
### Tracing / Logging (Examples, Not Dependencies)
- [ ] `[MUST]` Add an example that logs each processed event and decision at the runtime boundary
- [ ] `[SHOULD]` Include structured fields: `from`, `to`, `event`, `commands_len`, `denied`
**Constraint:** this crate does not depend on `tracing` or `log`; we document the pattern.
### `defmt` (Embedded Logging)
- [ ] `[MAY]` Add a `defmt` feature (optional dependency) for formatting core types in embedded logs
- [ ] `[MAY]` Ensure the feature composes with `no_std + alloc`
### Async “Wrapper” Example
- [ ] `[MUST]` Example showing a tokio task that wraps the synchronous `Runner`
- [ ] `[SHOULD]` Example showing channel-based event feeding and command dispatch
**Constraint:** no async runtime inside this crate; only examples.
### Starter Template (“Rails-like New Project”)
- [ ] `[MAY]` Publish a `cargo-generate` template:
- one-file `Mode/Event/Command` skeleton
- minimal runner loop
- 3–5 tests (including policy denial)
- optional tracing + serde integration toggles
---
## v0.3.0 — Embedded & Performance (PLANNED)
**Theme:** Make allocation/performance behavior explicit and safe for embedded/low-latency users.
**Why now:** The biggest realistic adoption footgun is surprise allocation in hot paths.
If we don’t address it pre-1.0, embedded teams will not trust the crate.
### Error Variant Expansion
- [ ] `[MAY]` Additional `LifecycleError` variants for machine-level domain errors
- [ ] `[SHOULD]` Replay errors surface which specific event caused divergence (event index / content)
### Allocation Story (Docs + Guarantees)
- [ ] `[MUST]` Document exactly when allocations can happen (kernel decisions, commands collection, journal recording)
- [ ] `[MUST]` Provide an “embedded checklist” doc section:
- `alloc` requirement
- how to keep command emission minimal
- how to pre-allocate where possible
- [ ] `[SHOULD]` Add a “No allocations in the hot path” recipe (if feasible with current API)
### Command Buffer Strategy (Pick One, Commit Pre-1.0)
We must choose and ship one of these approaches:
- [ ] `[MUST]` **Option A (preferred):** internal “0/1 command is allocation-free” representation
(no API change, keeps ergonomics, improves the common case)
- [ ] `[MUST]` **Option B:** optional fixed-capacity command buffer path (feature-gated)
without making the core API “clever” (no lifetimes in `Machine`, no macro DSL)
**Acceptance criteria (either option):**
- common decisions (`stay()`, `transition(x)`, and `emit(one)`) do not allocate (or are documented if they do)
- behavior is covered by a micro-benchmark or targeted test (where possible)
- docs clearly explain the trade-offs
### Performance Baseline
- [ ] `[MUST]` Add benchmarks for:
- decision throughput (no commands)
- throughput with 1 command
- throughput with N commands
- [ ] `[SHOULD]` Add allocation profiling guidance (how to measure with `-Z` tools / heaptrack equivalents)
### Journal Scaling Notes
- [ ] `[SHOULD]` Document memory growth characteristics of `InMemoryJournal`
- [ ] `[MAY]` Add `InMemoryJournal::retain_last(n)` helper if it proves useful in real systems
---
## v0.4.0 — Property Testing & Tooling (PLANNED)
**Theme:** Confidence at scale through automated testing and lifecycle analysis tooling.
**Why now:** State machines fail in the corners. Pure decision logic makes property testing cheap and powerful.
### Property Testing Helpers
- [ ] `[SHOULD]` Provide guidance (and optionally helpers) for proptest-based event sequences:
- “generate events”
- “run them through a `Runner`”
- “assert invariants”
- [ ] `[MAY]` Optional `proptest` feature that exposes helper utilities (kept out of default features)
### Invariant & Reachability Patterns
- [ ] `[SHOULD]` Document common invariants for lifecycle systems:
- Safe is absorbing unless explicitly recovered
- Active cannot be reached without Ready
- forbidden transitions are rejected by policy
- [ ] `[MAY]` Provide a simple “transition graph extraction” utility if it can stay honest
(no false claims of completeness; only what we can compute)
**What we do NOT build:**
- no model checker (this is not TLA+)
- no formal verification claims
### Visualization (Optional / External)
- [ ] `[MAY]` Provide a tiny example/tool that outputs Mermaid diagrams from recorded journals
(journal-driven visualization is honest because it’s based on observed transitions)
---
## v0.5.0 — Polish & Hardening (PLANNED)
**Theme:** Final API ergonomics work before stabilization and the 1.0 freeze.
### Rust API Guidelines Review
- [ ] `[MUST]` Review against the Rust API Guidelines checklist
- [ ] `[MUST]` Review public types for trait derivation completeness:
- `Debug`
- `Clone`
- `PartialEq` / `Eq`
- `Hash` where appropriate
- [ ] `[SHOULD]` Improve error messages for clarity/actionability (especially replay mismatches)
### Docs & Examples Polish
- [ ] `[MUST]` Ensure all docs match actual behavior (especially around policy denials and replay)
- [ ] `[SHOULD]` Add at least 2 additional “production-shaped” examples:
- service lifecycle (readiness/health, shutdown, safe mode)
- embedded controller lifecycle (fault -> safe -> recovery)
- [ ] `[MAY]` Add migration guides:
- from `statig`
- from `smlang`
- from “plain enum + match”
### Performance Review (Pre-1.0 Gate)
- [ ] `[MUST]` Ensure performance baseline is documented and does not regress across releases
---
## v0.6.0–v0.9.0 — Stabilization (PLANNED)
**Theme:** No new features. Bug fixes, docs, and real-world validation only.
### Criteria for Moving to 1.0
- [ ] API stable (no breaking changes) for at least 2 minor versions
- [ ] All feature modules implemented and documented (`time`, `runtime`, `journal`)
- [ ] `serde` integration shipped (optional feature) or explicitly rejected with rationale
- [ ] Used in at least 2 real projects (internal or external) with written integration notes
- [ ] Benchmarks exist and are tracked (at least locally)
- [ ] No open issues labeled `breaking-change` or `api-design`
### Actions
- [ ] `[MUST]` Freeze the public API surface (document what is stable)
- [ ] `[MUST]` Final doc pass (README, USER_GUIDE, COOKBOOK, RELIABILITY)
- [ ] `[MUST]` Write “0.x -> 1.0” migration notes (even if most changes are additive)
- [ ] `[SHOULD]` Do a security review (even though we have no `unsafe`)
---
## v1.0.0 — Stable
**Theme:** Production-ready, API-frozen, long-term supportable.
### What 1.0 Means (Concrete Promises)
- The kernel vocabulary is frozen: `Machine`, `Decision`, `ModeChange`, `Policy`, `LifecycleError`.
- Semantics are stable and documented:
- policy denials do not update mode and drop commands
- command order is preserved
- journaling/replay errors are actionable and deterministic
- The “batteries” modules (`time`, `runtime`, `journal`) are stable and documented.
- Feature flags are documented and do what they claim (no stubs).
- MSRV policy is documented and enforced.
### What 1.0 Does NOT Mean
- Not “feature complete forever.” New features can be added additively in 1.x.
- Not “no bugs.” Bugs will be fixed in patch releases.
- Not “framework.” We stay a kernel; adapters remain optional.
### Post-1.0 Possibilities (Not Commitments)
- Companion crates:
- async runtime adapter
- persistent journal adapters (file/db)
- observability adapters (tracing/metrics helpers)
- Visualization tooling (Mermaid/Graphviz) based on journal traces
- Policy combinators (`AllOf`, `AnyOf`, `Not`) if real users request them
- Transition metadata helpers (e.g., `Decision::because("reason")`) if it improves audits without adding magic
---
## Decision Log: DOs, DON'Ts, and Rejections
The roadmap is constrained by these rules. These are not “preferences”; they are
the identity boundary that makes the crate valuable.
**DO** (we double down on these):
- keep `Machine::on_event` deterministic and Sans-I/O
- keep decisions as data (effects executed by the runtime)
- keep the runtime boundary explicit (Runner is a convenience, not a framework)
- keep feature flags honest (no stubs, no placeholder modules)
- keep the kernel dependency-free
**DON’T** (we avoid these, because competitors taught us they’re expensive):
- hierarchical/statecharts semantics in this crate
- large callback matrices / hook systems
- internal event queues/schedulers (workflow engine gravity)
- built-in async runtime
- persistence baked into the core journal
- proc-macro DSL as the default interface
See `docs/GUARDRAILS.md` for the full rationale and “where it belongs instead”.
---
## Version Summary
| v0.1.0 | Foundation + Batteries | Kernel + `time`/`runtime`/`journal` modules + docs + examples + CI/CD |
| v0.2.0 | Interop & Observability | Optional `serde`, `E` parameter story, supply chain CI, tracing/async examples |
| v0.3.0 | Embedded & Performance | Allocation story + perf baseline + error expansion + embedded guidance |
| v0.4.0 | Property Testing & Tooling | proptest patterns/helpers + visualization ideas |
| v0.5.0 | Polish & Hardening | API review + migration guides + more examples |
| v0.6–0.9 | Stabilization | API freeze, validation, docs hardening |
| v1.0.0 | Stable | Frozen API, production-ready semantics, long-term posture |
---
## Anti-Roadmap: What We Will Never Build
These are explicit non-goals for every version, including post-1.0.
| Hierarchical / nested states | Not lifecycle modeling. Use `statig` or statecharts crates (e.g., `statechart`). |
| Callback system (beyond 2 hooks) | Callback matrices become the API; ordering becomes untestable. |
| Proc-macro DSL as primary interface | Tooling/errors/debugging degrade. Plain Rust stays the happy path. |
| Event queue / scheduler | Turns into workflow/orchestration; lifecycles are externally driven. |
| Built-in async runtime | Forces runtime choice; async is an adapter concern. |
| Database / file persistence in core | Persistence is an adapter concern; keep the kernel inspectable. |
| GUI / game state management | Wrong domain; we model operational lifecycles. |
| Type-state pattern enforcement | Doesn’t fit dynamic external events without an enum wrapper. |
| Auto-generated methods for transitions | IDE discoverability suffers; macro magic becomes a maintenance cost. |