Fix SCXML macrostep ordering and type the execution core - #39
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fff1e69a60
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| internal_queue=deque(), | ||
| context=context, | ||
| event=event, | ||
| output=None, |
There was a problem hiding this comment.
Preserve output when transitioning an already-done snapshot
When Machine.transition or a running interpreter receives any event after reaching a top-level final state, this initializes the macrostep output to None; because the final state is not re-entered, that value reaches the new State and replaces the previously resolved output. Thus even an ignored late event changes a completed snapshot from output={...} to output=None; carry the incoming snapshot output into the macrostep or short-circuit completed states.
AGENTS.md reference: AGENTS.md:L73-L74
Useful? React with 👍 / 👎.
| elif final_child is None or callable(final_child.donedata): | ||
| # Callable output is resolved by the algorithm at entry time. | ||
| # State construction outside a macrostep must never expose the | ||
| # internal HandlerAdapter as public snapshot output. | ||
| self.output = None |
There was a problem hiding this comment.
Restore serialized callable final output
For a final state whose output is callable, serialize_snapshot stores the resolved value, but deserialize_snapshot constructs State without passing that stored output; this new branch therefore restores it as None. A serialize/deserialize round trip now loses callable-derived completion results, so the deserializer needs to supply data["output"] to State.
AGENTS.md reference: AGENTS.md:L72-L72
Useful? React with 👍 / 👎.
Summary
Repairs four reproduced run-to-completion defects in the SCXML execution core and makes
algorithm.pystrict-mypy clean.This is layer 1 of 2. The public generic API will be proposed in a separate draft stacked on this branch.
Why
The normative W3C SCXML ordering is exit states, execute transition content in document order, then enter states. A macrostep must also consume one FIFO internal event queue until the machine reaches a stable configuration. See SCXML 3.13, Selecting and Executing Transitions.
The previous implementation reproduced these failures:
HandlerAdapter;algorithm.pyrelied on a relaxed mypy override and reported 28 errors under standalone strict mypy.Scope
pyproject.toml.State.output.HandlerAdapteras snapshot output.states_to_invokeplumbing from the pure algorithm. Actor reconciliation remains the invocation lifecycle owner.Regression coverage
State.output.Validation
Local validation on Python 3.14.4:
poetry run python -m pytest tests/ --ignore=tests/test_scxml.py- 432 passedpoetry run python -m pytest tests/test_scxml.py- 57 passedpoetry run python -m pytest tests/test_invoke.py tests/test_async_actors.py tests/test_actor_logic.py -q- 34 passedpoetry run mypy src/xstate/- passedpoetry run mypy --strict src/xstate/algorithm.py- passedpoetry run ruff check src/ tests/ scripts/ docs/examples/- passedpoetry run ruff format --check src/ tests/ scripts/ docs/examples/- passedpoetry check --lock- passedpoetry build- passedpoetry run python scripts/validate_distribution.py- installed-wheel smoke passedThe pull-request workflow supplies the requested Python 3.13 and 3.14 matrix evidence.
Hosted validation at exact head
fff1e69a60c3926d2ffc7ec59ac9b0c67cc1b70d:test (3.13, ubuntu-latest)- passedtest (3.14, ubuntu-latest)- passedSCXML smoke- passedcode-quality (3.14, ubuntu-latest)- passedDeliberately out of scope
Risk and rollback
The highest-risk surface is transition/action ordering in parallel configurations and queue lifetime across chained internal events. Existing conflict, entry, exit, history, final-state, SCXML, actor, and invoke suites run unchanged alongside the new regressions.
Rollback is the single commit in this branch. No schema, persistence, dependency, or runtime configuration migration is involved.
Review focus
internal_queueis created once at macrostep start and passed through every microstep.State.states_to_invokedoes not move invocation ownership out ofActorreconciliation.Merge order
Merge this PR before the stacked
typed-public-apiPR. Merge was authorized after exact-head verification.