moon-fsm

An auditable, type-safe workflow state machine library for MoonBit.

moon add Rz-coder8848/moon-fsm@0.3.0
Download zip
Version
0.3.0
License
Apache-2.0
Last updated
7 hours ago
Downloads
20
README

#MoonBit-FSM

moon-fsm is an auditable finite state machine component for MoonBit projects that need workflow-style state progression without adopting a full BPM runtime. The remediation release for OSC2026 focuses on reviewable engineering value: structured transition errors, context-updating actions, execution history, validator reports, workflow-oriented examples, and reproducible CI.

#Package Identity

#Why This Library

Typical uses for this package include:

  • approval and review workflows
  • device and UI state orchestration
  • agent or simulation state control
  • teaching and documenting transition-heavy business rules

The library keeps the public surface small while making the transition path auditable in code review and acceptance review.

#Re-Review Capabilities

  • Typed builder API with guarded transitions and transition actions.
  • Structured runtime errors via try_send(event) -> Result[Unit, TransitionError].
  • Compatibility layer via send(event) -> Result[Unit, String].
  • Transition history through history().
  • Checkpoint and rollback through checkpoint() and restore(snapshot).
  • Ordered best-effort event batches through try_send_all(events), with per-event outcomes and explicit compensation support.
  • Execution metrics and an audit log for accepted and rejected attempts.
  • Validator reports for unreachable states, dead ends, duplicate transitions, and states without outgoing edges.
  • Mermaid export with guard and action annotations.
  • Lifecycle hook coverage with explicit tests for on_enter / on_exit.
  • Four-domain, 32-case benchmark corpus covering approval, order, device, and support workflows with deterministic expected outcomes.
  • Boundary regression tests for empty machines, dead ends, blocked guards, unknown events, duplicate definitions, history ordering, and empty exports.
  • Runnable workflow examples and acceptance-oriented CI.

#Install

moon add Rz-coder8848/moon-fsm

#Minimal Example

let builder : @fsm.Builder[String, String, Int] = @fsm.Builder::new()
.transition_do("Draft", "Submit", "Review", fn(_s, _e, ctx) { ctx + 1 })
.transition_if_do(
"Review",
"Approve",
"Approved",
fn(_s, _e, ctx) { ctx >= 2 },
fn(_s, _e, ctx) { ctx + 10 },
)

let engine = builder.build("Draft", 1)
ignore(engine.try_send("Submit"))

#Workflow Example

The remediation release adds a composite approval workflow example:

stateDiagram-v2 Draft --> Review.Pending : Submit [action] Review.Pending --> Review.Approved : Approve [guard] [action] Review.Pending --> Review.Rework : RequestChanges Review.Pending --> Cancelled : Cancel Review.Pending --> Review.Rejected : Reject Review.Rework --> Review.Pending : Resubmit [action] Review.Rework --> Cancelled : Cancel Review.Rejected --> Error.Validation : Escalate Review.Approved --> Closed : Archive

The expanded order workflow demonstrates context actions, a guarded shipment, terminal error handling, lifecycle hooks, batch outcomes, and compensation:

stateDiagram-v2 Created --> Paid : Pay [action] Paid --> Packed : Pack [action] Packed --> Shipped : Ship [guard] [action] Shipped --> Delivered : Deliver Paid --> Cancelled : Cancel Packed --> Cancelled : Cancel Shipped --> Returned : Return [action]

Run it locally with:

moon run examples/approval_workflow moon run examples/order_workflow

Run the reproducible workflow benchmark corpus:

moon run benchmarks

The benchmark is a scenario benchmark rather than a hardware-dependent throughput claim. Its input cases are checked in under benchmarks/data/workflow_cases.csv, and the runner verifies expected final states, successful transitions, rejected events, successful history lengths, and one audit entry for every event attempt.

The vending machine example was further revised after the formal acceptance feedback on July 17, 2026. It no longer relies on duplicate (state, event) definitions, and now demonstrates a blocked purchase attempt followed by a successful retry after more coins are inserted.

#Core API

  • Builder::new() creates a workflow definition.
  • transition() and transition_if() add plain and guarded transitions.
  • transition_do() and transition_if_do() attach context-updating actions.
  • build() materializes an Engine.
  • try_send() returns TransitionError values for structured handling.
  • history() returns successful transition records.
  • checkpoint() and restore() provide explicit state/context/history snapshots for compensating workflows.
  • try_send_all() returns ordered BatchReport outcomes without silently stopping at the first rejected event.
  • metrics() and audit_log() expose execution evidence; reset_metrics() clears counters and audit entries while retaining the current workflow state.
  • last_error() exposes the most recent runtime failure.
  • validate_report() summarizes reachability and duplicate-definition issues.
  • to_mermaid() exports reviewer-friendly diagrams.

