ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# 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

| Version | Theme | Key Deliverables |
|---------|-------|-----------------|
| 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.

| Feature | Why Not |
|---------|---------|
| 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. |