@@ -11,127 +11,99 @@ sessions when entering a state, with automatic cancellation on exit.
1111
1212---
1313
14+ ## Rules
15+
16+ ### SCXML Test Fail Marks
17+ - Only variant-specific marks exist: ` testXXX.sync.fail.md ` / ` testXXX.async.fail.md `
18+ - No generic ` .fail.md ` — each variant is tracked independently
19+ - Marks are ephemeral: delete all and regenerate with ` --upd-fail `
20+ - During development, let tests fail — don't manage marks manually
21+
22+ ### Investigation Methodology
23+ - SCXML W3C tests are black-box integration tests
24+ - To investigate failures, write ** unit tests** that verify specific hypotheses
25+ - Only modify code after hypotheses are confirmed by failing unit tests
26+
27+ ---
28+
1429## Steps
1530
1631### 1. Create worktree and branch
17- - [x] Create git worktree and branch ` feat/invoke `
32+ - [x] Done
1833
1934### 2. Schema + Parser SCXML
20- - [x] Add ` InvokeDefinition ` dataclass to ` schema.py `
21- - [x] Add ` invocations ` field to ` schema.State `
22- - [x] Add ` parse_invoke() ` to ` parser.py `
23- - [x] Call from ` parse_state() ` for ` <invoke> ` elements
24- - [x] ** Commit:** ` feat: parse SCXML <invoke> elements ` (fe19eca → rebased to 6d8067d)
35+ - [x] ` InvokeDefinition ` dataclass, ` parse_invoke() ` , ` invocations ` field
36+ - [x] ** Commit:** 6d8067d
2537
2638### 3. InvokeConfig and InvokeManager
27- - [x] Create ` statemachine/invoke.py ` with:
28- - ` InvokeConfig ` — static configuration
29- - ` Invocation ` — runtime state
30- - ` ParentBridge ` — listener on child for ` #_parent ` sends
31- - ` InvokeManager ` — spawn/cancel/finalize lifecycle
32- - [x] ** Commit:** ` feat: add InvokeManager for child session lifecycle ` (76b9627 → rebased to 2b94c34)
39+ - [x] ` InvokeConfig ` , ` Invocation ` , ` ParentBridge ` , ` InvokeManager `
40+ - [x] ** Commit:** 2b94c34
3341
3442### 4. Engine integration — spawn and cancel
35- - [x] Add ` invoke_manager ` and ` states_to_invoke ` to ` BaseEngine.__init__ `
36- - [x] Track states with invocations in ` _enter_states() `
37- - [x] Cancel invocations in ` _exit_states() ` and ` _handle_final_state() `
38- - [x] Replace TODO in ` sync.py ` with actual invoke spawn code
39- - [x] Replace TODO in ` sync.py ` with finalize + autoforward
40- - [x] Mirror changes in ` async_.py `
41- - [ ] ** Commit** (pending — code written, needs final test pass)
43+ - [x] ` invoke_manager ` + ` states_to_invoke ` in BaseEngine
44+ - [x] Track invocations in ` _enter_states() ` , cancel in ` _exit_states() `
45+ - [x] Spawn in sync (daemon thread) and async (asyncio task) engines
46+ - [x] Finalize and autoforward in processing loops
47+ - [x] Fix async ` _enter_states ` missing ` states_to_invoke `
48+ - [x] ** Commit:** 0780e4f, 33edf3b
4249
4350### 5. Event routing (#_ parent, invokeid, done.invoke)
44- - [x] Add ` invokeid ` field to ` TriggerData ` in ` event_data.py `
45- - [x] Update ` Event.put() ` and ` Event.build_trigger() ` to accept ` invokeid `
46- - [x] Update ` StateChart.send() ` to accept ` invokeid `
47- - [x] Update ` EventDataWrapper.__init__ ` to read ` invokeid ` from trigger_data
48- - [x] Implement ` #_parent ` target in ` create_send_action_callable() `
49- - [x] Implement ` #_child ` target
50- - [x] Implement ` #_<invokeid> ` target
51- - [x] Fix ` _eval_send_params ` duplicate ` machine ` kwarg bug
52- - [x] Fix ` #_scxml_ ` vs ` #_invokeid ` target ordering (test496/521 regression)
53- - [ ] ** Commit** (pending — code written, needs final test pass)
51+ - [x] ` invokeid ` field on ` TriggerData `
52+ - [x] ` #_parent ` , ` #_child ` , ` #_<invokeid> ` send targets
53+ - [x] Fix ` _eval_send_params ` kwarg conflict
54+ - [x] Fix ` #_scxml_ ` vs ` #_invokeid ` target ordering
55+ - [x] ** Commit:** 0780e4f
5456
5557### 6. done_invoke_ naming convention and State(invoke=...) API
56- - [x] Add ` invoke ` parameter to ` State.__init__() `
57- - [x] Add ` _normalize_invoke() ` static method
58- - [x] Add ` invocations ` property to ` InstanceState `
59- - [x] Add ` invoke ` to ` BaseStateKwargs ` in ` io/__init__.py `
60- - [ ] Add ` done_invoke_ ` handler in ` factory.py ` (not yet implemented)
58+ - [x] ` State(invoke=...) ` parameter + ` _normalize_invoke() ` + ` InstanceState.invocations `
59+ - [ ] ` done_invoke_ ` handler in ` factory.py `
60+ - [ ] Unit tests for Python API
6161- [ ] ** Commit**
6262
6363### 7. SCXML advanced invoke features
64- - [x] ` src ` — load SCXML from external file
65- - [x] ` namelist ` / ` <param> ` — pass initial data to child
66- - [x] ` autoforward ` — forward all external events to child
67- - [x] ` <finalize> ` — execute before processing child event
68- - [ ] ` typeexpr ` — evaluate expression for type (not yet)
69- - [ ] ` srcexpr ` — evaluate expression for src (not yet)
64+ - [x] ` src ` — load SCXML from file
65+ - [x] ` namelist ` / ` <param> ` — pass data to child
66+ - [x] ` autoforward ` / ` <finalize> `
67+ - [ ] ` srcexpr ` / ` typeexpr `
68+ - [ ] Fix relative ` src ` path resolution (test239 failure)
7069- [ ] ** Commit**
7170
72- ### 8. Run W3C invoke tests and fix failures
73- - [ ] Remove ` .fail.md ` for passing tests
74- - [ ] Group failures by root cause and fix
75- - [ ] Current status (post-rebase, post-bug-fixes):
76- - Tests that now xpass (need .fail.md removed): test191, test207, test220, test223, test228,
77- test232, test233, test235, test237, test241, test242, test245, test247, test338, test347,
78- test422, test554
79- - Tests still expected to fail: test187, test192, test216, test226, test229, test234, test236,
80- test240, test243, test244, test276, test530
81- - [ ] ** Commits per group**
71+ ### 8. Fix W3C invoke test failures
72+ - [ ] Write unit tests to reproduce specific failure modes
73+ - [ ] Fix root causes identified by unit tests
74+ - [ ] Regenerate fail marks with ` --upd-fail `
75+ - Known failure categories:
76+ - ** Async timing** : child events not reaching parent before timeout
77+ - ** Relative src paths** : ` src="file:test239sub1.scxml" ` not resolved
78+ - ** Cross-session event routing** : ` #_parent ` send from child thread
79+ - ** Session termination** : child onexit not firing on cancel
8280
8381### 9. Unit tests for Python invoke API
84- - [ ] Create ` tests/test_invoke.py `
85- - [ ] Test basic: ` State(invoke=ChildMachine) ` , child terminates, parent gets ` done.invoke `
86- - [ ] Test cancellation: parent exits state, child is cancelled
87- - [ ] Test cross-engine: sync parent + async child, async parent + sync child
88- - [ ] Test ` done_invoke_ ` naming convention
89- - [ ] Test autoforward
90- - [ ] Test multiple invocations
91- - [ ] Use ` sm_runner ` fixture for sync/async coverage
92- - [ ] ** Commit**
82+ - [ ] ` tests/test_invoke.py `
83+ - [ ] Basic: ` State(invoke=ChildMachine) ` , child terminates, ` done.invoke `
84+ - [ ] Cancellation: parent exits state, child cancelled
85+ - [ ] ` done_invoke_ ` naming convention
86+ - [ ] Autoforward, multiple invocations
87+ - [ ] Use ` sm_runner ` fixture
9388
9489### 10. Documentation and release notes
95- - [ ] Create ` docs/invoke.md ` (concept, Python API, SCXML, examples)
96- - [ ] Add to ` docs/index.md ` toctree
97- - [ ] Add section in ` docs/releases/3.0.0.md `
98- - [ ] Reference from ` docs/statecharts.md `
99- - [ ] ** Commit**
90+ - [ ] ` docs/invoke.md `
91+ - [ ] ` docs/index.md ` toctree
92+ - [ ] ` docs/releases/3.0.0.md `
10093
101- ### 11. Final cleanup and verification
102- - [ ] ` uv run ruff check . ` — all clean
103- - [ ] ` uv run ruff format . ` — all clean
104- - [ ] ` uv run mypy statemachine/ ` — no errors
105- - [ ] ` uv run pytest -n auto ` — full suite passes
106- - [ ] Verify W3C invoke tests pass (` .fail.md ` removed for passing tests)
107- - [ ] Fix any regressions
94+ ### 11. Final cleanup
95+ - [ ] Linting, mypy, full test suite
96+ - [ ] Regenerate fail marks with ` --upd-fail `
10897
10998---
11099
111- ## Key Files
112-
113- | File | Status |
114- | ------| --------|
115- | ` statemachine/invoke.py ` | Committed + uncommitted changes |
116- | ` statemachine/engines/base.py ` | Uncommitted changes |
117- | ` statemachine/engines/sync.py ` | Uncommitted changes |
118- | ` statemachine/engines/async_.py ` | Uncommitted changes |
119- | ` statemachine/event.py ` | Uncommitted changes |
120- | ` statemachine/event_data.py ` | Uncommitted changes |
121- | ` statemachine/state.py ` | Uncommitted changes |
122- | ` statemachine/statemachine.py ` | Uncommitted changes |
123- | ` statemachine/io/__init__.py ` | Uncommitted changes |
124- | ` statemachine/io/scxml/actions.py ` | Uncommitted changes |
125- | ` statemachine/io/scxml/processor.py ` | Uncommitted changes |
126- | ` statemachine/io/scxml/schema.py ` | Committed |
127- | ` statemachine/io/scxml/parser.py ` | Committed |
128-
129100## Bugs Fixed
130101
131- 1 . ** ` _eval_send_params ` duplicate ` machine ` kwarg** — function took ` machine ` as positional
132- AND received it via ` **kwargs ` . Fixed by taking only ` **kwargs ` and extracting ` machine ` from it.
133- 2 . ** ` #_scxml_ ` vs ` #_invokeid ` target ordering** — ` #_scxml_foo ` targets were caught by the
134- generic ` #_ ` invoke handler before reaching the ` #_scxml_ ` → ` error.communication ` handler.
135- Fixed by checking ` #_scxml_ ` first.
136- 3 . ** Child ` _parent_sm ` not set during initial entry** — fixed by setting ` _parent_sm ` and
137- ` _invokeid ` on the child CLASS before instantiation.
102+ 1 . ** ` _eval_send_params ` duplicate ` machine ` kwarg** — took ` machine ` as positional AND via
103+ ` **kwargs ` . Fixed by taking only ` **kwargs ` .
104+ 2 . ** ` #_scxml_ ` vs ` #_invokeid ` target ordering** — ` #_scxml_foo ` caught by generic ` #_ `
105+ handler. Fixed by checking ` #_scxml_ ` first.
106+ 3 . ** Child ` _parent_sm ` not set during initial entry** — fixed by setting on child CLASS
107+ before instantiation.
108+ 4 . ** Async ` _enter_states ` missing ` states_to_invoke ` ** — async override didn't track states
109+ with invocations. Fixed by adding the tracking.
0 commit comments