fix!: up-transitions no longer exit and re-enter the target ancestor

A transition whose target is already active (an ancestor of the
innermost active state) now unwinds only the subtree strictly below
the target. The target's entry/exit actions do not fire and its
default() does not re-descend — you cannot re-enter a state you never
left. The old behavior could loop infinitely when a child transitioned
to its own parent.

Self-transitions (target == innermost) and lateral transitions are
unchanged. See spec §2.6 and tests §T2.8 / T2.8b / T2.8c.

Also in this release:

* Full workspace justfile (check, test, coverage, mutants, pre-release)
* Unit tests covering all __private internals; `cargo mutants` now exits
  clean (38 caught, 178 unviable, 0 missed, 0 timeouts).
* .config/nextest.toml with slow-timeout/terminate-after so hung tests
  fail fast rather than wedging cargo-mutants runs.

BREAKING CHANGE: transition-to-ancestor semantics. Machines that relied
on the old re-entry behavior of up-transitions will observe different
exit/entry action sequences.
This commit is contained in:
swaits committed 2026-04-22 18:22:04 -06:00
1 parent bc7e17e58e
commit 4d7baf7236
12 files changed
+774 -32

No files matched your search

+20
View File
@@ -4,6 +4,26 @@ All notable changes to this workspace are documented here. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
this project adheres to [Semantic Versioning](https://semver.org/).
## 0.2.0 — 2026-04-22
### Fixed — behavior-visible (breaking)
- **Up-transitions no longer exit and re-enter the target ancestor.**
A transition whose target is already active (i.e., the target is on
the path from root to the innermost active state, and the target is
not itself the innermost) now unwinds only the subtree strictly
below the target. The target's `exit:` / `entry:` actions do NOT
fire, and its `default(...)` does NOT re-descend. You cannot
re-enter a state you never left. The old semantics (exit target,
re-enter, re-descend to default) was incorrect and could produce
infinite re-entry when a child transitioned to its own parent.
See spec §2.6, §T2.8, §T2.8b, §T2.8c.
- Self-transitions (`target == innermost`) are unchanged: the target
is still exited and re-entered, timers still restart.
- Lateral transitions (target not on the active path) are unchanged.
- `current_state()` may now return a composite state when it became
innermost via an up-transition.
## 0.1.0 — 2026-04-22
Initial release.