API details live in docs/api_reference.md.

#Examples

  • moon run examples/traffic_light
  • moon run examples/vending_machine
  • moon run examples/game_npc
  • moon run examples/approval_workflow
  • moon run examples/order_workflow
  • moon run cmd/fsm-cli

#Verification

The current MoonBit 0.10.3-compatible verification set is:

moon version --all moon fmt --check moon info moon check --deny-warn --target all moon test --deny-warn --target all powershell -ExecutionPolicy Bypass -File scripts/verify_acceptance.ps1 -SkipMooncakes moon run benchmarks moon publish --dry-run

moon fmt --deny-warn and moon info --deny-warn are not used because the current CLI does not expose those flags; the repository instead runs the equivalent supported checks above. On Windows machines without a system C compiler, local moon test --deny-warn --target all may stop at the native target; the CI workflow remains the source of truth for full multi-target coverage.

#Release Alignment

  • 0.1.0 was the initial Mooncakes publication.
  • 0.1.1 was the first OSC2026 re-review remediation release.
  • 0.2.0 is the workflow-runtime expansion release with snapshots, batch dispatch, metrics, audit records, a 32-case corpus, and a runnable order workflow.
  • 0.2.1 is the MoonBit 0.10.3 compatibility patch for executable package configuration.
  • Release alignment details live in docs/release-alignment.md.

#Documentation

#Notes For Reviewers

  • GitHub and GitLink are both public review surfaces for the same codebase.
  • Generated build output is intentionally excluded from version control.
  • The checked-in competition material is 申报书.md.

#Contributing

Small, reviewable changes are preferred. Before opening a PR, run the same verification commands listed above and keep examples executable.

#
Callback

type Callback[S, E, Ctx] = (S, E, Ctx) -> Unit

A callback function that executes during a state lifecycle hook.

#
Guard

type Guard[S, E, Ctx] = (S, E, Ctx) -> Bool

A guard function that determines if a transition is allowed.

#
TransitionAction

type TransitionAction[S, E, Ctx] = (S, E, Ctx) -> Ctx

A transition action that returns the next context value for a transition.

#
AuditRecord

pub(all) struct AuditRecord[S, E] {
from : S
to : S
event : E
success : Bool
error : TransitionError?
history_index : Int?
}

Complete audit entry for one event attempt.

#
BatchReport

pub(all) struct BatchReport[E] {
outcomes : Array[DispatchOutcome[E]]
successful : Int
rejected : Int
complete : Bool
}

Summary of an ordered, best-effort event batch.

#
Builder

pub(all) struct Builder[S, E, Ctx] {
config : EngineConfig[S, E, Ctx]
}

A Builder for constructing finite state machines declaratively.

#
Builder::build

fn[S : Hash + Eq, E : Hash + Eq, Ctx] Builder::build(self : Builder[S, E, Ctx], initial_state : S, initial_context : Ctx) -> Engine[S, E, Ctx]

Builds the FSM runtime engine starting at the given state.

#
Builder::new

fn[S : Hash + Eq, E, Ctx] Builder::new() -> Builder[S, E, Ctx]

Creates a new FSM Builder.

#
Builder::on_enter

fn[S : Hash + Eq, E, Ctx] Builder::on_enter(self : Builder[S, E, Ctx], state : S, callback : (S, E, Ctx) -> Unit) -> Builder[S, E, Ctx]

Registers a callback to be executed when entering a specific state.

#
Builder::on_exit

fn[S : Hash + Eq, E, Ctx] Builder::on_exit(self : Builder[S, E, Ctx], state : S, callback : (S, E, Ctx) -> Unit) -> Builder[S, E, Ctx]

Registers a callback to be executed when exiting a specific state.

#
Builder::transition

fn[S, E, Ctx] Builder::transition(self : Builder[S, E, Ctx], from : S, event : E, to : S) -> Builder[S, E, Ctx]

Adds a transition from one state to another triggered by an event.

#
Builder::transition_do

