All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Entries for unreleased work are not written here directly. Each issue drops a
fragment in changelog.d/; the fragments are assembled
into the section below at release. See that README for the format and for when a
change warrants an entry at all.
[2.1.1] 2026-08-24
Documentation-only patch release: the hexdocs package and README were overhauled. No code changes.
Changed
- ADRs are no longer published to hexdocs. They remain in the repository under docs/adr/; hexdocs now carries only the user-facing guides.
- README rebuilt around a quick start, a documentation index, and the badge row.
- Guide and README links converted so cross-references resolve both on hexdocs and on GitHub.
Fixed
- All 69 docstring reference warnings across
lib/fixed;mix docsnow builds with zero warnings.
[2.1.0] 2026-08-24
Added
Statifier.Telemetryemits the[:statifier, :session, ...]events for any stepping driver, taking the driver as a leading atom, so a host stepping the pure interpreter without aStatifier.Sessionprocess can emit the same contract (ADR-0067).
Changed
- Every
[:statifier, :session, ...]event's metadata carriesdriver;Statifier.Sessionemitsdriver: :session.Statifier.Session.Telemetrykeeps its functions and arities as a facade overStatifier.Telemetry.
[2.0.0] 2026-08-22
Statifier 2.0.0 is a ground-up rewrite of the engine. The interpreter is a literal port of the W3C SCXML Appendix D algorithm over a pure functional core that returns effects; sessions, timers, and invocations are layered on top of that core rather than woven through it; predicator remains the datamodel - safe, non-evaluative expressions, no ECMAScript. To a 1.x user the whole engine is new, so this entry is written as a migration document rather than a transcript of the rewrite: the entries below are where 2.0 differs from 1.x in public API and observable behavior. Re-implemented 1.x behavior is not listed.
The core public surface is four functions: Statifier.compile/2,
Statifier.initialize/2, Statifier.send_event/2, and
Statifier.active_leaf_states/1. Everything else - sessions, persistence,
recording and replay, invoke handlers, telemetry - is built on the effect
stream those functions return. docs/architecture.md is the map.
Added
Statifier.compile/1compiles SCXML source straight to aStatifier.Machine, running the parse, lowering, validation, and compile stages in order and stopping at the first stage that fails. Failures from every stage are reported the same way:{:error, [error]}.Statifier.initialize/2runs a compiledStatifier.Machine's initialization macrostep to quiescence,Statifier.send_event/2sends one event to aStatifier.MachineStateand runs a macrostep to quiescence (accepting either aStatifier.Eventor a plain name string), andStatifier.active_leaf_states/1reads the active leaf configuration back as aMapSetof string ids.Statifier.Compiler.compile/1compiles a validatedStatifier.Documentinto aStatifier.Machine: states interned to a flat, document-order tuple with parent pointers and descendant ranges, transitions and executable content given dense document-order indexes, and everycond/expr/<content>compiled once intoStatifier.Machine.expr().Statifier.compile/2accepts an options list (compile/1's existing behavior iscompile/2with no options). The one recognized option,invoke_content_markup: true, is for compiling an<invoke><content>markup slice standalone; it is not a general validation off-switch.Statifier.compile/2stamps a content-hash chart identity onto everyStatifier.Machine.t()it produces (Statifier.Machine.identity/1).Statifier.Machine.warningscarries the validator's non-fatal findings on a successfully compiled machine.Statifier.Machine.StateandStatifier.Machine.Transitioneach carry anattribute_locationsfield, the source node's written-attribute value spans carried through compilation verbatim - the same treatmentStatifier.Machine.Invokealready had. An entry exists only for an attribute the author actually wrote, soMap.has_key?/2on the map answers "was this written or defaulted" for a transition'stypeor a history'stype, which the compiled field's value alone cannot. The root state (index 0) carries the<scxml>element's own attribute spans; the interpreter-synthesized initial transition carries%{}.Statifier.Parser.Location.resolve_span/4maps a predicator expression span onto an absolute document location, entity and character references in the raw attribute text included.<datamodel>/<data>are now supported, with bothbinding="early"andbinding="late"respected: a<data>'sexpror child content is compiled and, under early binding, evaluated at document-initialization time; under late binding, evaluation is deferred until the owning state is entered.<assign>is now supported:expror child content writes a value at a deeplocationpath (user.profile.name,items[0].sku), auto-vivifying intermediate maps and lists along the way.<script>is now supported: a body is a predicator statement program, compiled at load and run against the session datamodel wherever executable content or a top-level child of<scxml>may appear.<script src>is rejected at load; a body outside predicator's statement grammar (var, compound assignment, object literals,typeof, function definitions) is deferred to runtime and raiseserror.executionwhen reached.- A transition's
condattribute is evaluated against the datamodel and gates whether the transition is selected:trueenables it,falsedoes not. Acondthat fails to evaluate totrueorfalse(an unbound variable, or any other non-boolean result) raiseserror.executionon the internal event queue instead of silently not enabling the transition, catchable in the same macrostep by a<transition event="error.execution">on the currently active state. Statifier.EventData.coerce/1: one normalization function that turns raw<content>text, a<param>list, or an already-evaluated expression value into_event.data, per spec B.2.8.1.Statifier.Sessionis a new GenServer that runs a state chart to completion:start_link/2starts one,send_event/2delivers events asynchronously,interpret/2hands it externally-produced effects,cancel/1stops it as<cancel>would,snapshot/1andstatus/1read its current position, andsubscribe/2/unsubscribe/2manage the subscriber pids that receive its effect stream.Statifier.Supervisor, the optional session runtime an embedder places in their own supervision tree, holding the library's session registry and the dynamic supervisor sessions start on. The library ships no application callback, so a host that never places it starts no processes.Statifier.start_session/2, which starts a session on that runtime and registers it, making it reachable by#_scxml_<sessionid>from another session. A bareStatifier.Session.start_link/2session stays legal and unregistered, and is not reachable that way.<send>targets beyond the sending session:#_internal/_internalroute to the internal queue,#_scxml_<sessionid>delivers to the named registered session (the sending session's own id delivers to its own inbox with no registry),#_parent(or_parent) reaches the invoking parent, and#_<invokeid>delivers to that live invoked child's external queue. A target naming a session, parent, or invocation that is not reachable raiseserror.communicationon the sending session, per the SCXML Event I/O Processor (C.1). Delivered events carryorigin,origintype, andsendidper spec 5.10.1/C.1.<send delay>/delayexpraccepts a native predicator duration value (e.g. a computed2s + backoff) as well as a string, and recognizes the full{y,mo,w,d,h,m,s,ms}unit set rather than the SCXML schema's{ms,s,m,h,d}subset.<invoke>is now lowered, validated, and compiled, including<param>,<content>, and<finalize>. The interpreter runs the Appendix D invoke and cancel-invoke passes and emits{:invoke, _},{:cancel_invoke, _}, and{:autoforward, _}effects;<finalize>runs in the core before transition selection.<invoke type="scxml">starts a real child session, under an embedder-placedStatifier.Supervisor. The child's datamodel is seeded from<param>/namelist values whose names match one of its own top-level<data>ids; the parent monitors the child and the child monitors the parent.<send target="#_parent">reaches the parent, running the invoking state's<finalize>first; a child that reaches a top-level final deliversdone.invoke.<invokeid>to the parent carrying its<donedata>; every external event the parent processes is forwarded verbatim to eachautoforward="true"invocation; and exiting the invoking state stops the child (running its<onexit>handlers) and discards any of its events still queued at the parent.<invoke><content><scxml>...</scxml></content></invoke>compiles, and starts the inline child document as a session.<content>'s element children are preserved as the verbatim source they were written as, and compiled when the invocation runs; a<content>that specifies both anexprand inline markup is rejected as the same 5.6.2 violation asexpralongside inline text. A child<scxml>that declares noxmlnsat all is placed in the SCXML namespace per spec Appendix G.6; a child that declares a namespace other than SCXML's raiseserror.communicationwhen the invocation runs, and markup whose root uses a namespace prefix declared on an ancestor outside<content>is not supported (the declaration is lost with the slice).- A new
invoke_sourceoption onStatifier.start_session/2andStatifier.Session.start_link/2: a functionsrc -> {:ok, Machine.t()} | {:error, term()}an embedder supplies to resolve<invoke src="...">. The library never fetches asrcitself - with noinvoke_sourceconfigured, asrc-based<invoke>raiseserror.communicationon the parent, the same as any other failure to start the invoked child. - Hosts can register their own
<invoke>handlers for types beyond the built-inscxml(and its long-URI spelling): a newStatifier.Invoke.Handlerbehaviour and a per-sessioninvoke_handlers: %{type_string => module}option onStatifier.Session.start_link/2. A handler implements three pure planning callbacks (start/2,cancel/2,forward/3) and one optional impureperform/2; the built-inscxmlhandling runs behind this same interface.perform/2may be called more than once for the sameinvoke_idafter a crash and retry, so handlers must be idempotent on it - the library performs no deduplication of its own. An<invoke>whose type names no registered handler raiseserror.execution. - A new
Statifier.Session.done_invocation/3reports a non-scxmlinvocation's completion back to its owning session, constructingdone.invoke.<invoke_id>from the caller-supplied donedata - the door a process-less or externally-run service uses in place of a child session's owndonetransition. Statifier.Session.invocations/1lists a session's live invocations as%{invoke_id, session_id, pid}, sorted byinvoke_id.Statifier.Session.start_link/2's:inherit_observersoption (defaultfalse) starts every child a session starts for an<invoke>with that session's:tracesetting and subscriber pids, plusinherit_observers: trueof its own, so one opt-in at the root traces the whole invoke tree transitively.Statifier.Chart.to_binary/1andfrom_binary/1give a compiled chart a versioned binary contract that carries its SCXML source and recompiles on load, never a compiled term.Statifier.Machine.source/1andcompile_opts/1expose the source and persisted compile options aMachinewas built from.Statifier.Position.to_binary/1andfrom_binary/2give aMachineStatea versioned binary contract that refuses to load against a chart whose identity does not match.Statifier.Position.export/1andimport/2let a host migrate a position across chart revisions using string state ids.Statifier.Session.start_link/2gains a:resumeoption that boots a session at a persistedStatifier.Positioninstead of runningStatifier.Interpreter.initialize/2. In-flight delayed-send timers, live invoked children, and the external inbox are not restored; seedocs/persistence.mdfor why and for the host's remaining obligations.Statifier.Session.start_link/2acceptsrecord: trueto capture every delivered event, timer firing, cancel marker, andinterpret/2batch a session handles, in input order.Statifier.Session.recording/1returns the capturedStatifier.Session.Recording.t(), or{:error, :not_recording}if the session was not started withrecord: true.Statifier.Replay.run/1replays aStatifier.Session.Recording.t()through the pure core with no process and no timer, reproducing the original run's effect stream and terminal%Statifier.MachineState{}.Statifier.Session.Recording.to_binary/1andfrom_binary/1give a recording a versioned binary contract that nests the chart's own blob and recompiles it on load, never a compiled term.Statifier.Session.subscribe/3withcatch_up: truereturns the session's recording alongside the subscription, so a pid that attached afterstart_link/2can rebuild the effects it missed withStatifier.Replay.run/1. Requiresrecord: true; otherwise returns{:error, :not_recorded}and does not subscribe.Statifier.MachineState.new/2accepts:max_macrostep_rounds(a positive integer, default10_000, or:infinity), bounding one macrostep's fold.- A trace of a macrostep that never reaches quiescence can be ordered and
counted:
%Statifier.MachineState{}carries aroundcounter, and theStatifier.Effect.Trace.*payloads,%Statifier.Event.Cause{}, and%Statifier.Effect.BudgetExhausted{}are stamped with it. Statifier.Sessionemits:telemetryevents for its effect and trace stream - lifecycle, macrostep spans, and per-effect events, all prefixed[:statifier, :session, ...].Statifier.Session.Telemetry.events/0enumerates every event name a session can emit, and its moduledoc is the full measurement/metadata reference.Statifier.Effect.DatamodelInitjoins the core effect vocabulary as{:datamodel_init, %Statifier.Effect.DatamodelInit{}}, carrying the datamodel's starting map, andStatifier.Effect.DatamodelChangejoins it as{:datamodel_change, %Statifier.Effect.DatamodelChange{}}; both have matching[:statifier, :session, :effect, ...]telemetry events. Every<data>binding - early or late - emits a:datamodel_changenaming ad_indexfield, the<data>element's own compiler-assigned identity (mutually exclusive with the existingc_index); a failed or environment-overridden binding emits nothing, matching the write-side rule, andmetadata.locationfor a binding resolves throughStatifier.Machine.data/2.- The starting datamodel is fully reconstructable from the effect stream
alone, with no
Session.snapshot/1call and no%Machine{}handle: a subscriber folds the one:datamodel_initbaseline and every:datamodel_changeafter it and reproduces every datamodel key exactly, except"_event"(spec 5.10's current-event-under-evaluation, not authored or assigned content). This holds under bothbinding="early"andbinding="late", for an environment-supplied:datamodeloption (including one that shadows a top-level<data>), and withtrace: false- both effects are core.
Statifier.Effect.Trace.EntrySetandStatifier.Effect.Trace.ExitSetgain aconfigurationfield: the full configuration (ancestors included) as it stands after the entry set or exit set the payload names has been applied, so a subscriber can render the active configuration after every microstep without foldingindexesdeltas itself. AtStatifier.Interpreter.exit_interpreter/1's termination sweep,ExitSet.configurationis the empty set;Trace.Done.configurationstill carries the configuration as it stood at exit.Statifier.Effect.SendDelayedandStatifier.Effect.Cancelgain anordinalfield: a per-execution sequence number minted fromStatifier.MachineState's session-globaltimer_counter, which starts at0and never resets. It is what disambiguates two<foreach>iterations that execute the same<send delay>or<cancel>content node, in the same microstep, under the same author-written id - a durable host's at-least-once dedup key reads{session scope, send_id, macrostep, microstep, round, c_index, owner, ordinal}.[:statifier, :session, :effect, :send_delayed]and[..., :cancel]both carryordinalin their telemetry measurements.Statifier.Event,Statifier.Effect.SendDelayed, andStatifier.Effect.Cancelgain an opaquecaller_context :: term()field (defaultnil, ADR-0063). A host attaches a correlation value - an OTel span context, a request id - viaStatifier.Event.external(name, caller_context: ctx); the library copies it onto the durable-timer effects and onto the events a scheduled timer later delivers, and never reads it. Four telemetry events carry acaller_contextmetadata key:[:statifier, :session, :macrostep, :start]and[..., :stop](the triggering external event's slot,nilfor the other triggers), and[:statifier, :session, :effect, :send_delayed]and[..., :cancel](the effect's own slot).Statifier.Testing.CaseandStatifier.Testing.FeatureDetectorare public API. A downstream application can write declarative chart tests - an expected initial configuration, then{event, expected_configuration}steps - withuse Statifier.Testing.Case, without copying files out of this repository. Documents using an unsupported SCXML feature flunk naming the feature rather than skipping.test_scxml/4accepts optional:settle_window_msand:configuration_deadline_msfor charts whose load-bearing delays exceed the defaults. Seedocs/testing-charts.md.Statifier.Testing.HandlerCase: a reusable conformance case anyStatifier.Invoke.Handlerimplementation runs against itself - planning purity and determinism,perform/2idempotency oninvoke_id, cancel semantics for unknown invocations,error.executionsurfacing for a failingstart/2, and exception propagation.
Changed
- Expression syntax follows predicator 9.0, and the
predicatordependency floor is~> 9.0(v1 shipped against predicator 2.x).if,else,while,undefined, andnullare reserved words: using one as a bare identifier, as a property name after., or as an unquoted object key is a compile error; quote an object key ({"null": 1}) to keep it. A bareundefinedis the undefined literal and a barenullthe null literal rather than variable loads. A declared datamodel entry with no value reads asundefined, notnull, sox === undefinedkeeps its answer.Math.powandMath.sqrtreturn an integer, not a float, when both the base/radicand and the result are integer-exact (Math.pow(2, 3)is8, not8.0); a float argument or a non-integer result is unaffected. Statifier.Event'sdatadefault moves fromnilto:undefined, and an environment-supplied:datamodelvalue ofnilnow means predicator's null rather than "declared, no value yet" - pass:undefinedfor that.Statifier.Validator.validate/2returns a third element, a list of non-fatal warnings, on both its{:ok, ...}and{:error, ...}arms.- Rejects an SCXML document whose root is missing
xmlnsorversion, or whoseversionis not"1.0". v1 silently inserted both into the source and accepted any version. - Source locations always refer to the binary you passed in, whether or not
it has an
xmlns, aversion, or an XML declaration. v1's relaxed mode rewrote the source and shifted reported positions; v2 never rewrites, so a span slices back out of your own string. <log expr>is evaluated against the datamodel; a failed evaluation raiseserror.executioninstead of loggingnil.<donedata><content>evaluates: anexprattribute is evaluated against the datamodel and its value coerced throughStatifier.EventData.coerce/1; a text body is likewise coerced (so<content>21</content>yields the integer21, not the string"21"). A failedexprraiseserror.executionand thedone.state.*/ terminalEffect.Doneevent carries no data, matching spec 5.10.1's "leave the field blank" rather than 5.6's empty-string rule for<content>in other contexts.<param>under<donedata>compiles and evaluates: each<param>'sexprorlocationattribute compiles through the same value-expression path, and at done time the params fold in document order into a string-keyed map viaStatifier.EventData.coerce({:params, _})- a duplicate name takes the last value, and a<donedata>with no surviving params carriesnildata, not%{}. A param whose expression fails to evaluate raises its ownerror.executionand is dropped from the result rather than aborting the remaining params (spec 5.7's "MUST ignore the name and value"). Thedone.state.*event and the terminalEffect.Doneboth carry the same folded map, and each sucherror.executioncarries a{:donedata_param, state_index, param_index}cause origin naming the individual<param>that failed. A<donedata>must not specify both a<content>child and one or more<param>children (spec 5.5), and each<param>must specify exactly one ofexprorlocation(spec 5.7).- The core
{:done, %Statifier.Effect.Done{}}effect carriesconfiguration, the full configuration as it stood at exit, so a consumer can observe the terminal position without switching tracing on. Statifier.Sessionisrestart: :temporary(v1's supervisor-friendly restart posture does not carry over). A supervisor restart would mint a freshsess_id and lose the crashed session's state, so restarting was never recoverable.- With tracing on, an empty executable-content block (an
<onentry/>or<onexit/>with no children) emits aTrace.ContentExecutedeffect withc_indexes: [], so a trace consumer can tell it ran with no content apart from a block that never ran at all. Untraced runs are unaffected. - A
<send>or<invoke>whosenamelistattribute contains a syntactically ill-formed location expression now loads successfully throughStatifier.compile/1instead of failing the whole document. The malformed entry is caught when the element runs: a<send>discards the message and an<invoke>aborts, each raisingerror.execution, per ADR-0036 and ADR-0031. Statifier.Session.Recordingblobs are format version 2, carrying an optional anchor position for recordings started by a resumed session; version 1 blobs still decode.- Hand-built
%Statifier.Effect.SendDelayed{}and%Statifier.Effect.Cancel{}structs - test fixtures and durable-scheduler harnesses, mostly - must passordinal; it is enforced on both structs. Pattern matching on either struct is unaffected. Where a hand-built effect only needs to be distinct from its neighbours, any positive integer will do; where it stands in for one the engine produced, use theordinalthe engine stamped.
Removed
Statifier.Parser.parse/1takes no options. v1'sparse/2had:relaxed(default true) and:xml_declaration(default false); v2 behaves as v1 did withrelaxed: true, xml_declaration: falsealways, so callers relying on either option just drop it. A missing SCXML namespace or version is reported by validation.- Drops the
uxiddependency. Session ids keep the samesess_-prefixed, hyphen-free, time-sortable format, so nothing that reads_sessionidneeds to change.
Fixed
- Character data is folded per XML 1.0 2.11: a literal CRLF pair or a lone
literal CR in a
<script>,<content>,<data>, or<assign>text body becomes a single\n, while a\rdecoded from a character reference such as keeps its character.Script.text,Content.text,Data.text, andAssign.textnow match what a conforming XML processor hands the engine on a CRLF checkout. - Attribute values are normalized per XML 1.0 3.3.3: a literal tab, newline, or
carriage return inside an attribute value becomes a space, while a character
reference such as
keeps its character. Acondorexprwrapped across source lines now compiles from a single-line string. - A
<send>whosetypeis unsupported, or whosetargetnames none of the special targets C.1 defines, is rejected while the element is evaluated rather than after its block has run. The rest of the enclosing<onentry>/<onexit>/transition block no longer executes, per spec 4.9, and the raisederror.executioncarries the failing send'ssendid. - A
<send>whose target names a session, parent, or invocation that is not reachable raiseserror.communicationat the<send>'s own position, carrying itssendid, and the rest of the enclosing block does not run (spec 4.9, C.1). A target that becomes unreachable after the block has run still raises the error afterwards. <scxml initial="...">naming a state nested under a wrapper state no longer fails validation. The document enters that state together with every ancestor between it and the root, per spec 3.11.- A macrostep that cannot reach quiescence returns a
{:budget_exhausted, %Statifier.Effect.BudgetExhausted{}}effect with a resumable position instead of hanging the calling process. Statifier.Position.to_binary/1no longer writesroutesorinvoke_typesinto the blob, andStatifier.Position.from_binary/2now blanks both fields tonilon decode regardless of what the blob carries. A resumed position previously came back with the stale per-drive snapshot from whenever it was saved; a host must re-stamp both before the first drive, asStatifier.Interpreter's moduledoc instructs.- A session reaching its top-level final (or being cancelled) discards its
own pending delayed sends at the halt, per spec 6.2, instead of leaving
the timers armed on the idled process - previously a delayed
<send>scheduled before termination still fired and delivered to a live cross-session target.:budget_exhaustedkeeps its timers armed until a latercancel/1, since the interpreter has not exited there.
[1.9.0] 2025-09-09
Added
Enhanced Data Model Support
Statifier.active_leaf_states/1: Added public API function to retrieve only leaf states from active configuration- Leaf State Focus: Returns only the leaf (atomic) states that are currently active, excluding ancestor states
- MapSet Return: Returns active leaf states as a MapSet for efficient membership testing and set operations
- Public API: Provides direct access to leaf state information for client applications
- Documentation: Comprehensive function documentation with clear usage examples
Enhanced Event Processing Improvements
SCXML-Compliant Event Matching: Complete implementation of W3C SCXML event matching patterns
- Universal Wildcard: "*" matches any event name per SCXML specification
- Prefix Matching: "foo" matches "foo", "foo.bar", "foo.bar.baz" with dot-separated token logic
- OR Pattern Support: "foo bar" matches events that match "foo" OR "bar" (space-separated alternatives)
- Wildcard Suffix: "foo.*" matches "foo.bar", "foo.baz" but not "foo" (requires additional tokens)
- Token-Based Logic: Proper dot-separated token parsing for hierarchical event names
Error Event Generation: Comprehensive error.execution event generation per SCXML specification
- Assign Action Errors: Failed assignments now generate error.execution events with detailed context
- Error Event Structure: Events include reason, type, location, and expression information
- Internal Event Queue: Error events properly queued as internal events for processing
- Graceful Error Handling: State machine continues execution after logging errors
Strict Nested Assignment Validation
- Enhanced Datamodel Validation: Strict checking for nested map assignments to prevent auto-creation
- Intermediate Structure Validation: Assignments to nested paths require all intermediate structures to exist
- Type Safety: Prevents assignment to non-map intermediate values with clear error messages
- SCXML Compliance: Aligns with proper SCXML datamodel semantics for assignment operations
- Error Reporting: Detailed error messages indicating specific validation failures
Changed
Test Infrastructure Improvements
- Updated Internal Tests: Modified 9 internal tests to expect strict nested assignment behavior
- Correct SCXML Behavior: Tests now verify proper failure when attempting to assign to non-existent intermediate structures
- Error Expectation: Tests properly expect {:error, reason} responses for invalid assignments
- Maintained Coverage: All test updates preserve comprehensive test coverage
Event Processing Updates
- Enhanced Event Module: Improved event matching capabilities with comprehensive pattern support
- Robust Pattern Matching: Handles complex event patterns with proper token parsing
- Performance Optimization: Efficient string splitting and token comparison algorithms
- Comprehensive Testing: Full test coverage for all event matching scenarios
Fixed
Code Quality Improvements
- Removed Unnecessary Validation: Eliminated whitespace validation from Evaluator resolve_location functions
- Simplified Logic: Removed redundant whitespace checking that wasn't addressing root issues
- Cleaner Implementation: Focus on core location resolution functionality without extra validation layers
- Performance: Reduced unnecessary string processing in location resolution
Datamodel Assignment Fixes
- Strict Assignment Implementation: Fixed auto-creation of intermediate map structures in nested assignments
- Prevented Invalid Behavior: No longer auto-creates intermediate maps when assigning to nested paths
- Proper Error Handling: Clear error messages when attempting to assign to non-existent intermediate structures
- SCXML Compliance: Aligns with W3C SCXML specification for datamodel assignment semantics
Technical Improvements
Enhanced Test Coverage
- Comprehensive Event Testing: Added extensive tests for SCXML event matching patterns
- Universal Wildcard Tests: Verification that "*" matches all event types
- Prefix Pattern Tests: Testing hierarchical event matching with dot notation
- OR Logic Tests: Validation of space-separated alternative event patterns
- Wildcard Suffix Tests: Complex wildcard pattern testing with proper token requirements
- Coverage Achievement: Improved test coverage to 90.1% (up from 89.7%)
Developer Experience
- Enhanced Error Messages: Improved error context and logging throughout assignment operations
- Structured Logging: Comprehensive logging with metadata for debugging assignment failures
- Debug Support: Enhanced debugging capabilities with elixir log adapter recommendations
Examples
Event Matching Patterns
<!-- Universal wildcard - matches any event -->
<transition event="*" target="catch_all"/>
<!-- Prefix matching - matches "user", "user.login", "user.logout" -->
<transition event="user" target="user_handler"/>
<!-- OR patterns - matches "start" OR "begin" OR "init" -->
<transition event="start begin init" target="startup"/>
<!-- Wildcard suffix - matches "system.error", "system.warning" but not "system" -->
<transition event="system.*" target="system_handler"/>Error Event Handling
<state id="processing">
<onentry>
<!-- This will generate error.execution event if foo doesn't exist -->
<assign location="foo.bar" expr="'value'"/>
</onentry>
<!-- Handle assignment errors -->
<transition event="error.execution" target="error_state">
<log expr="'Assignment failed: ' + _event.data.reason"/>
</transition>
</state>Strict Assignment Validation
# This will now fail with proper error instead of auto-creating structures
{:error, reason} = Datamodel.put_in_path(%{}, ["foo", "bar"], "value")
# reason: "Cannot assign to nested path: 'foo' does not exist"
# Proper usage requires intermediate structures to exist
datamodel = %{"foo" => %{}}
{:ok, updated} = Datamodel.put_in_path(datamodel, ["foo", "bar"], "value")
# updated: %{"foo" => %{"bar" => "value"}}Migration Notes
- Event Matching: Existing event patterns continue to work with enhanced capabilities
- Assignment Behavior: Code relying on auto-creation of intermediate structures may need updates
- Error Handling: New error.execution events provide better error visibility and handling
- Test Coverage: Internal tests updated to reflect correct SCXML assignment behavior
Notes
- Enhanced SCXML Compliance: Improved adherence to W3C SCXML specification for event processing and datamodel operations
- Better Error Handling: Comprehensive error event generation and structured error reporting
- Robust Event Processing: Full implementation of SCXML event matching patterns with proper token-based logic
- Strict Datamodel Semantics: Proper validation of nested assignments prevents unexpected behavior
- Test Coverage Improvement: Achieved 90.1% test coverage with comprehensive event matching tests
[1.8.0] 2025-09-02
Added
Documentation Site and Infrastructure
- VitePress Documentation Site: Complete documentation site setup with Diataxis structure following the four documentation types (Tutorials, How-to Guides, Reference, Explanation)
- GitHub Actions Integration: Automatic deployment to GitHub Pages with separate workflows for documentation building and linting
- Specialized Documentation Agent: Added Diataxis-aware documentation agent for structured content creation and management
SCXML Feature Enhancements
- Internal Transitions: Complete implementation of
type="internal"transitions that execute actions without exiting/re-entering source state per W3C SCXML specification - Enhanced Send Elements: Improved
<send>element support with better content data processing, parameter validation, and JSON serialization for complex values - Nested If Parsing: Fixed SAX parser to properly handle nested
<if>blocks, enabling complex conditional logic structures
Performance and Developer Experience
- Macro-Based Logging: Converted all LogManager logging functions to performance-optimized macros with lazy evaluation and zero overhead when logging is disabled
- Environment-Aware Logging: Added automatic logging configuration based on environment (trace in dev, debug in test, info in prod)
- Enhanced Debugging: Comprehensive trace logging throughout transition resolution and action execution with structured metadata
Fixed
- Parallel Transition Conflicts: Enhanced TransitionResolver to handle conflicts between transitions from parallel regions using document order per SCXML specification
- Expression Compilation: Moved expression compilation from creation-time to validation-time with fallback runtime compilation for backward compatibility
- Pipeline-Friendly APIs: Updated all action execute functions to take state_chart as first argument for better Elixir pipeline composition
Changed
- Feature Detection Updates: Moved send_content_elements and send_param_elements from partial to supported status
- Code Quality: Resolved Credo static analysis issues by extracting helper functions and reducing cyclomatic complexity
- Error Handling: Improved error context logging for failed conditions and expression evaluations
Infrastructure
- Separated Workflows: Split documentation workflows into focused build/deployment and linting processes
- ESM Module Support: Added proper ES module configuration for VitePress compatibility
- Test Coverage: Improved test coverage across parser components, action executors, and logging infrastructure
[1.7.0] 2025-09-01
Added
Enhanced SCXML Feature Detection and Test Infrastructure
- Comprehensive Feature Detection: Added detection for 8 new SCXML features including wildcard_events, invoke_elements, script_elements, cancel_elements, finalize_elements, donedata_elements, send_content_elements, send_param_elements, and send_delay_expressions
- Automated Test Updates: Created script to automatically update @required_features attributes across 182 test files (123 SCION + 59 W3C) based on actual XML content analysis
- Wildcard Events Support: Full implementation of event="*" patterns with proper transition processing and comprehensive test coverage
- Partial Feature Testing: Modified test framework to allow :partial features to run, providing better feedback instead of automatic exclusion
New SCXML Elements and Features
- Foreach Element Support: Complete SCXML
<foreach>implementation with W3C-compliant variable scoping, permanent variable declaration, and nested action support - Targetless Transitions: Implementation of SCXML targetless transitions that execute actions without state changes, following W3C specification requirements
- Enhanced Send Elements: Improved
<send>element parsing with proper content element text capture, fixing previously ignored text content in send actions
Development Infrastructure
- Quality Mix Task: Added comprehensive
mix qualitytask with automated formatting, testing, static analysis, and coverage checking - Coverage Improvements: Significantly improved test coverage across multiple modules including parser components, action executors, and logging infrastructure
Changed
Test Framework Improvements
- Enhanced Feature Validation: Updated FeatureDetector.validate_features/1 to treat :partial features as runnable rather than excluded
- Improved Test Accuracy: All test files now have precise feature requirements based on actual SCXML content rather than manual specification
- Better Regression Coverage: Regression test coverage improved from 141/142 to 145/145 (100% pass rate)
SCXML Compliance Enhancements
- History State Fixes: Fixed history state restoration to properly execute ancestor onentry actions per W3C specification
- Logging Improvements: Implemented safe_to_string function to handle complex data types in log actions, preventing String.Chars protocol errors
- Increased Iteration Limits: Raised eventless transition iteration limit from 100 to 1000 to handle complex automatic transition chains
Fixed
- Content Element Parsing: Fixed SAX parser to capture text content
within
<content>elements for send actions - Variable Scoping: Proper SCXML variable scoping in foreach loops with restoration of existing variables after iteration
- Feature Classification: Corrected wildcard_events status from :partial to :supported with full implementation
Benefits
- Enhanced Test Coverage: Comprehensive detection prevents false positive/negative test results with accurate feature requirements
- Better Development Feedback: Partial features now provide real feedback rather than being automatically excluded from testing
- SCXML Compliance: Improved adherence to W3C SCXML specification with proper implementation of complex features like foreach and targetless transitions
- Developer Experience: Automated quality checking and enhanced test infrastructure provide better development workflow
All 857+ tests continue to pass with enhanced regression coverage and improved SCXML feature support.
[1.6.0] 2025-08-30
Changed
API Consolidation and Cleanup
Consolidated Active States API: Unified active states functionality into single source of truth
- Renamed Functions for Clarity:
active_states→active_leaf_states,active_ancestors→all_active_states - Single Source of Truth: All active state queries now handled by
Configurationmodule - Removed Wrapper Functions: Eliminated duplicate functions from
StateChartandInterpretermodules - Updated All Tests: All 857 tests updated to use consolidated API directly
- Fixed History Tracking: History tracking now correctly uses all active states (including ancestors) for proper shallow history computation
- Renamed Functions for Clarity:
Removed Backwards Compatibility Layers: Cleaned up legacy API functions for better maintainability
- Removed Legacy Delegates: Eliminated
Statifier.validate/1andStatifier.interpret/1delegate functions - Removed
Statifier.parse_only/1: Eliminated unused function that provided no additional value overSCXML.parse/2 - Forced Explicit Module Usage: Users must now call
Statifier.Interpreter.initialize/1andStatifier.Validator.validate/1directly - Updated Documentation: All examples and README updated to use explicit module references
- Removed Legacy Delegates: Eliminated
Code Quality Improvements
Implemented Proper Logging: Replaced TODO comments with actual logging infrastructure
- Validation Warning Logging:
Interpreter.initialize/1now properly logs validation warnings usingLogManager - Structured Logging: Warnings logged with warning count and detailed messages
- Consistent Infrastructure: Uses existing logging system throughout codebase
- Validation Warning Logging:
Fixed All Credo Issues: Resolved all static code analysis warnings
- Added Module Aliases: Proper module aliasing to eliminate nested module access warnings
- Clean Code Standards: All 288 source files now pass
mix credo --strictwith no issues - Improved Readability: Better import organization and alias usage
Benefits
- Clearer API: Eliminates confusion between multiple similar functions
- Better Maintainability: Single source of truth for active state management
- Explicit Architecture: Direct module usage removes API ambiguity
- Enhanced Debugging: Proper structured logging for validation issues
- Code Quality: Clean codebase with no static analysis issues
All 857 tests pass with 91.2% code coverage maintained throughout the refactoring.
[1.5.0] 2025-08-29
Added
Modern API with Relaxed Parsing Mode
Statifier.parse/2Function: New streamlined API combining parsing and validation in one call- 3-Tuple Return Format: Returns
{:ok, document, warnings}for comprehensive result handling - Automatic Validation: Validates documents by default, returns errors as
{:error, {:validation_errors, errors, warnings}} - Options Support: Accepts keyword options for parsing customization
- Relaxed Mode Support: Passes options to SCXML.parse for enhanced flexibility
- Skip Validation Option:
validate: falsereturns unvalidated documents for advanced use cases
- 3-Tuple Return Format: Returns
Enhanced
SCXML.parse/2with XML Normalization: Comprehensive relaxed parsing mode for simplified SCXML authoring- XML Declaration Handling: Optional XML declaration addition with
xml_declarationoption (default: false to preserve line numbers) - Default Namespace Addition: Automatically adds W3C SCXML namespace when missing
- Default Version Addition: Automatically adds version="1.0" when missing
- Backwards Compatible: Preserves existing XML declarations and attributes when present
- Test-Friendly: Eliminates XML boilerplate for cleaner test documents
- XML Declaration Handling: Optional XML declaration addition with
Validation Status Tracking: Added
validatedfield to Document struct for better API clarity- Document.validated: Boolean field indicating whether document has been validated
- Interpreter Optimization: Skips redundant validation for pre-validated documents
- Helper Function:
Statifier.validated?/1for checking document validation status
Basic Send Element Support
<send>Element Implementation: Comprehensive Phase 1 support for SCXML send elements with internal event communicationStatifier.Actions.SendAction: Complete data structure with event_expr, target_expr, type_expr, delay_expr, namelist supportStatifier.Actions.SendParam: Support for<param>child elements with name/expr attributesStatifier.Actions.SendContent: Support for<content>child elements with expr attribute- Expression Evaluation: Dynamic event names, target resolution, and data payload construction
- Internal Event Routing: Events sent to #_internal properly queued and processed in state machine
- Transition Actions: Send elements within
<transition>elements with proper execution order
Enhanced Parser Support: Extended SCXML parser for comprehensive send element parsing
- Send Element Parsing: Complete parsing of
<send>elements with all W3C attributes - Child Element Support: Parsing of nested
<param>and<content>elements - Location Tracking: Precise source location tracking for all send-related elements
- Handler Integration: SAX-based parsing with proper state stack management
- Send Element Parsing: Complete parsing of
ActionExecutor Integration: Enhanced action execution framework with send support
- Transition Action Execution: Added
execute_transition_actions/3for actions within transitions - Proper Action Order: SCXML-compliant action execution (exit → transition → entry)
- Pipeline Programming: Refactored parameter order for better |> operator usage
- Transition Action Execution: Added
StateHierarchy Module Extraction
Statifier.StateHierarchy: 422-line dedicated module extracted from Interpreter for hierarchy operations- 8 Core Functions:
descendant_of?/3,compute_lcca/3,get_ancestor_path/2,get_parallel_ancestors/2, etc. - Reduced Interpreter Size: 824 → 636 lines (23% reduction, 188 lines extracted)
- Single Responsibility: All state hierarchy logic consolidated in focused module
- Comprehensive Testing: 45 new tests covering complex hierarchies, edge cases, parallel regions
- 8 Core Functions:
Hierarchy Caching Infrastructure
Statifier.HierarchyCache: O(1) performance optimization system for expensive hierarchy operations- Pre-computed Relationships: Ancestor paths, LCCA matrix, descendant sets, parallel regions
- Performance Gains: 5-15x speedup for hierarchy operations (O(depth) → O(1))
- Memory Efficient: ~1.5-2x memory overhead for significant performance benefits
- Automatic Building: Cache built during validation phase for valid documents only
- Statistics Tracking: Build time, memory usage, and cache size metrics
Enhanced Document Structure: Extended Document struct with hierarchy_cache field
- Integration with Validation: Cache built in Validator.finalize/2 pipeline
- Helper Functions:
Document.get_all_states/1for comprehensive state enumeration - Benchmark Testing: Performance and memory usage validation with dedicated benchmarks
TransitionResolver Module Extraction
Statifier.Interpreter.TransitionResolver: 161-line focused module extracted from Interpreter- Single Responsibility: Dedicated to SCXML transition conflict resolution and matching
- 6 Core Functions:
find_enabled_transitions/2,find_eventless_transitions/1,resolve_transition_conflicts/2, etc. - SCXML-Compliant: Implements W3C specification for optimal transition set computation
- Comprehensive Testing: 300 lines of tests with 12 test cases covering all scenarios
- Better Maintainability: Reduces Interpreter complexity from 655 to 581 lines (11% reduction)
Changed
API Modernization and Backwards Compatibility
⚠️ BREAKING: Updated all test files to use new 3-tuple
Statifier.parse/2API- Comprehensive Migration: All 857 tests updated to new API format
- Maintained Coverage: All tests continue passing with enhanced API
- Improved Test Clarity: 3-tuple format provides better access to warnings in tests
Streamlined Main Module: Complete rewrite of
/lib/statifier.exwith modern architecture- New Functions:
parse/2andvalidated?/1for comprehensive API coverage - Error Handling: Enhanced error handling with
handle_validation/2helper - Reduced Nesting: Improved code maintainability with better function organization
- Options Integration: Seamless integration with relaxed parsing options
- New Functions:
Code Quality and Performance Improvements
Perfect Credo Compliance: Achieved 0 issues across 863 analyzed modules/functions
- Function Nesting Depth: Fixed all nesting depth violations in StateHierarchy module
- Helper Function Extraction: Added
check_descendant_relationship/3,lookup_lcca_in_matrix/3,normalize_lcca_key/2 - Clean Architecture: Better separation of concerns and improved readability
- Benchmark Test Configuration: Added Credo disable for IO.puts in benchmark tests
Major Interpreter Refactoring: Comprehensive architectural improvements for better maintainability
- Module Extraction Benefits: StateHierarchy, TransitionResolver, and HierarchyCache provide focused functionality
- Performance Optimizations: O(1) hierarchy operations with pre-computed cache infrastructure
- Pipeline Programming: Enhanced parameter ordering for better Elixir |> operator usage
- Action Execution Improvements: Proper SCXML-compliant action execution order and integration
- Future Extensibility: Clean architecture prepared for advanced SCXML features and optimizations
Enhanced Action Execution Architecture
- ActionExecutor Parameter Refactoring: Improved parameter ordering for better Elixir programming patterns
- StateChart First: All execute_*_actions functions now put state_chart as first parameter
- Pipeline Friendly: Better |> operator support for functional programming style
- Transition Actions: New
execute_transition_actions/3function for actions within transitions - Separation of Concerns: Moved transition action execution from Interpreter to ActionExecutor
Fixed
XML Normalization and Location Tracking
- Version Attribute Detection: Fixed regex pattern in
maybe_add_default_version/1for proper version attribute recognition - Location Tracking Preservation: Ensured line number accuracy maintained with optional XML declaration
- Function Signature Conflicts: Resolved parse/1 vs parse/2 function definition conflicts
Test Infrastructure Improvements
- Location Tracking Tests: Updated location-specific tests to include XML declarations for accurate line numbers
- TransitionResolver Integration: Fixed StateChart field name issues and event timing in extracted module tests
- Comprehensive Test Coverage: All 857 tests passing with new architecture and API changes
Technical Improvements
Enhanced Developer Experience
Simplified SCXML Authoring: Relaxed parsing mode eliminates repetitive XML boilerplate
- No XML Declaration Required: Tests can omit
<?xml version="1.0" encoding="UTF-8"?> - No Namespace Required: Automatic W3C SCXML namespace addition
- No Version Required: Automatic version="1.0" addition
- Cleaner Test Documents: Focus on state machine logic rather than XML syntax
- No XML Declaration Required: Tests can omit
Better Error Messages: Enhanced validation error reporting with maintained source location accuracy
Improved API Consistency: Uniform return patterns and option handling across all parsing functions
Comprehensive Documentation: Updated all function documentation with examples and options
Performance and Quality Metrics
- All Quality Gates Pass: Format ✓ Test (857/857) ✓ Credo (0 issues) ✓ Dialyzer ✓
- Comprehensive Test Coverage: 857 total tests with significant new module coverage
- New Test Modules: SendAction (236 lines), StateHierarchy (591 lines), TransitionResolver (313 lines)
- Advanced Testing: Handler (483 lines), StateStack (453 lines), HierarchyCache (524 lines)
- Performance Benchmarks: HierarchyCache benchmarks demonstrate 5-15x performance improvements
- Memory Efficiency: O(1) hierarchy operations with intelligent caching system
- Production Ready: All functionality thoroughly tested with comprehensive edge case coverage
- Architecture Quality: Clean separation of concerns with focused, testable modules
Examples
New Streamlined API
# Modern API - Parse and validate in one step
{:ok, document, warnings} = Statifier.parse(xml)
# Parse without validation for advanced use cases
{:ok, document, []} = Statifier.parse(xml, validate: false)
# Check validation status
validated = Statifier.validated?(document) # true/false
# Skip validation explicitly
{:ok, document, []} = Statifier.parse(xml, validate: false)Relaxed XML Parsing Mode
# Before v1.5.0 - Full XML boilerplate required
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start"/>
</scxml>
"""
# After v1.5.0 - Clean, minimal syntax
xml = """
<scxml initial="start">
<state id="start"/>
</scxml>
"""
{:ok, document, warnings} = Statifier.parse(xml)
# XML declaration, namespace, and version automatically addedXML Declaration Control
# Preserve line numbers (default behavior)
{:ok, document, warnings} = Statifier.parse(minimal_xml)
# Add XML declaration explicitly
{:ok, document, warnings} = Statifier.parse(minimal_xml, xml_declaration: true)Error Handling Examples
# Validation errors with enhanced format
case Statifier.parse(invalid_xml) do
{:ok, document, warnings} ->
# Success with optional warnings
{:error, {:validation_errors, errors, warnings}} ->
# Validation failed with detailed errors
{:error, reason} ->
# Parsing failed
endSend Element Usage
<scxml initial="waiting">
<state id="waiting">
<transition event="start" target="processing">
<!-- Send internal event with data -->
<send target="#_internal" event="process_data">
<param name="userId" expr="'user123'"/>
<param name name="priority" expr="5"/>
<content expr="'Processing started'"/>
</send>
</transition>
</state>
<state id="processing">
<transition event="process_data" target="complete">
<!-- Event data available via _event.data -->
<log expr="'Processing for user: ' + _event.data.userId"/>
</transition>
</state>
<state id="complete"/>
</scxml>Dynamic Send Elements
<state id="router">
<transition event="route_message">
<!-- Dynamic event and target evaluation -->
<send targetexpr="_event.data.target"
eventexpr="_event.data.eventName"
namelist="status priority">
<param name="timestamp" expr="Date.now()"/>
</send>
</transition>
</state>Performance Optimization Examples
# Before v1.5.0 - O(depth) hierarchy operations
time_uncached = benchmark_hierarchy_operations(uncached_document)
# After v1.5.0 - O(1) hierarchy operations with caching
{:ok, cached_document, _warnings} = Statifier.parse(xml)
time_cached = benchmark_hierarchy_operations(cached_document)
# Typical performance improvement: 5-15x speedup
speedup = time_uncached / time_cached # => ~10.5xMigration Guide
API Updates
# Before v1.5.0
{:ok, document} = SCXML.parse(xml)
{:ok, validated_doc, warnings} = Validator.validate(document)
# After v1.5.0 - Streamlined approach
{:ok, document, warnings} = Statifier.parse(xml)Test Simplification
# Before v1.5.0 - Verbose XML
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="idle">
<state id="idle">
<transition event="start" target="running"/>
</state>
<state id="running"/>
</scxml>
"""
# After v1.5.0 - Focus on logic
xml = """
<scxml initial="idle">
<state id="idle">
<transition event="start" target="running"/>
</state>
<state id="running"/>
</scxml>
"""Notes
- Major Release: Comprehensive modernization spanning API, architecture, performance, and new SCXML features
- API Modernization: Complete modernization of parsing and validation API for better developer experience
- Architectural Revolution: Major refactoring with StateHierarchy, TransitionResolver, HierarchyCache, and SendAction modules
- Performance Breakthrough: O(1) hierarchy operations provide 5-15x performance improvements for complex state machines
- SCXML Feature Expansion: Basic send element support enables internal event communication and transition actions
- Quality Excellence: Perfect Credo compliance, comprehensive test coverage (857 tests), and thorough documentation
- Developer Productivity: Significant reduction in XML boilerplate and improved error handling
- Production Ready: Battle-tested architecture with comprehensive edge case coverage and benchmark validation
- Foundation for Future: Clean, extensible architecture prepares for advanced SCXML features (delays, external targets, etc.)
[1.4.0] 2025-08-29
Added
Complete SCXML History State Support
History State Data Model: Full support for SCXML
<history>elements per W3C specificationStatifier.StateExtensions: Addedhistory_typefield (:shallow | :deep) andhistory_type_locationfor validation- Parser Support: Complete parsing of
<history>elements withtype="shallow|deep"attributes - Default Behavior: History type defaults to
:shallowwhen not specified - Element Builder: New
build_history_state/4function for creating history state structures - Location Tracking: Full source location tracking for history elements and attributes
History State Validation: Comprehensive validation per W3C SCXML specification requirements
Statifier.Validator.HistoryStateValidator: Dedicated validator module for all history constraints- Structural Validation: History states cannot be at root level (must have compound/parallel parent)
- Content Validation: History states cannot have child states (pseudo-states only)
- Uniqueness Validation: Only one history state per type (shallow/deep) per parent state
- Type Validation: History type must be valid (
:shallowor:deep) - Target Validation: Default transition targets must exist in document
- Reachability Analysis: Warns if history states are unreachable (no transitions target them)
History Tracking Infrastructure: Complete W3C SCXML compliant history recording and restoration
Statifier.HistoryTracker: Core history state tracking with efficient MapSet operations- Shallow History: Records and restores immediate children of parent state that contain active descendants
- Deep History: Records and restores all atomic descendant states within parent state
- StateChart Integration: History tracking integrated into StateChart lifecycle
- Record History API:
record_history/2,get_shallow_history/2,get_deep_history/2,has_history?/2
History State Resolution: Full W3C SCXML compliant history state transition resolution
- Pseudo-State Handling: History states resolve to stored configuration or default targets (never active themselves)
- Shallow Resolution: Restores immediate children from recorded shallow history
- Deep Resolution: Restores all atomic descendants from recorded deep history
- Default Transitions: Uses history state's default transitions when parent has no recorded history
- Complex Hierarchy Support: Maintains proper state hierarchy during restoration
Multiple Transition Target Support
Space-Separated Target Parsing: SCXML transitions now support multiple targets per W3C specification
- Parser Enhancement: Handles
target="state1 state2 state3"syntax with proper whitespace splitting - Data Model:
Statifier.Transition.targetsfield (list) replacestargetfield (string) - Validator Updates: All transition validators updated for list-based target validation
- Empty Target Support: Empty target lists properly handled for targetless transitions
- Feature Detection: Updated feature detection to recognize multiple target capability
- Parser Enhancement: Handles
Enhanced Parallel State Exit Logic: Critical fix for W3C SCXML parallel state exit semantics
- Exit Set Computation: Proper W3C SCXML exit set calculation for complex parallel hierarchies
- Parallel Ancestor Detection:
get_parallel_ancestors/3identifies all parallel ancestors in hierarchy - Region Identification:
are_in_parallel_regions/3correctly identifies states in different parallel regions - Cross-Boundary Exits:
exits_parallel_region/3detects transitions that exit parallel regions - Comprehensive Exit Logic: All parallel regions properly exited when transitioning to external states
Changed
API Improvements (Breaking Changes)
- ⚠️ BREAKING:
Statifier.Transitionstruct field renamed fromtargettotargetsType Change:
target: String.t() | nil→targets: [String.t()]- Migration: Update pattern matches from
%Transition{target: target}to%Transition{targets: targets} - Benefit: Self-documenting code that clearly indicates list-based target support
- Validation: All existing tests and validators updated for new API
Document Helper Functions
Statifier.DocumentEnhancements: New helper functions for history state runtime managementis_history_state?/2: Check if state has history type with O(1) lookupfind_history_states/2: Find all history states within a parent stateget_history_default_targets/2: Get default transition targets for history state- Optimized Performance: All functions use existing O(1) state_lookup maps
History Integration in Interpreter
- Interpreter History Support: Complete integration of history states into state machine lifecycle
- History Recording: Automatic history recording before onexit actions during state transitions
- W3C Timing Compliance: History recorded "before taking any transition that exits the parent"
- Parent Detection:
find_parents_with_history/2identifies parents needing history recording - Ancestor Analysis:
get_ancestors_with_history/2finds all ancestors with history children - StateChart Parameters: Enhanced interpreter functions to work with StateChart for history access
Fixed
SCION Test Coverage Improvements
History Test Parsing: Fixed critical parser bug where transitions inside
<history>elements weren't being processed- StateStack Fix: Added missing
{"history", parent_state}case inhandle_transition_end/1 - History Default Transitions: History states can now have proper default transitions
- SCION History Tests: 5/8 SCION history tests now passing (62.5% success rate, up from 12.5%)
- Test Results: history0, history1, history2, history3, history6 now pass
- StateStack Fix: Added missing
Parallel State Exit Logic: Resolved critical parallel state exit semantics issues
- Cross-Region Transitions: Fixed transitions from parallel regions to external states
- Exit Set Calculation: Proper W3C SCXML exit set computation for complex hierarchies
- SCION Test Fixes: Multiple SCION history tests (history4b, history5) now pass completely
- Regression Protection: All 118 regression tests continue to pass
Feature Detection Updates
- History State Support: Updated
FeatureDetectorto mark:history_statesas:supported - Multiple Target Support: Enhanced feature detection for multiple transition targets
- Test Infrastructure: 12 history state tests (8 SCION + 4 W3C) now properly validated
Technical Improvements
Test Infrastructure
- Comprehensive Test Coverage: 707 total tests with enhanced history state coverage
- New Test Organization: Created
test/statifier/history/folder for organized history testing - History Test Suite: 15+ dedicated history tests covering all scenarios (recording, restoration, validation)
- Integration Tests: End-to-end testing of history states with complex state hierarchies
- Regression Tests: All 118 regression tests continue passing with new functionality
- New Test Organization: Created
Code Quality
- Credo Compliance: All static analysis issues resolved across the codebase
- Pattern Matching: Enhanced pattern matching for cleaner, more readable code
- Type Safety: Full typespec coverage for all new history state functionality
- Documentation: Comprehensive documentation for all new modules and functions
- Performance: Maintained O(1) lookups with efficient MapSet operations for history tracking
W3C SCXML Compliance
- History State Specification: Full compliance with W3C SCXML 1.0 history state requirements
- Parallel State Semantics: Proper W3C exit set computation and parallel region handling
- Multiple Target Support: Compliant with W3C SCXML multiple target syntax
- Pseudo-State Handling: Correct implementation of history as non-active pseudo-states
- Default Transition Logic: Proper handling of history default transitions per specification
Examples
History State Usage
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="main">
<state id="main" initial="sub1">
<!-- Shallow history - restores immediate children -->
<history id="main_hist" type="shallow">
<transition target="sub1"/> <!-- Default when no history -->
</history>
<state id="sub1">
<transition event="go" target="sub2"/>
</state>
<state id="sub2">
<transition event="go" target="sub3"/>
</state>
<state id="sub3">
<transition event="exit" target="other"/>
<transition event="back" target="main_hist"/> <!-- Restore history -->
</state>
</state>
<state id="other">
<transition event="return" target="main_hist"/> <!-- Restore to last sub-state -->
</state>
</scxml>Deep History Example
<parallel id="game">
<!-- Deep history - restores all atomic descendants -->
<history id="game_hist" type="deep">
<transition target="level1"/> <!-- Default: start at level 1 -->
</history>
<state id="progress" initial="level1">
<state id="level1">
<state id="checkpoint1"/>
<state id="checkpoint2"/>
</state>
<state id="level2">
<state id="checkpoint3"/>
<state id="checkpoint4"/>
</state>
</state>
<state id="inventory" initial="empty">
<state id="empty"/>
<state id="sword"/>
<state id="shield"/>
</state>
</parallel>Multiple Target Transitions
<state id="source">
<!-- Multiple targets - enter multiple states simultaneously -->
<transition event="activate" target="target1 target2 target3"/>
</state>
<parallel id="system">
<state id="target1"/>
<state id="target2"/>
<state id="target3"/>
</parallel>Programmatic History Usage
# Check if state is a history state
Document.is_history_state?(document, "main_hist") # true
# Find all history states in a parent
history_states = Document.find_history_states(document, "main")
# Get default targets for history state
defaults = Document.get_history_default_targets(document, "main_hist")
# Record and retrieve history
state_chart = StateChart.record_history(state_chart, "main")
shallow_history = StateChart.get_shallow_history(state_chart, "main")
deep_history = StateChart.get_deep_history(state_chart, "main")Migration Guide
Transition Target API
# Before v1.4.0
%Transition{target: "state1"}
%Transition{target: nil} # targetless
# After v1.4.0
%Transition{targets: ["state1"]}
%Transition{targets: []} # targetless
# Pattern matching migration
case transition do
%Transition{target: nil} -> # targetless
%Transition{target: target} -> # has target
end
# becomes
case transition do
%Transition{targets: []} -> # targetless
%Transition{targets: targets} -> # has targets
endNotes
- History State Foundation: Complete foundation for SCXML history states established
- W3C Compliance: Full compliance with W3C SCXML 1.0 history state specification
- Multiple Target Support: Enhanced SCXML transition capability per specification
- Parallel State Fixes: Critical parallel state exit logic issues resolved
- Test Coverage: Comprehensive test coverage maintained (91.8% overall)
- Production Ready: All functionality thoroughly tested and validated
- SCION Progress: Significant improvement in SCION history test compliance
[1.3.0] 2025-08-27
Added
Core Logging Infrastructure
Flexible Protocol-Based Logging System: Complete logging architecture for state chart operations
Statifier.Logging.AdapterProtocol: Extensible logging backend interface withlog/5andenabled?/2functionsStatifier.Logging.ElixirLoggerAdapter: Production logging adapter that integrates with Elixir's Logger systemStatifier.Logging.TestAdapter: In-memory log storage adapter for clean test environmentsStatifier.Logging.LogManager: Central coordination module with automatic metadata extraction- Log Level Hierarchy: Complete support for
:trace,:debug,:info,:warn,:errorlevels with filtering
Automatic Metadata Extraction: StateChart context automatically added to all log messages
- Current State Tracking: Active states automatically included in log metadata
- Event Context: Current event name automatically included when available
- Custom Metadata Support: Additional metadata can be provided per log message
- Metadata Precedence: Custom metadata takes precedence over automatic extraction
Advanced Memory Management: Circular buffer support for bounded log storage
- Configurable Limits: TestAdapter supports optional
max_entriesfor memory-bounded logging - Circular Buffer Behavior: Automatically removes oldest entries when limit exceeded
- Unlimited Storage: Optional unlimited log storage for comprehensive test coverage
- Helper Functions:
get_logs/1,2,clear_logs/1for test log inspection and management
- Configurable Limits: TestAdapter supports optional
StateChart Integration: Enhanced StateChart structure with logging capabilities
- Logging Fields: Added
log_adapter,log_level, andlogsfields to StateChart struct - Configuration Helpers:
configure_logging/3andset_log_level/2functions for easy setup - Seamless Integration: Logging works with existing StateChart lifecycle and event processing
- Logging Fields: Added
Logging Configuration System
Enhanced
Interpreter.initialize/2: Comprehensive logging configuration support during state chart initialization- Runtime Configuration Options: Accept
:log_adapterand:log_leveloptions via keyword list - Adapter Configuration Flexibility: Support for direct adapter structs or
{Module, opts}tuples - Backward Compatibility: Existing
initialize/1calls continue to work with sensible defaults - Comprehensive Documentation: Detailed examples and usage patterns in function documentation
- Runtime Configuration Options: Accept
Centralized Configuration Logic: All configuration logic consolidated in
LogManager.configure_from_options/2- Configuration Precedence: Runtime options > Application config > Environment defaults
- Application Configuration Support: Integration with
Application.get_env/3for system-wide settings - Robust Error Handling: Graceful fallback to ElixirLoggerAdapter on invalid configurations
- Validation and Safety: Comprehensive configuration validation with detailed error messages
Production-Ready Defaults: Sensible defaults that work across all environments
- ElixirLoggerAdapter Default: Always the base default for robust logging in all environments
- Test Environment Configuration: TestAdapter configured via
test_helper.exsfor clean test output - Flexible Fallback Strategy: Invalid configurations always fall back to most robust adapter
- Environment Independence: No dependency on
Mix.env()or custom environment detection
Comprehensive Configuration Testing: 12 dedicated tests covering all configuration scenarios
- Runtime Configuration Tests: Verification of all option types and combinations
- Application Configuration Tests: Testing precedence and override behavior
- Error Handling Tests: Validation of graceful fallback for invalid configurations
- Integration Tests: End-to-end testing of configuration system with state chart initialization
Examples
Core Logging Infrastructure
# Configure logging with TestAdapter for testing
adapter = %Statifier.Logging.TestAdapter{max_entries: 100}
state_chart = StateChart.configure_logging(state_chart, adapter, :debug)
# Configure logging with ElixirLoggerAdapter for production
adapter = %Statifier.Logging.ElixirLoggerAdapter{}
state_chart = StateChart.configure_logging(state_chart, adapter, :info)
# Log messages with automatic metadata extraction
state_chart = LogManager.info(state_chart, "Processing started", %{action_type: "initialization"})
state_chart = LogManager.error(state_chart, "Validation failed", %{field: "email"})
# Inspect captured logs in tests
logs = TestAdapter.get_logs(state_chart)
error_logs = TestAdapter.get_logs(state_chart, :error)
state_chart = TestAdapter.clear_logs(state_chart)Production Logging Integration
# Initialize state chart with production logging
{:ok, state_chart} = Interpreter.initialize(document)
adapter = %Statifier.Logging.ElixirLoggerAdapter{}
state_chart = StateChart.configure_logging(state_chart, adapter, :info)
# All state chart operations now include automatic logging
{:ok, state_chart} = Interpreter.send_event(state_chart, event)
# Logs: [info] Processing event "start" current_state=["idle"] event="start"Test Environment Usage
defmodule MyStateMachineTest do
use ExUnit.Case
test "validates error logging during processing" do
adapter = %Statifier.Logging.TestAdapter{max_entries: 50}
state_chart = StateChart.configure_logging(state_chart, adapter, :debug)
# ... perform operations that should log ...
# Verify specific log messages were captured
logs = TestAdapter.get_logs(state_chart)
assert [%{level: :error, message: "Validation failed"}] = logs
# Check metadata extraction
assert logs |> hd() |> Map.get(:metadata) |> Map.get(:current_state) == ["processing"]
end
endLogging Configuration System
# Use default configuration (ElixirLoggerAdapter, :info level)
{:ok, state_chart} = Interpreter.initialize(document)
# Configure with runtime options
{:ok, state_chart} = Interpreter.initialize(document, [
log_adapter: {TestAdapter, [max_entries: 50]},
log_level: :debug
])
# Configure with direct adapter struct
adapter = %TestAdapter{max_entries: 100}
{:ok, state_chart} = Interpreter.initialize(document,
log_adapter: adapter,
log_level: :trace
)
# Configure via application environment (in config files or test_helper.exs)
Application.put_env(:statifier, :default_log_adapter, {TestAdapter, [max_entries: 200]})
Application.put_env(:statifier, :default_log_level, :warn)
{:ok, state_chart} = Interpreter.initialize(document) # Uses application configConfiguration Precedence Examples
# Application configuration
Application.put_env(:statifier, :default_log_adapter, {TestAdapter, [max_entries: 300]})
Application.put_env(:statifier, :default_log_level, :error)
# Runtime options override application config
{:ok, state_chart} = Interpreter.initialize(document, [
log_adapter: {ElixirLoggerAdapter, []}, # Overrides TestAdapter
log_level: :info # Overrides :error
])
# Invalid configurations fall back gracefully
{:ok, state_chart} = Interpreter.initialize(document, [
log_adapter: {NonExistentModule, []} # Falls back to ElixirLoggerAdapter
])Changed
Logger to LogManager Migration
Centralized Logging Architecture: Migrated all existing
Logger.*calls throughout the codebase to use the newLogManager.*API- ActionExecutor: All debug logging now uses
LogManager.debugwith structured metadata (action_type, state_id, phase, etc.) - LogAction: Replaced
Logger.infowithLogManager.info, now returns updated StateChart from logging operations - RaiseAction: Migrated
Logger.infotoLogManager.infowith event metadata, properly threads StateChart through logging calls - AssignAction: Updated
Logger.errortoLogManager.errorwith comprehensive error context and assignment details - Datamodel: Replaced
Logger.debugcalls withLogManager.debugfor expression evaluation failures
- ActionExecutor: All debug logging now uses
Structured Logging Enhancement: All LogManager calls now include appropriate context-specific metadata
- Action Context: Debug logs include action_type, state_id, and execution phase information
- Error Context: Error logs include detailed failure information, locations, and expressions
- Event Context: Event-related logs include event names and metadata
- Expression Context: Expression evaluation logs include compiled expressions and error details
StateChart Threading: Actions now properly return updated StateChart instances from logging operations
- Consistent Return Values: All action modules maintain StateChart consistency through logging calls
- State Preservation: Logging operations preserve and return the complete StateChart state
- Queue Management: Internal and external event queues remain intact through logging operations
Log Storage Optimization
Chronological Log Ordering: TestAdapter now stores logs in intuitive chronological order (oldest first, newest last)
- Natural Reading Order: Logs now appear in the order they were created for easier debugging
- Standard Behavior: Aligns with typical logging system expectations and developer intuitions
- Improved Test Assertions:
assert_log_ordernow uses ascending index order for cleaner test logic
Memory Management: Updated circular buffer behavior to maintain chronological ordering
- FIFO Behavior: When max_entries limit is reached, oldest entries are removed first
- Append Operations: New log entries are appended to maintain chronological sequence
- Backward Compatibility: API remains unchanged while improving internal behavior
Test Infrastructure Enhancements
StateChart Log Integration: All action tests now use StateChart logs instead of
capture_logfor verification- Helper Functions: Added
test_state_chart()helper for properly configured StateChart instances - Log Assertions: Created
assert_log_entry()andassert_log_order()helpers for clean log verification - Configuration Helpers: Added
create_configured_state_chart()helpers to reduce test duplication
- Helper Functions: Added
Test Coverage Maintenance: Maintained 91.2% test coverage with comprehensive log assertion coverage
- Regression Protection: All 108 regression tests continue passing with new logging infrastructure
- Action Coverage: Complete test coverage for all action logging behaviors
- Error Handling: Comprehensive test coverage for logging error scenarios
[1.2.0] 2025-08-27
Added
If/Else/ElseIf Conditional Action Support
<if>Action Support: Full implementation of SCXML<if>elements with conditional executionStatifier.Actions.IfActionStruct: Represents if/elseif/else conditional blocks- Nested Action Execution: Supports multiple actions within each conditional block
- Expression Evaluation: Uses Statifier.Evaluator for condition evaluation
- ActionExecutor Integration: Seamlessly integrates with existing action execution framework
- Complex Conditionals: Support for if/elseif/else chains with proper precedence
Parser Extensions for Conditional Actions: Extended SCXML parser to handle conditional elements
- If/ElseIf/Else Parsing: Complete parsing support for conditional action blocks
- StateStack Integration: Proper conditional block handling in parsing state stack
- Mixed Action Support: Parse conditional actions alongside log/raise/assign actions
- Location Tracking: Complete source location tracking for debugging conditional blocks
Test Coverage Improvements
90.8% Overall Coverage: Comprehensive test coverage improvements through targeted edge case testing
- StateStack Coverage: Improved from 72.7% to 95.8% (+23.1% - biggest impact module)
- ActionExecutor Edge Cases: Added comprehensive error handling and edge case tests
- Interpreter Coverage: Added simple edge case tests avoiding duplication with existing functionality
- Handler Coverage: Added unknown element handling and parsing edge case tests
- 4 New Test Files: Comprehensive coverage tests for critical modules
Enhanced LogAction: Improved string evaluation and error handling
- Evaluator Integration: Uses Statifier.Evaluator for consistent expression handling
- String Validation: Proper Unicode string validation and safe logging
- Fallback Parsing: Graceful fallback for quoted string parsing
- Error Recovery: Continues execution even with invalid expressions
Architecture Improvements
Unified
Statifier.EvaluatorModule: ConsolidatedConditionEvaluatorandValueEvaluatorinto single module- Single Entry Point: One module for all expression evaluation (conditions and values)
- Improved Maintainability: Eliminated code duplication between evaluator modules
- Future Extensibility: Better prepared for pluggable datamodel architectures (ECMAScript, XPath)
- Consistent API: Unified function signatures and error handling patterns
Enhanced
Statifier.DatamodelModule: Improved data model operations and separation of concernsput_in_path/3Function: Moved from Evaluator to Datamodel for better architectureImproved Error Handling: Returns
{:ok, result} | {:error, reason}instead of raising exceptions- Type Safety: Proper
Datamodel.t()typing throughout the codebase - Data Model Operations: Centralized location for all data model manipulation logic
Feature Detection Updates: Enhanced SCXML feature tracking for better test validation
- Datamodel Support: Marked
:datamodeland:data_elementsas:supportedin feature registry - Accurate Test Results: Prevents false test failures from unsupported feature detection
- Better Compliance Tracking: Improved visibility into SCXML feature implementation status
- Datamodel Support: Marked
Changed
Action Execution Architecture
ActionExecutor Delegation Pattern: Improved action execution through proper delegation
- Public
execute_single_action/2: Made function public for IfAction integration - Action Delegation: ActionExecutor now properly delegates to action.execute/2 methods
- Centralized Execution: All actions now execute through consistent ActionExecutor interface
- Better Separation of Concerns: Each action type handles its own execution logic
- Public
Code Quality Improvements: Enhanced code maintainability and compliance
- Zero Credo Issues: All static analysis issues resolved across the codebase
- Unused Variable Cleanup: Fixed unused variable warnings in Handler and ElementBuilder
- Alias Ordering: Proper alphabetical alias ordering in all test files
- Clean Validation Pipeline: All steps pass - format ✓ test ✓ credo ✓ dialyzer ✓
Test Coverage Improvements
13 New Passing Tests: Unlocked additional test coverage through datamodel improvements
- 9 SCION Tests: Including assign actions, current small step assignments, data initialization
- 4 W3C Tests: Including executable content evaluation, foreach loops, conditional execution
- Test Categories:
assign/,assign_current_small_step/,data/,foreach/,if_else/ - Overall Progress: Improved from 48/184 to 61/184 total tests passing (33% compliance)
Updated Test Baseline: Added new passing tests to regression test suite
- SCION Tests: 44 → 53 passing tests
- W3C Tests: 5 → 9 passing tests
- Maintained Quality: All 98 regression tests continue to pass
Examples
If/Else/ElseIf Conditional Actions
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start">
<onentry>
<assign location="score" expr="85"/>
<if cond="score >= 90">
<assign location="grade" expr="'A'"/>
<log label="grade" expr="'Excellent work!'"/>
<elseif cond="score >= 80"/>
<assign location="grade" expr="'B'"/>
<log label="grade" expr="'Good job!'"/>
<elseif cond="score >= 70"/>
<assign location="grade" expr="'C'"/>
<log label="grade" expr="'Satisfactory'"/>
<else/>
<assign location="grade" expr="'F'"/>
<log label="grade" expr="'Needs improvement'"/>
</if>
</onentry>
</state>
</scxml>Nested Conditional Logic
<state id="processing">
<onentry>
<if cond="user.authenticated">
<if cond="user.role == 'admin'">
<assign location="permissions" expr="'full'"/>
<raise event="admin_access"/>
<else/>
<assign location="permissions" expr="'limited'"/>
<raise event="user_access"/>
</if>
<else/>
<assign location="permissions" expr="'none'"/>
<raise event="auth_required"/>
</if>
</onentry>
</state>Mixed Actions with Conditionals
<state id="validation">
<onentry>
<log label="status" expr="'Starting validation'"/>
<assign location="errors" expr="[]"/>
<if cond="data.email == null">
<assign location="errors[0]" expr="'Email required'"/>
</if>
<if cond="data.age < 18">
<assign location="errors[1]" expr="'Must be 18 or older'"/>
</if>
<if cond="errors.length > 0">
<raise event="validation_failed"/>
<else/>
<raise event="validation_passed"/>
</if>
</onentry>
</state>[1.1.0] 2025-08-26
Added
Phase 1 Enhanced Expression Evaluation
Predicator v3.0 Integration: Upgraded from v2.0 to v3.0 with enhanced capabilities
- Enhanced Nested Property Access: Deep dot notation support (
user.profile.settings.theme) - Mixed Access Patterns: Combined bracket/dot notation (
users['john'].active) - Context Location Resolution: New
context_location/2function for assignment path validation - Value Evaluation: Non-boolean expression evaluation for actual data values
- Type-Safe Operations: Improved type coercion and error handling
- Graceful Fallback: Returns
:undefinedfor missing properties instead of errors
- Enhanced Nested Property Access: Deep dot notation support (
Statifier.ValueEvaluatorModule: Comprehensive value evaluation system for SCXML expressions- Expression Compilation:
compile_expression/1for reusable expression compilation - Value Evaluation:
evaluate_value/2extracts actual values (not just boolean results) - Location Path Resolution:
resolve_location/1,2validates assignment paths using predicator v3.0 - Safe Assignment:
assign_value/3performs type-safe nested data model updates - Integrated Assignment:
evaluate_and_assign/3combines evaluation and assignment - SCXML Context Support: Full integration with state machine context (events, configuration, datamodel)
- Error Handling: Comprehensive error handling with detailed logging
- Expression Compilation:
<assign>Element Support: Full W3C SCXML assign element implementationStatifier.Actions.AssignActionStruct: Represents assign actions with location and expr attributes- Location-Based Assignment: Validates assignment paths before execution
- Expression Evaluation: Uses Statifier.ValueEvaluator for complex expression processing
- Nested Property Assignment: Supports deep assignment (
user.profile.name = "John") - Mixed Notation Support: Handles both dot and bracket notation in assignments
- Context Integration: Access to current event data and state configuration
- Error Recovery: Graceful error handling with logging, continues execution on failures
- Action Integration: Seamlessly integrates with existing action execution framework
StateChart Data Model Enhancement
- Datamodel Storage: Added
datamodelfield toStatifier.StateChartfor variable persistence - Current Event Context: Added
current_eventfield for expression evaluation context - Helper Methods:
update_datamodel/2andset_current_event/2for state management - SCXML Context Building: Enhanced context building for comprehensive expression evaluation
Parser Extensions
- Assign Element Parsing: Extended SCXML parser to handle
<assign>elements- Element Builder:
build_assign_action/4creates AssignAction structs with location tracking - Handler Integration: Added assign element start/end handlers
- StateStack Integration:
handle_assign_end/1properly collects assign actions - Mixed Action Support: Parse assign actions alongside log/raise actions in onentry/onexit
- Location Tracking: Complete source location tracking for debugging
- Element Builder:
Feature Detection Updates
- Assign Elements Support: Updated
assign_elementsfeature status to:supported - Feature Registry: Enhanced feature detection for new capabilities
- Test Infrastructure: Tests now recognize assign element capability
Changed
Dependency Updates
- predicator: Upgraded from
~> 2.0to~> 3.0(major version upgrade)- Breaking Change: Enhanced property access semantics
- Migration: Context keys with dots now require nested structure (e.g.,
%{"user" => %{"email" => "..."}}instead of%{"user.email" => "..."}) - Benefit: More powerful and flexible data access patterns
Technical Improvements
- Test Coverage: Maintained 92.9% overall code coverage with comprehensive new tests
- New Test Modules: Statifier.ValueEvaluatorTest, Statifier.Actions.AssignActionTest, Statifier.Parser.AssignParsingTest
- 556 Total Tests: All tests pass including new assign functionality
- Log Capture: Added
@moduletag capture_log: truefor clean test output
- Performance: O(1) lookups maintained with new data model operations
- Error Handling: Enhanced error handling and logging throughout assign operations
- Code Quality: Maintained Credo compliance with proper alias ordering
Examples
Basic Assign Usage
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start">
<onentry>
<assign location="userName" expr="'John Doe'"/>
<assign location="counter" expr="42"/>
<assign location="user.profile.name" expr="'Jane Smith'"/>
</onentry>
<transition target="working"/>
</state>
<state id="working">
<onentry>
<assign location="counter" expr="counter + 1"/>
<assign location="status" expr="'processing'"/>
</onentry>
</state>
</scxml>Mixed Notation Assignment
<onentry>
<assign location="users['admin'].active" expr="true"/>
<assign location="settings.theme" expr="'dark'"/>
<assign location="counters[0]" expr="counters[0] + 1"/>
</onentry>Event Data Assignment
<state id="processing">
<onentry>
<assign location="lastEvent" expr="_event.name"/>
<assign location="eventData" expr="_event.data.value"/>
</onentry>
</state>Programmatic Usage
# Value evaluation
{:ok, compiled} = Statifier.ValueEvaluator.compile_expression("user.profile.name")
{:ok, "John Doe"} = Statifier.ValueEvaluator.evaluate_value(compiled, context)
# Location validation
{:ok, ["user", "settings", "theme"]} = Statifier.ValueEvaluator.resolve_location("user.settings.theme")
# Combined evaluation and assignment
{:ok, updated_model} = Statifier.ValueEvaluator.evaluate_and_assign("result", "count * 2", context)Notes
- Phase 1 Complete: Enhanced Expression Evaluation phase is fully implemented
- Foundation for Phase 2: Data model and expression evaluation infrastructure ready
- Backward Compatible: All existing functionality preserved
- Production Ready: Comprehensive test coverage and error handling
- SCION Progress:
assign_elementsfeature now supported (awaiting Phase 2 for full datamodel tests)
[1.0.0] - 2025-08-23
Added
Phase 1 Executable Content Support
<log>Action Support: Full implementation of SCXML<log>elements with expression evaluationStatifier.LogActionStruct: Represents log actions with label and expr attributes- Expression Evaluation: Basic literal expression support (full evaluation in Phase 2)
- Logger Integration: Uses Elixir Logger for output with contextual information
- Location Tracking: Complete source location tracking for debugging
<raise>Action Support: Complete implementation of SCXML<raise>elements for internal event generationStatifier.RaiseActionStruct: Represents raise actions with event attribute- Event Generation: Logs raised events (full event queue integration in future phases)
- Anonymous Events: Handles raise elements without event attributes
<onentry>and<onexit>Action Support: Executable content containers for state transitions- Action Collection: Parses and stores multiple actions within onentry/onexit blocks
- Mixed Actions: Support for combining log, raise, and future action types
- State Integration: Actions stored in Statifier.State struct with onentry_actions/onexit_actions fields
- Action Execution Infrastructure: Comprehensive system for executing SCXML actions
Statifier.ActionExecutorModule: Centralized action execution with phase tracking- Interpreter Integration: Actions executed during state entry/exit in interpreter lifecycle
- Type Safety: Pattern matching for different action types with extensibility
Test Infrastructure Improvements
- Required Features System: Automated test tagging system for feature-based test exclusion
@tag required_features:annotations on all W3C and SCION tests- Feature Detection Integration: Tests automatically excluded if required features unsupported
- 262 Tests Tagged: Comprehensive coverage of W3C and SCION test requirements
- Maintainable System: Script-based tag updates for easy maintenance
Eventless/Automatic Transitions
- Eventless Transitions: Full W3C SCXML support for transitions without event attributes that fire automatically
- Automatic Transition Processing: Microstep loop processes chains of eventless transitions until stable configuration
- Cycle Detection: Prevents infinite loops with configurable iteration limits (100 iterations default)
- Parallel Region Preservation: Proper SCXML semantics for transitions within and across parallel regions
- Conflict Resolution: Child state transitions take priority over ancestor transitions per W3C specification
Enhanced Parallel State Support
- Parallel State Transitions: Fixed regression where transitions within parallel regions affected unrelated parallel regions
- Cross-Parallel Boundaries: Proper exit semantics when transitions cross parallel region boundaries
- SCXML Exit State Calculation: Implements correct W3C exit set computation for complex state hierarchies
- Sibling State Management: Automatic exit of parallel siblings when transitions leave their shared parent
Fixed
- Regression Test: Fixed parallel state test failure (
test/scion_tests/more_parallel/test1_test.exs) - SCION Test Suite: All 4
cond_jstests now pass (previously 3/4) - Parallel Interrupt Tests: Fixed 6 parallel interrupt test failures in regression suite
- Code Quality: Resolved all
mix credo --strictissues (predicate naming, unused variables, aliases) - Pattern Matching Refactoring: Converted Handler module case statements to idiomatic Elixir pattern matching
handle_event(:end_element, ...)Function: Refactored to separate function clauses with pattern matchingdispatch_element_start(...)Function: Converted from case statement to pattern matching function clauses- StateStack Module: Applied same pattern matching refactoring to action handling functions
Changed (Breaking)
ActionExecutor API Modernization
- REMOVED:
Statifier.Actions.ActionExecutor.execute_onentry_actions/2function clause that accepted%Document{}as second parameter - REMOVED:
Statifier.Actions.ActionExecutor.execute_onexit_actions/2function clause that accepted%Document{}as second parameter - BREAKING: These functions now only accept
%StateChart{}as the second parameter for proper event queue integration - Migration: Replace
ActionExecutor.execute_*_actions(states, document)withActionExecutor.execute_*_actions(states, state_chart) - Benefit: Action execution now properly integrates with the StateChart event queue system, enabling raised events to be processed correctly
Technical Improvements
- SCXML Terminology Alignment: Updated codebase to use proper SCXML specification terminology
- Microstep/Macrostep Processing: Execute microsteps (single transition sets) until stable macrostep completion
- Exit Set Computation: Implements W3C SCXML exit set calculation algorithm for proper state exit semantics
- LCCA Computation: Full Least Common Compound Ancestor algorithm for accurate transition conflict resolution
- NULL Transitions: Added SCXML specification references while maintaining "eventless transitions" terminology
- Feature Detection: Enhanced feature registry with newly supported capabilities
- Added
eventless_transitions: :supportedto feature registry - Added
log_elements: :supportedfor log action support - Added
raise_elements: :supportedfor raise action support - Maintained
onentry_actions: :supportedandonexit_actions: :supported** status
- Added
- Performance: Optimized ancestor/descendant lookup using existing parent attributes
- Test Coverage: Comprehensive testing across all new functionality
- Total Tests: 461 tests (up from 444), including extensive executable content testing
- New Test Files: 13 comprehensive test files for log/raise actions and execution
- Coverage Improvement: Interpreter module coverage increased from 70.4% to 83.0%
- Project Coverage: Overall coverage improved from 89.0% to 92.3% (exceeds 90% minimum requirement)
- Regression Testing: All core functionality tests pass with no regressions
[0.1.0] - 2025-08-20
Added
Core SCXML Implementation
- W3C SCXML Parser: Full XML parser supporting SCXML 1.0 specification
- State Machine Interpreter: Synchronous, functional API for state chart execution
- State Configuration Management: Efficient tracking of active states with O(1) lookups
- Event Processing: Support for internal and external events with proper queueing
- Document Validation: Comprehensive validation with detailed error reporting
SCXML Elements Support
<scxml>: Root element with version, initial state, and namespace support<state>: Compound and atomic states with nested hierarchy<initial>: Initial state pseudo-states for deterministic startup<transition>: Event-driven transitions with conditions and targets<data>: Data model elements for state machine variables
Conditional Expressions
condAttribute: Full support for conditional expressions on transitions- Predicator Integration: Secure expression evaluation using predicator library v2.0.0
- SCXML
In()Function: W3C-compliant state checking predicate - Logical Operations: Support for AND, OR, NOT, and comparison operators
- Event Data Access: Conditions can access current event name and payload
- Error Handling: Invalid expressions gracefully handled per W3C specification
- Modern Functions API: Uses Predicator v2.0's improved custom functions approach
Performance Optimizations
- Parse-time Compilation: Conditional expressions compiled once during parsing
- O(1) State Lookups: Fast state and transition resolution using hash maps
- Document Order Processing: Deterministic transition selection
- Memory Efficient: Minimal memory footprint with optimized data structures
Developer Experience
- Comprehensive Testing: 426+ test cases covering all functionality
- Integration Tests: End-to-end testing with real SCXML documents
- Type Safety: Full Elixir typespec coverage for all public APIs
- Documentation: Detailed module and function documentation
- Error Messages: Clear, actionable error reporting with location information
Validation & Quality
- State ID Validation: Ensures unique and valid state identifiers
- Transition Validation: Validates target states exist and are reachable
- Initial State Validation: Enforces SCXML initial state constraints
- Reachability Analysis: Identifies unreachable states in state charts
- Static Analysis: Credo-compliant code with strict quality checks
Test Coverage
- W3C Compliance: Support for W3C SCXML test cases (excluded by default)
- SCION Compatibility: Integration with SCION test suite for validation
- Unit Tests: Comprehensive unit testing of all modules
- Integration Tests: Real-world SCXML document processing
- Regression Tests: Critical functionality protection
Dependencies
- saxy ~> 1.6: Fast XML parser for SCXML document processing
- predicator ~> 2.0: Secure conditional expression evaluation (upgraded to v2.0 with improved custom functions API)
- credo ~> 1.7: Static code analysis (dev/test)
- dialyxir ~> 1.4: Static type checking (dev/test)
- excoveralls ~> 0.18: Test coverage analysis (test)
Technical Specifications
- Elixir: Requires Elixir ~> 1.17
- OTP: Compatible with OTP 26+
- Architecture: Functional, immutable state machine implementation
- Concurrency: Thread-safe, stateless evaluation
- Memory: Efficient MapSet-based state tracking
Examples
Basic State Machine
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="idle">
<state id="idle">
<transition event="start" target="working"/>
</state>
<state id="working">
<transition event="finish" target="done"/>
</state>
<state id="done"/>
</scxml>Conditional Transitions
<state id="validation">
<transition event="submit" cond="score > 80" target="approved"/>
<transition event="submit" cond="score >= 60" target="review"/>
<transition event="submit" target="rejected"/>
</state>SCXML In() Function
<state id="processing">
<transition event="check" cond="In('processing') AND progress > 50" target="almost_done"/>
<transition event="check" target="continue_working"/>
</state>Usage
# Parse SCXML document
{:ok, document} = Statifier.Parser.SCXML.parse(scxml_string)
# Initialize state machine
{:ok, state_chart} = Statifier.Interpreter.initialize(document)
# Send events
event = %Statifier.Event{name: "start", data: %{}}
{:ok, new_state_chart} = Statifier.Interpreter.send_event(state_chart, event)
# Check active states
active_states = new_state_chart.configuration.active_statesNotes
- This is the initial release of the Statifier SCXML library
- Full W3C SCXML 1.0 specification compliance for supported features
- Production-ready with comprehensive test coverage
- Built for high-performance state machine processing in Elixir applications
- Uses Predicator v2.0 with modern custom functions API (no global function registry)