fn[S, E, Ctx] Builder::transition_do(self : Builder[S, E, Ctx], from : S, event : E, to : S, action : (S, E, Ctx) -> Ctx) -> Builder[S, E, Ctx]

Adds a transition with a context-updating action.

#
Builder::transition_if

fn[S, E, Ctx] Builder::transition_if(self : Builder[S, E, Ctx], from : S, event : E, to : S, guard_cond : (S, E, Ctx) -> Bool) -> Builder[S, E, Ctx]

Adds a transition with a guard condition.

#
Builder::transition_if_do

fn[S, E, Ctx] Builder::transition_if_do(self : Builder[S, E, Ctx], from : S, event : E, to : S, guard_cond : (S, E, Ctx) -> Bool, action : (S, E, Ctx) -> Ctx) -> Builder[S, E, Ctx]

Adds a guarded transition with a context-updating action.

#
ContextManager

pub(all) struct ContextManager[Ctx] {
inner : Ctx
}

Internal context manager structure. Abstracting this logic allows future expansion for nested context trees.

#
ContextManager::get

fn[Ctx] ContextManager::get(self : ContextManager[Ctx]) -> Ctx

Retrieves the current context value.

#
ContextManager::new

fn[Ctx] ContextManager::new(initial : Ctx) -> ContextManager[Ctx]

Creates a new ContextManager.

#
ContextManager::set

fn[Ctx] ContextManager::set(self : ContextManager[Ctx], val : Ctx) -> Unit

Sets a new context value.

#
DispatchOutcome

pub(all) struct DispatchOutcome[E] {
event : E
success : Bool
error : TransitionError?
}

The result of one event attempt in an ordered batch dispatch.

#
DuplicateTransition

pub(all) struct DuplicateTransition[S, E] {
from : S
event : E
} derive(Eq)

A duplicate transition entry discovered during validation.

#
Engine

pub(all) struct Engine[S, E, Ctx] {
transitions : Map[S, Map[E, S]]
guards : Map[S, Map[E, (S, E, Ctx) -> Bool]]
actions : Map[S, Map[E, (S, E, Ctx) -> Ctx]]
on_enter : Map[S, (S, E, Ctx) -> Unit]
on_exit : Map[S, (S, E, Ctx) -> Unit]
history_entries : Array[TransitionRecord[S, E]]
audit_entries : Array[AuditRecord[S, E]]
metrics_value : ExecutionMetrics
current_state : S
context : Ctx
last_error_value : TransitionError?
build_error : TransitionError?
}

The runtime instance of the state machine.

#
Engine::audit_log

fn[S, E, Ctx] Engine::audit_log(self : Engine[S, E, Ctx]) -> Array[AuditRecord[S, E]]

Returns the complete event-attempt audit log.

#
Engine::checkpoint

fn[S, E, Ctx] Engine::checkpoint(self : Engine[S, E, Ctx]) -> EngineSnapshot[S, E, Ctx]

Captures the observable runtime state of an engine.

#
Engine::context

fn[S, E, Ctx] Engine::context(self : Engine[S, E, Ctx]) -> Ctx

Retrieves the current context of the engine.

#
Engine::history

fn[S, E, Ctx] Engine::history(self : Engine[S, E, Ctx]) -> Array[TransitionRecord[S, E]]

Returns the successful transition history.

#
Engine::last_error

fn[S, E, Ctx] Engine::last_error(self : Engine[S, E, Ctx]) -> TransitionError?

Returns the most recent transition error, if any.

#
Engine::metrics

fn[S, E, Ctx] Engine::metrics(self : Engine[S, E, Ctx]) -> ExecutionMetrics

Returns a copy of the execution metrics.

#
Engine::reset_metrics

fn[S, E, Ctx] Engine::reset_metrics(self : Engine[S, E, Ctx]) -> Unit

Resets metrics and audit entries without changing workflow state.

#
Engine::restore

fn[S, E, Ctx] Engine::restore(self : Engine[S, E, Ctx], snapshot : EngineSnapshot[S, E, Ctx]) -> Unit

Restores state, history, audit records, metrics, and the last error.

#
Engine::send

fn[S : Hash + Eq, E : Hash + Eq, Ctx] Engine::send(self : Engine[S, E, Ctx], event : E) -> Result[Unit, String]

Sends an event to the state machine engine to trigger a transition.

#
Engine::state

fn[S, E, Ctx] Engine::state(self : Engine[S, E, Ctx]) -> S

Retrieves the current state of the engine.

#
Engine::try_send

fn[S : Hash + Eq, E : Hash + Eq, Ctx] Engine::try_send(self : Engine[S, E, Ctx], event : E) -> Result[Unit, TransitionError]

Attempts a transition and returns a structured transition error on failure.

#
Engine::try_send_all

fn[S : Hash + Eq, E : Hash + Eq, Ctx] Engine::try_send_all(self : Engine[S, E, Ctx], events : Array[E]) -> BatchReport[E]

Dispatches events in order and records every success or rejection.

#
EngineConfig

pub(all) struct EngineConfig[S, E, Ctx] {
transitions : Array[Transition[S, E, Ctx]]
on_enter : Map[S, (S, E, Ctx) -> Unit]
on_exit : Map[S, (S, E, Ctx) -> Unit]
}

Core configuration for a state machine builder.

#
EngineOptions

pub(all) struct EngineOptions {
strict_mode : Bool
max_history : Int
}

FSM engine configuration parameters.

#
EngineOptions::default

fn EngineOptions::default() -> EngineOptions

Provides default engine options.

#
EngineSnapshot

pub(all) struct EngineSnapshot[S, E, Ctx] {
state : S
context : Ctx
history_entries : Array[TransitionRecord[S, E]]
audit_entries : Array[AuditRecord[S, E]]
metrics : ExecutionMetrics
last_error : TransitionError?
}

A restorable boundary for an engine's observable runtime state.

#
ExecutionMetrics

pub(all) struct ExecutionMetrics {
attempted : Int
successful : Int
rejected : Int
guard_rejected : Int
unknown_event : Int
no_outgoing_state : Int
duplicate_configuration : Int
invalid_configuration : Int
lifecycle_hooks : Int
} derive(Eq)

Runtime counters for successful and rejected event attempts.

#
ExecutionMetrics::copy

Copies all counters so snapshots and public reads do not share mutable state.

#
ExecutionMetrics::zero

Creates an empty execution counter set.

#
Transition

pub(all) struct Transition[S, E, Ctx] {
from : S
to : S
event : E
guard_cond : (S, E, Ctx) -> Bool?
action : (S, E, Ctx) -> Ctx?
}

A transition definition specifying the source state, event, and target state.

#
TransitionError

pub(all) enum TransitionError {
NoTransitionsForCurrentState
EventNotHandledInCurrentState
GuardRejected
DuplicateTransitionDefinition
InvalidConfiguration
} derive(Eq)

Structured transition errors returned by the enhanced runtime API.

#
TransitionRecord

pub(all) struct TransitionRecord[S, E] {
from : S
event : E
to : S
used_guard : Bool
used_action : Bool
} derive(Eq)

A record of a successful transition execution.

#
ValidationReport

pub(all) struct ValidationReport[S, E] {
unreachable_states : Array[S]
dead_end_states : Array[S]
duplicate_transitions : Array[DuplicateTransition[S, E]]
states_without_outgoing_edges : Array[S]
} derive(Eq)

Structured validation output for repository acceptance and reviewer audits.

#
assert_invariant

fn assert_invariant(cond : Bool, _msg : String) -> Unit

Assert a condition, panics if false (useful for internal engine invariants).

#
duplicate_transition_entries

fn[S : Eq, E : Eq, Ctx] duplicate_transition_entries(builder : Builder[S, E, Ctx]) -> Array[DuplicateTransition[S, E]]

Returns duplicate (state, event) definitions discovered in a builder.

#
format_transition_error

fn format_transition_error(err : TransitionError) -> String

Converts a structured transition error into a stable public string.

#
format_transition_log

fn[S : Show, E : Show] format_transition_log(from : S, event : E, to : S) -> String

Helper to format transition logs.

#
to_mermaid

fn[S : Show, E : Show, Ctx] to_mermaid(builder : Builder[S, E, Ctx]) -> String

Exports the FSM configuration as a Mermaid stateDiagram string.

#
validate

fn[S : Hash + Eq, E : Eq, Ctx] validate(builder : Builder[S, E, Ctx], initial_state : S) -> Array[S]

Returns the list of unreachable states for backwards compatibility.

#
validate_report

fn[S : Hash + Eq, E : Eq, Ctx] validate_report(builder : Builder[S, E, Ctx], initial_state : S) -> ValidationReport[S, E]

Validates the FSM configuration for unreachable states, dead ends, and duplicates.